From 87f296e332947c65d8b8906160e34c1f84c0bd18 Mon Sep 17 00:00:00 2001 From: Ingrihka Date: Mon, 12 Oct 2026 15:28:05 +0200 Subject: [PATCH] docs: full datadump og seed i README (#34) --- README.md | 208 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 146 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 617c6a1..1b207e9 100644 --- a/README.md +++ b/README.md @@ -38,31 +38,37 @@ Handlelappen er en digital handleliste. Du søker opp dagligvarer eller blar i k Designet er laget i Figma. Disse skjermbildene er et første utkast og kan endre seg. ### Hjem og søk + Forsiden har søkefelt, kategorier og den aktive lappen. På søkesiden kan man filtrere på kategori, pris og merking (økologisk, laktosefri, norskprodusert) og sortere resultatene. Varer legges rett på lappen fra resultatlista, og lappen vises ved siden av. ![Hjem og søk](docs/design/hjem-og-sok.png) #### Søk og filtrering -Hva du får opp når du filtrerer på en bestemt butikk hvis den har produktet og hvis den ikke har produktet. + +Hva du får opp når du filtrerer på en bestemt butikk hvis den har produktet og hvis den ikke har produktet. ![Søk og filtrering](docs/design/sok-filtrert-pa-butikk.png) ### Produktside og meny + Produktsiden viser pris, kilopris, ingredienser, allergener og næringsinnhold. Her kan man endre antall og legge til et notat, for eksempel «skivet, hvis de har». Menyen gir tilgang til alle delene av appen. ![Produktside og meny](docs/design/produkt-og-meny.png) ### Mine handlelister og aktiv liste + Man kan ha flere aktive lister, og den man bruker nå er merket. På den aktive lista kan man endre antall, fjerne varer, sortere etter butikkavdeling og se estimert sum. ![Handlelister](docs/design/handlelister.png) ### Tidligere handlelister + Ferdige lister er gruppert etter måned. Man kan søke i dem og laste inn eldre lister. Fra en tidligere liste kan man bruke den på nytt eller legge varene på den aktive lappen. ![Tidligere lister](docs/design/tidligere-lister.png) ### Handletur (mobil) + Handleturen er laget for mobil, siden den brukes i butikken. Varene er gruppert etter avdeling, og en fremdriftslinje viser hvor mange som er krysset av. ![Handletur på mobil](docs/design/handletur-mobil.png) @@ -105,14 +111,14 @@ Backenden kjører på port 3001. Når backenden kjører lokalt, kan du åpne htt ### Implementert til innlevering 2 -| Type | Navn | Hva den gjør | -| --- | --- | --- | -| Query | `products(search, category, limit = 20, offset = 0)` | Søker i varenavn uten å skille mellom store og små bokstaver (`ILIKE`). Kan filtreres på kategori og pagineres med `limit` og `offset`. Sortert på navn. | -| Query | `product(ean)` | Henter én vare med strekkode. | -| Query | `categories` | Henter alle kategorier, sortert på navn. | -| Query | `shoppingList(id)` | Henter en handleliste med varene på den. | -| Mutation | `createShoppingList(name)` | Lager en ny handleliste. | -| Mutation | `addItemToList(listId, ean, quantity = 1)` | Legger en vare på lista. Ligger varen der fra før, økes antallet. | +| Type | Navn | Hva den gjør | +| -------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Query | `products(search, category, limit = 20, offset = 0)` | Søker i varenavn uten å skille mellom store og små bokstaver (`ILIKE`). Kan filtreres på kategori og pagineres med `limit` og `offset`. Sortert på navn. | +| Query | `product(ean)` | Henter én vare med strekkode. | +| Query | `categories` | Henter alle kategorier, sortert på navn. | +| Query | `shoppingList(id)` | Henter en handleliste med varene på den. | +| Mutation | `createShoppingList(name)` | Lager en ny handleliste. | +| Mutation | `addItemToList(listId, ean, quantity = 1)` | Legger en vare på lista. Ligger varen der fra før, økes antallet. | En `Product` har feltene `ean`, `name`, `brand`, `imageUrl`, `categoryId`, `category` og `prices`. Hver pris har `storeCode`, `price`, `unitPrice` og `url`. @@ -170,10 +176,21 @@ Dette er API-et vi har planlagt videre. Det kan endre seg underveis. ```graphql type Query { - products(search: String, filter: ProductFilter, sort: ProductSort, first: Int!, after: String): ProductConnection! + products( + search: String + filter: ProductFilter + sort: ProductSort + first: Int! + after: String + ): ProductConnection! product(id: ID!): Product categories: [Category!]! - shoppingLists(status: ListStatus, search: String, first: Int, after: String): ShoppingListConnection! + shoppingLists( + status: ListStatus + search: String + first: Int + after: String + ): ShoppingListConnection! shoppingList(id: ID!): ShoppingList } @@ -181,41 +198,58 @@ type Mutation { createShoppingList(name: String!): ShoppingList! setListInUse(listId: ID!): ShoppingList! addToList(listId: ID!, productId: ID!, quantity: Int = 1): ListItem! - updateListItem(itemId: ID!, quantity: Int, note: String, checked: Boolean): ListItem! + updateListItem( + itemId: ID! + quantity: Int + note: String + checked: Boolean + ): ListItem! removeFromList(itemId: ID!): ShoppingList! finishShopping(listId: ID!): ShoppingList! - reuseList(listId: ID!): ShoppingList! # ny aktiv liste med samme varer + reuseList(listId: ID!): ShoppingList! # ny aktiv liste med samme varer copyItemsToList(fromListId: ID!, toListId: ID!): ShoppingList! deleteShoppingList(listId: ID!): Boolean! } -input ProductFilter { categoryIds: [ID!], priceMin: Float, priceMax: Float, labels: [String!] } -enum ProductSort { RELEVANCE, NAME_ASC, NAME_DESC, PRICE_ASC, PRICE_DESC, UNIT_PRICE_ASC } +input ProductFilter { + categoryIds: [ID!] + priceMin: Float + priceMax: Float + labels: [String!] +} +enum ProductSort { + RELEVANCE + NAME_ASC + NAME_DESC + PRICE_ASC + PRICE_DESC + UNIT_PRICE_ASC +} ``` Lister med mange elementer, som søkeresultater og tidligere handlelister, returnerer én side om gangen med totalt antall og en markør (cursor) for neste side. ## Hvordan vi oppfyller kravene -| Krav | Planlagt løsning | -| --- | --- | -| Søk | Søkefelt med knapp og debounce, så vi ikke sender et kall for hver tast. Søket skiller ikke mellom store og små bokstaver og håndterer æ, ø og å. Man kan også bla i kategorier. | -| Store resultatsett | Paginering på backend. Klienten henter én side av gangen og aldri hele datasettet. Tidligere lister lastes på samme måte med «Last inn eldre lister». | -| Detaljer | Egen produktside med pris, kilopris, ingredienser, allergener og næringsinnhold. | -| Sortering og filtrering | Gjøres i databasen, så det gjelder hele resultatsettet og ikke bare det som er lastet inn. Viste filtre har antall treff, og man kan nullstille dem. | -| Brukergenererte data | Handlelister, antall, notater og avkrysninger lagres i databasen på VM-en. | -| Tilgjengelighet | Semantisk HTML, tastaturnavigasjon, synlig fokus, labels på skjemafelt og god nok kontrast (WCAG 2.1 AA). Avkrysning vises med mer enn farge (hake og overstreking). Vi tester med Lighthouse, axe og skjermleser. | -| Bærekraft | Bilder er skrudd av i resultatlista som standard («Vis bilder»), og når de vises, er de små og lastes lazy. Data hentes fra Kassalapp med en import og ikke ved hvert søk. Spørringene henter bare feltene som vises, og vi bruker få og lette biblioteker. | +| Krav | Planlagt løsning | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Søk | Søkefelt med knapp og debounce, så vi ikke sender et kall for hver tast. Søket skiller ikke mellom store og små bokstaver og håndterer æ, ø og å. Man kan også bla i kategorier. | +| Store resultatsett | Paginering på backend. Klienten henter én side av gangen og aldri hele datasettet. Tidligere lister lastes på samme måte med «Last inn eldre lister». | +| Detaljer | Egen produktside med pris, kilopris, ingredienser, allergener og næringsinnhold. | +| Sortering og filtrering | Gjøres i databasen, så det gjelder hele resultatsettet og ikke bare det som er lastet inn. Viste filtre har antall treff, og man kan nullstille dem. | +| Brukergenererte data | Handlelister, antall, notater og avkrysninger lagres i databasen på VM-en. | +| Tilgjengelighet | Semantisk HTML, tastaturnavigasjon, synlig fokus, labels på skjemafelt og god nok kontrast (WCAG 2.1 AA). Avkrysning vises med mer enn farge (hake og overstreking). Vi tester med Lighthouse, axe og skjermleser. | +| Bærekraft | Bilder er skrudd av i resultatlista som standard («Vis bilder»), og når de vises, er de små og lastes lazy. Data hentes fra Kassalapp med en import og ikke ved hvert søk. Spørringene henter bare feltene som vises, og vi bruker få og lette biblioteker. | ## Teknologi -| Del | Valg | Hvorfor | -| --- | --- | --- | -| Frontend | React, TypeScript og Vite | Kravet i oppgaven | -| State | Apollo Client | Cache for data fra GraphQL og lokal state som filtervalg og aktiv liste | -| Backend | Node, TypeScript og Apollo Server | Egen GraphQL-backend på port 3001 | -| Database | PostgreSQL direkte på VM-en | Relasjonelle data (varer, priser, lister) og godt søk og indekser | -| Kvalitet | ESLint, Prettier, Vitest og Playwright | Lint, komponenttester, ende-til-ende-tester og tester av API-et | +| Del | Valg | Hvorfor | +| -------- | -------------------------------------- | ----------------------------------------------------------------------- | +| Frontend | React, TypeScript og Vite | Kravet i oppgaven | +| State | Apollo Client | Cache for data fra GraphQL og lokal state som filtervalg og aktiv liste | +| Backend | Node, TypeScript og Apollo Server | Egen GraphQL-backend på port 3001 | +| Database | PostgreSQL direkte på VM-en | Relasjonelle data (varer, priser, lister) og godt søk og indekser | +| Kvalitet | ESLint, Prettier, Vitest og Playwright | Lint, komponenttester, ende-til-ende-tester og tester av API-et | ## Plan for innleveringene @@ -254,7 +288,8 @@ README-en til første innlevering er skrevet med hjelp fra Claude (KI), basert p - **Node.js 22.12 eller nyere** og npm. Vite 8 krever minst denne versjonen. Sjekk med `node -v`. - **PostgreSQL 14 eller nyere** med utvidelsen `pg_trgm`, som brukes til søk. Den følger med vanlige installasjoner, for eksempel Homebrew på Mac og pakken `postgresql-contrib` på Ubuntu. - **Git**. -- **Kassalapp API-nøkkel** trengs bare hvis du skal hente nye data. En ferdig dump ligger i `backend/data/kassalapp-dump.json`, så du kan sette opp databasen uten nøkkel. +- **Den fulle datadumpen** `kassalapp-dump-full.json` (ca. 200 MB, 46 853 varer). Den er for stor for git og lastes ned separat, se steg 4. Filen `backend/data/kassalapp-dump.json` i repoet har bare 199 testvarer. +- **Kassalapp API-nøkkel** trengs bare hvis du skal hente nye data fra API-et. Du trenger den ikke for å sette opp databasen. ### 1. Klon repoet @@ -288,35 +323,71 @@ cp frontend/.env.example frontend/.env **`backend/.env`** -| Variabel | Eksempel | Beskrivelse | -| --- | --- | --- | -| `PORT` | `3001` | Porten GraphQL-serveren kjører på. | -| `DATABASE_URL` | `postgres://localhost:5432/handlelapp` | Tilkobling til PostgreSQL. Legg til brukernavn og passord om databasen krever det: `postgres://bruker:passord@localhost:5432/handlelapp`. | -| `KASSALAPP_API_KEY` | | Brukes bare av `npm run import`. Kan stå tom ellers. | +| Variabel | Eksempel | Beskrivelse | +| ------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `PORT` | `3001` | Porten GraphQL-serveren kjører på. | +| `DATABASE_URL` | `postgres://localhost:5432/handlelapp` | Tilkobling til PostgreSQL. Legg til brukernavn og passord om databasen krever det: `postgres://bruker:passord@localhost:5432/handlelapp`. | +| `KASSALAPP_API_KEY` | | Brukes bare av `npm run import`. Kan stå tom ellers. | **`frontend/.env`** -| Variabel | Eksempel | Beskrivelse | -| --- | --- | --- | +| Variabel | Eksempel | Beskrivelse | +| ------------------ | ------------------------ | ---------------------------------------------------------------------------- | | `VITE_GRAPHQL_URL` | `http://localhost:3001/` | Adressen til GraphQL-backenden. Uten denne viser frontend bare en hvit side. | Vite leser `.env` bare ved oppstart, så start frontend på nytt etter at du har endret den. ### 4. Sett opp databasen -Start PostgreSQL, lag databasen og kjør skjemaet. Kommandoene kjøres fra `backend/`: +Gjør dette før du starter backenden. Kommandoene kjøres fra `backend/`. + +**a) Lag databasen og kjør skjemaet** + +Start PostgreSQL først. ```bash cd backend createdb handlelapp psql handlelapp -f db/migrations/001_init.sql +``` + +`001_init.sql` lager tabellene `categories`, `products`, `prices`, `shopping_lists` og `list_items`, med indekser for søk, filtrering og sortering. Skjemaet bruker `IF NOT EXISTS` og kan kjøres flere ganger. + +**b) Hent den fulle datadumpen** +Filen `data/kassalapp-dump.json` i repoet har bare 199 testvarer. For å få hele datasettet: + +1. Last ned [kassalapp-dump-full.json.zip](https://studntnu-my.sharepoint.com/:u:/r/personal/annisaam_ntnu_no/Documents/Semester%203/IT2810/Prosjekt%202/kassalapp-dump-full.json.zip?d=wf8e8319b6fc9450fa68034f474d85e48&csf=1&web=1&e=ht0XnP) og pakk den ut. Lenken krever NTNU-pålogging. Har du ikke tilgang, fungerer appen også med testdumpen (199 varer), eller du kan bruke versjonen på VM-en, som har alle dataene. +2. Kopier filen over testdumpen. Bytt ut `~/Downloads` med mappa du lagret den i: + + ```bash + cp ~/Downloads/kassalapp-dump-full.json data/kassalapp-dump.json + ``` + +3. Be git ignorere den lokale endringen, så filen ikke blir committet ved et uhell: + + ```bash + git update-index --skip-worktree data/kassalapp-dump.json + ``` + + Hvis du senere må hente en ny versjon av testdumpen fra git, slår du det av igjen med `git update-index --no-skip-worktree data/kassalapp-dump.json`. + +> **Ikke commit `data/kassalapp-dump.json` etter at du har byttet den ut.** Bruk `git add ` i stedet for `git add .`, og sjekk `git status` før du committer. + +**c) Fyll databasen** + +```bash npm run seed ``` -- `001_init.sql` lager tabellene `categories`, `products`, `prices`, `shopping_lists` og `list_items`, med indekser for søk, filtrering og sortering. Skjemaet bruker `IF NOT EXISTS` og kan kjøres flere ganger. -- `npm run seed` fyller databasen fra `data/kassalapp-dump.json`. Den kan også kjøres flere ganger, siden eksisterende varer og priser oppdateres i stedet for å dupliseres. Den må kjøres fra `backend/`, fordi filstien er relativ. +Med den fulle dumpen tar dette noen minutter. Ikke avbryt. Seeden kan kjøres flere ganger, siden eksisterende varer og priser oppdateres i stedet for å dupliseres. Den må kjøres fra `backend/`, fordi filstien er relativ. + +**d) Sjekk at du har alle dataene** + +```bash +psql handlelapp -c "SELECT (SELECT COUNT(*) FROM products) AS varer, (SELECT COUNT(*) FROM prices) AS priser, (SELECT COUNT(*) FROM categories) AS kategorier;" +``` -Når seeden er ferdig, skriver den ut hvor mange kategorier, varer og priser som ble lagt inn. +Med den fulle dumpen skal du få **46 853 varer, 67 132 priser og 1 299 kategorier**. Med testdumpen får du 199 varer. Får du flere priser enn 67 132, si fra i gruppa før du gjør noe mer. ### 5. Kjør backend og frontend @@ -342,7 +413,9 @@ npm run dev ### Hente nye data fra Kassalapp (valgfritt) -Dette trengs bare hvis dataene skal oppdateres. Legg inn `KASSALAPP_API_KEY` i `backend/.env` og kjør: +> **Ikke kjør dette for å sette opp prosjektet.** Importen henter alt fra API-et på nytt, tar flere timer og overskriver `data/kassalapp-dump.json`. Bruk den fulle dumpen fra steg 4 i stedet. + +Dette trengs bare hvis gruppa har bestemt at dataene skal oppdateres. Legg inn `KASSALAPP_API_KEY` i `backend/.env` og kjør: ```bash cd backend @@ -354,28 +427,29 @@ Gratisnøkkelen tillater 60 kall i minuttet, så importen tar litt tid. ### Skript -| Mappe | Kommando | Hva den gjør | -| --- | --- | --- | -| `backend/` | `npm run dev` | Starter GraphQL-serveren og restarter ved endringer. | -| `backend/` | `npm run build` | Kompilerer TypeScript til `dist/`. | -| `backend/` | `npm start` | Kjører den kompilerte serveren fra `dist/`. | -| `backend/` | `npm run import` | Henter data fra Kassalapp til `data/kassalapp-dump.json`. | -| `backend/` | `npm run seed` | Fyller databasen fra dumpen. | -| `frontend/` | `npm run dev` | Starter utviklingsserveren til Vite. | -| `frontend/` | `npm run build` | Typesjekker og bygger frontend til `dist/`. | -| `frontend/` | `npm run lint` | Kjører ESLint. | -| `frontend/` | `npm run preview` | Viser det bygde resultatet lokalt. | +| Mappe | Kommando | Hva den gjør | +| ----------- | ----------------- | --------------------------------------------------------- | +| `backend/` | `npm run dev` | Starter GraphQL-serveren og restarter ved endringer. | +| `backend/` | `npm run build` | Kompilerer TypeScript til `dist/`. | +| `backend/` | `npm start` | Kjører den kompilerte serveren fra `dist/`. | +| `backend/` | `npm run import` | Henter data fra Kassalapp til `data/kassalapp-dump.json`. | +| `backend/` | `npm run seed` | Fyller databasen fra dumpen. | +| `frontend/` | `npm run dev` | Starter utviklingsserveren til Vite. | +| `frontend/` | `npm run build` | Typesjekker og bygger frontend til `dist/`. | +| `frontend/` | `npm run lint` | Kjører ESLint. | +| `frontend/` | `npm run preview` | Viser det bygde resultatet lokalt. | ### Vanlige feil -| Feil | Løsning | -| --- | --- | -| Hvit side i nettleseren | `frontend/.env` mangler. Se steg 3, og start frontend på nytt. | -| `Failed to resolve import "..."` | Kjør `npm install` i `frontend/`. | +| Feil | Løsning | +| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| Hvit side i nettleseren | `frontend/.env` mangler. Se steg 3, og start frontend på nytt. | +| `Failed to resolve import "..."` | Kjør `npm install` i `frontend/`. | | `EADDRINUSE` eller «Port 5173 is in use» | En gammel server kjører fortsatt. Stopp den med `Ctrl + C`, eller kjør `lsof -ti :3001 :5173 \| xargs kill`. | -| `DATABASE_URL mangler` | `backend/.env` mangler. Se steg 3. | -| Tom resultatliste | Databasen er tom. Kjør `npm run seed` i `backend/`. | -| `extension "pg_trgm" is not available` | Installer `postgresql-contrib` (Ubuntu) eller bruk en PostgreSQL-installasjon som inkluderer den. | +| `DATABASE_URL mangler` | `backend/.env` mangler. Se steg 3. | +| Tom resultatliste | Databasen er tom. Kjør `npm run seed` i `backend/`. | +| Få treff, bare 199 varer | Du har seedet testdumpen. Hent den fulle dumpen og seed på nytt, se steg 4b. | +| `extension "pg_trgm" is not available` | Installer `postgresql-contrib` (Ubuntu) eller bruk en PostgreSQL-installasjon som inkluderer den. | ## Kjøre på VM-en @@ -394,6 +468,16 @@ curl -s http://:3001/ \ -d '{"query":"{ categories { name } }"}' ``` +**Data på VM-en:** Den fulle dumpen må også ligge på VM-en, ellers har den bare 199 varer. Kopier den dit og seed der: + +```bash +# Fra din egen maskin: +scp ~/Downloads/kassalapp-dump-full.json @:/backend/data/kassalapp-dump.json + +# På VM-en, i backend/: +npm run seed +``` + For å kjøre frontend lokalt mot backenden på VM-en, sett adressen i `frontend/.env` og start frontend på nytt: ```