Skip to content
Merged
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
208 changes: 146 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ GraphQL-backend i Node og PostgreSQL.

## Prøv appen

Appen kjører på gruppens VM: <https://it2810-36.idi.ntnu.no/project2/>
Appen kjører på gruppens VM: <http://it2810-36.idi.ntnu.no/project2/>

VM-en er bare tilgjengelig fra NTNU-nettet. Utenfor campus må du koble til
[NTNU VPN](https://i.ntnu.no/vpn) først. Hvis VM-en ikke svarer, kan appen
Expand All @@ -17,25 +17,44 @@ kjøres lokalt med egen database og backend, se

## Om prototypen

Første innlevering var en klikkbar **frontend-prototype** som viste idéen og
hvordan løsningen er tenkt brukt.

**Innlevering 2** kobler prototypen til en ekte database og backend:

- **Backend:** PostgreSQL med migrasjoner og seed, og et GraphQL-API i Node
og TypeScript i `backend/`. Backenden kjører som en systemd-tjeneste på VM-en,
og Apache sender `/project2/api/` videre til den.
- **Katalogen** henter annonsene fra backenden, 24 om gangen med de nyeste
først. Databasen på VM-en har 10 010 annonser.
- **Detaljvisningen** henter én annonse fra backenden, også når lenken åpnes
direkte.
- **Utleieskjemaet** lagrer nye annonser i databasen. Serveren validerer dem
på nytt, og den nye annonsen vises øverst i katalogen.

Leieforespørselen er fortsatt demo. Søk, filtre og sortering i katalogen
gjøres på serveren, i hele databasen, se
[Søk, filtre og sortering](#søk-filtre-og-sortering). Brukere kan også gi
annonser en vurdering fra 1 til 5, se [Vurderinger](#vurderinger). Se også
**Innlevering 3** er en betaversjon der hele kjernen i appen virker: søk,
filtre, sortering, blaing, detaljvisning og lagring av brukerdata, mot en
database med 10 010 annonser på VM-en.

| Innlevering | Hva den inneholdt |
| ----------- | --------------------------------------------------------------------------------------------------------------- |
| 1 | Klikkbar frontend-prototype med mockdata som viste idéen og visningene |
| 2 | PostgreSQL og GraphQL-backend på VM-en. Katalogen, detaljvisningen og utleieskjemaet koblet til backenden |
| 3 | Søk, filtre og sortering i hele databasen, vurderinger, tilgjengelighet, bærekraft og ende-til-ende-tester (ny) |

**Nytt i innlevering 3:**

- **Søk, filtre og sortering på serveren.** Søk i tittel og beskrivelse, med
filtre for kategori, område, tilstand, døgnpris og gratis utlån, og
sortering på nyeste eller pris. Alt gjøres i databasen før resultatet deles
opp i sider, se [Søk, filtre og sortering](#søk-filtre-og-sortering).
- **Aktive filtre og tomt resultat.** Valgte filtre vises som brikker som kan
fjernes én og én. Et søk uten treff sier hva som ble søkt på og har en knapp
for å vise alle annonser.
- **Veien tilbake.** Søk, filtre, sortering og side ligger i URL-en. Fra en
annonse kommer brukeren tilbake til samme søk og side, uten ny forespørsel
til serveren.
- **Vurderinger.** Brukere kan gi en annonse 1–5 stjerner og en valgfri
kommentar.
Vurderingene lagres i databasen, og nytt snitt vises med en gang, se
[Vurderinger](#vurderinger).
- **Tilgjengelighet.** Gått gjennom mot WCAG 2.1 AA med axe-core, tastatur,
320 px bredde og tekstavstand. Funnene er rettet, og axe kjører i testene,
se [docs/tilgjengelighet.md](docs/tilgjengelighet.md).
- **Bærekraft.** Målt datamengde før og etter tiltak. Komprimering og
cache-headere i Apache gjør et nytt besøk 2,2 kB i stedet for 5,7 kB, se
[docs/baerekraft.md](docs/baerekraft.md).
- **Ende-til-ende-tester** med Playwright av søk, filtre, sortering, blaing,
veien tilbake, ny annonse og vurdering, se
[docs/testing.md](docs/testing.md#ende-til-ende-tester-playwright).

Valgene bak løsningen står under
[Valg og begrunnelser](#valg-og-begrunnelser). Se også
[Frontend mot GraphQL](#frontend-mot-graphql) og
[Backend og database](#backend-og-database).

Expand All @@ -51,7 +70,7 @@ låne verktøy i nærheten i stedet for å kjøpe nytt.

| Visning | Slik kommer du dit | Hva du kan gjøre |
| -------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Katalog | Startsiden, «Katalog» i menyen eller navnet øverst | Søke, filtrere på kategori og gratis utlån, sortere og bla, 24 om gangen |
| Katalog | Startsiden, «Katalog» i menyen eller navnet øverst | Søke, filtrere på kategori, område, tilstand, pris og gratis utlån, sortere og bla, 24 om gangen |
| Detaljvisning | Klikk på et annonsekort i katalogen | Se bilde, beskrivelse, tilstand, tilbehør, pris og vurderinger, gi en vurdering, velge datoer og se samlet pris |
| Lei ut verktøy | «Lei ut verktøy» i menyen | Fylle ut skjemaet for en ny annonse med tittel, kategori, beskrivelse, område og pris |

Expand Down Expand Up @@ -108,59 +127,124 @@ mens selve annonsene genereres. Se [docs/datasett.md](docs/datasett.md).

### Planlagt videre

- **Søk i hele databasen:** Søk, filtre og sortering skjer på serveren, før
resultatet deles opp i sider. Katalogen får blaing med «Forrige» og
«Neste», og endrede valg starter på første side. Søket venter til brukeren
har sluttet å skrive (debouncing), og svar på utdaterte søk forkastes.
- **State:** Apollo Client henter og bufrer allerede data fra backenden. Søk,
filtre og sortering skal fortsatt ligge i URL-en, slik at de kan deles som
lenke.
- **Til innlevering 4:** Test med skjermleser (VoiceOver eller NVDA), flere
tester av utypisk bruk og snapshot-tester, se
[docs/testing.md](docs/testing.md#gjenstående-kontroller) og
[docs/tilgjengelighet.md](docs/tilgjengelighet.md#gjenstår).
- **Innlogging og egne annonser:** Brukere kan opprette, redigere og
deaktivere egne annonser. Serveren sjekker at bare eieren kan endre dem.
- **Booking:** Eieren godkjenner eller avslår leieforespørsler, og begge
parter ser sine leieforhold. Godkjente bookinger kan ikke overlappe for
samme verktøy. Utlevering og retur registreres, og leietakeren kan gi en
vurdering etterpå. Gratis utlån bruker samme flyt.
- **Tilgjengelighet:** Appen er gått gjennom mot WCAG 2.1 AA med axe-core,
tastatur, 320 px bredde og `prefers-reduced-motion`, og funnene er rettet,
se [docs/tilgjengelighet.md](docs/tilgjengelighet.md). Test med skjermleser
gjenstår.
- **Bærekraft:** Små bilder som lastes når de trengs, systemfont, mørkt tema,
komprimering og cache-headere i Apache, og spørringer som bare henter feltene
som vises. Målingene før og etter står i
[docs/baerekraft.md](docs/baerekraft.md).
- **Testing:** Komponenttester med Vitest, API-tester mot en testdatabase og
ende-til-ende-tester med Playwright finnes allerede, se
[docs/testing.md](docs/testing.md). Senere kommer en ende-til-ende-test av en
hel leieflyt, når booking finnes.
vurdering etterpå. Gratis utlån bruker samme flyt. Da kommer også en
ende-til-ende-test av hele leieflyten.

Betaling, depositum, frakt og chat er ikke en del av planen. Hvis kjernen blir
ferdig og testet, vurderer vi prosjektlister, for eksempel «Bygg en
bokhylle», med verktøyene som trengs.

### Dette ønsker vi tilbakemelding på

**Oppsett og samspill mellom klient og backend:**

- Var det lett å finne ut hvordan appen kjøres, både på VM-en og lokalt? Hvor
stoppet du opp?
- Er det forståelig hvordan Apache, backenden og databasen henger sammen på
VM-en, ut fra [docs/vm-backend.md](docs/vm-backend.md) og
[deploy/apache/README.md](deploy/apache/README.md)?
- Er GraphQL-skjemaet i `backend/src/graphql/schema.ts` lett å forstå? Ville
du delt opp spørringene eller feltene annerledes?
- Føles katalogen rask nok når du blar, og er det tydelig hva som skjer mens
data lastes eller hvis backenden ikke svarer?
- Dukket annonsen du la ut via «Lei ut verktøy», opp i katalogen slik du
forventet?

**Idé og brukeropplevelse:**

- Er idéen og forskjellen på leie og gratis utlån lett å forstå?
- Hvilke filtre og sorteringer ville du brukt mest når søket kommer tilbake,
for eksempel område eller ledige datoer?
- Er det tydelig hvordan en leieforespørsel fungerer?
- Er det noe i datamodellen over som virker unødvendig, eller som mangler?
**Søk og filtre:**

- Finner du raskt det du leter etter? Prøv for eksempel gratis drill på Moholt,
eller en stige under 100 kr per døgn.
- Er det tydelig hvilke filtre som er valgt, og hvordan du fjerner dem? Kom du
noen gang til et tomt resultat uten å skjønne hvorfor?
- Kommer du tilbake til samme søk og side når du åpner en annonse og går
tilbake, både med lenken og med nettleserens tilbakeknapp?
- Mangler det et filter eller en sortering du ville brukt, for eksempel
nærmeste område eller best vurdert?

**Tilgjengelighet:**

- Kan du bruke hele appen med bare tastatur, og ser du alltid hvor fokus er?
- Har du skjermleser, og fungerer søket og antall treff med den? Vi har ikke
testet med skjermleser selv ennå.
- Ser du noe som bryter med WCAG som vi ikke har fanget i
[docs/tilgjengelighet.md](docs/tilgjengelighet.md)?

**Bærekraft:**

- Er målingene i [docs/baerekraft.md](docs/baerekraft.md) overbevisende, og
måler vi det som betyr noe?
- Ser du tiltak vi har oversett, for eksempel i bundle-størrelsen eller i
hvordan katalogen henter data?

**Kodekvalitet:**

- Er koden lett å sette seg inn i? Prøv for eksempel å følge et søk fra
`CatalogSearch` til SQL-en i `backend/src/db/listingsQuery.ts`.
- Er det deler som er vanskelige å forstå, eller som burde vært delt opp?
- Er testene i [docs/testing.md](docs/testing.md) dekkende, og hva ville du
testet i tillegg?

**Oppsett:**

- Var det lett å kjøre appen, både på VM-en og lokalt? Hvor stoppet du opp?

## Valg og begrunnelser

### Søk, filtre og skalering

Søk, filtre og sortering gjøres i PostgreSQL før resultatet deles opp i sider
på 24 annonser. Klienten henter derfor bare én side om gangen, og filtrene
gjelder hele databasen, ikke bare det som er lastet inn. `totalCount` gir
antall treff i hele databasen, så katalogen kan vise riktig antall treff og
sider.

Løsningen er laget for å tåle mye mer data enn vi har:

- **Indekser** for sortering, søk i tekst (trigram, `pg_trgm`), kategori,
område og pris. Med 50 000 annonser tar søk etter vanlige og sjeldne ord
under 2 ms, mot rundt 50 ms uten indeks. Svært vanlige ord, som «til», er
fortsatt tregere. Målingene og begrensningene står i
[Indekser for søk og filtre](docs/datasett.md#indekser-for-søk-og-filtre).
- **Blaing med `limit` og `offset`.** De første sidene tar like kort tid
uansett datamengde. Sider langt bak blir litt tregere, men det merkes lite
med 50 000 annonser. Målingene og alternativet med cursor-paginering står i
[Paginering og indeks](docs/datasett.md#paginering-og-indeks).
- **Færre forespørsler.** Søket sendes først når brukeren har sluttet å
skrive (debouncing), og Apollo-cachen gjør at en side som er hentet før,
vises uten ny forespørsel.
- **Ingen N+1.** Snitt og antall vurderinger regnes ut i samme spørring som
annonsene, ikke med én ekstra spørring per kort.

### State management

Vi bruker ikke et eget state-bibliotek som Redux, fordi tilstanden i appen
deler seg naturlig i tre:

| Tilstand | Hvor den ligger | Hvorfor |
| ------------------------------------ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| Data fra serveren | Apollo Client sin cache | Normalisert cache: en annonse som er oppdatert ett sted, for eksempel med ny vurdering, oppdateres overalt. Laste- og feiltilstand følger med |
| Søk, filtre, sortering og side | URL-en | Overlever sideoppdatering, kan deles som lenke og virker med nettleserens tilbakeknapp |
| Siste søk til «Katalog» i menyen | `sessionStorage` | Søket huskes i fanen, men dukker ikke opp igjen neste dag, se [docs/sok-og-filtre.md](docs/sok-og-filtre.md#katalog-i-menyen-husker-søket) |
| Skjemafelt, åpne paneler og lignende | Vanlig React-state | Gjelder bare én komponent |

Et globalt lager ville kopiert data som allerede finnes i Apollo-cachen eller
URL-en, og måtte holdes i takt med dem.

### Komponenter og biblioteker

Brukergrensesnittet er laget med egne komponenter og CSS-moduler, uten et
komponentbibliotek. Vi har full kontroll over semantikken og
tilgjengeligheten, og bunten blir mindre, se
[JS-bunten](docs/baerekraft.md#js-bunten). Bibliotekene vi bruker:

| Bibliotek | Brukes til | Hvorfor |
| --------------------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| React 19 og TypeScript | Brukergrensesnittet | Krav i emnet |
| React Router | Ruter og valgene i URL-en | `useSearchParams` gjør URL-en til tilstand for søk og filtre |
| Apollo Client (med `graphql` og `rxjs`) | Henting og cache av data | Laget for GraphQL, med normalisert cache, `MockedProvider` til testene og oppdatering av cachen etter mutasjoner |
| Apollo Server 5 | GraphQL-API-et | Utbredt og godt dokumentert, og lite kode for å sette opp skjema og resolvere |
| `pg` (node-postgres) | Spørringer mot PostgreSQL | Vi skriver SQL selv med parametere og har kontroll over indekser og spørreplaner, uten en ORM imellom |
| `node-pg-migrate` | Endringer i databasen | Versjonerte migrasjoner som kan kjøres likt lokalt, i testdatabasen og på VM-en |
| `tsx` | Kjøre backenden | Kjører TypeScript direkte, uten et eget byggesteg, både lokalt og i systemd på VM-en |
| Vitest, React Testing Library og jsdom | Komponent- og enhetstester | Bruker samme oppsett som Vite. Testing Library finner elementer slik en bruker gjør, med rolle og navn |
| axe-core | Automatisk sjekk av tilgjengelighet | Samme motor som Lighthouse, og kjører både i testene og i nettleseren |
| Playwright | Ende-til-ende-tester | Styrer en ekte nettleser mot backenden og testdatabasen |
| ESLint og Prettier | Kodekvalitet og formatering | Krav i emnet. `eslint-config-prettier` hindrer at de to krangler |

## Krav

Expand Down