diff --git a/README.md b/README.md index cf12b4c..65599cd 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ GraphQL-backend i Node og PostgreSQL. ## Prøv appen -Appen kjører på gruppens VM: +Appen kjører på gruppens VM: 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 @@ -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). @@ -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 | @@ -108,31 +127,17 @@ 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 @@ -140,27 +145,106 @@ 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