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
95 changes: 77 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ privatpersoner i Trondheim. Bygget med React, TypeScript og Vite.

## Om prototypen

Første innlevering var en klikkbar **frontend-prototype** som viser idéen og
hvordan løsningen er tenkt brukt. Katalogen henter fortsatt data fra mock i
nettleseren, og leie/utleie er demo uten innlogging.
Første innlevering var en klikkbar **frontend-prototype** som viste idéen og
hvordan løsningen er tenkt brukt.

**Datalag (innlevering 2):** PostgreSQL, migrasjon, seed og GraphQL-API ligger i
`backend/`. Frontend henter fortsatt mock i nettleseren; Apollo Client kommer i
senere issue. Se [Backend og database](#backend-og-database).
**Innlevering 2:** PostgreSQL, migrasjon, seed og GraphQL-API ligger i
`backend/`. Katalogen og detaljvisningen henter nå annonsene fra backenden med
Apollo Client, én side om gangen. Leieforespørsel og utleieskjema er fortsatt
demo. Se [Frontend mot GraphQL](#frontend-mot-graphql) og
[Backend og database](#backend-og-database).

### Idé

Expand All @@ -27,7 +28,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 | Se alle annonsene, søke, filtrere på kategori og gratis utlån og sortere på pris |
| Katalog | Startsiden, «Katalog» i menyen eller navnet øverst | Bla gjennom alle annonsene i databasen, 24 om gangen, med de nyeste først |
| Detaljvisning | Klikk på et annonsekort i katalogen | Se bilde, beskrivelse, tilstand, tilbehør og pris, 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 All @@ -49,9 +50,8 @@ annonser viser for eksempel «150 kr/døgn», mens gratis annonser viser
Ingen forespørsel blir sendt, og verktøyet blir ikke reservert.
- **Utleieskjemaet er en demo.** Knappen heter «Legg ut verktøy (demo)».
Annonsen blir ikke lagret og vises ikke i katalogen.
- **Appen lagrer ikke leie eller nye annonser.** Utleie- og leieforespørsel er
demo. Søk og filtre i katalogen går mot mockdata i nettleseren. Seed i
databasen er for backend og senere API, ikke noe katalogen leser ennå.
- **Appen lagrer ikke leie eller nye annonser ennå.** Utleieskjema og
leieforespørsel er demo. Katalogen og detaljvisningen leser fra databasen.

### Data

Expand Down Expand Up @@ -131,9 +131,61 @@ TypeScript 7.

```bash
npm ci # installer avhengigheter nøyaktig etter package-lock.json
npm run dev # start utviklingsserver på http://localhost:5173
npm run dev # start utviklingsserver på http://localhost:5173/project2/
```

Katalogen henter annonsene fra backenden. Uten backend viser appen en
feilmelding med «Prøv igjen». Se [Frontend og backend sammen](#frontend-og-backend-sammen).

## Frontend mot GraphQL

Frontend bruker [Apollo Client](https://www.apollographql.com/docs/react/) for
å hente data fra GraphQL-backenden. Vi valgte Apollo fordi:

- **Cache:** Svar lagres i en normalisert cache. En side eller annonse som er
hentet før, vises med en gang og uten ny forespørsel når brukeren går
tilbake.
- **Tilstand:** `useQuery` gir laste-, feil- og datatilstand og `refetch` for
«Prøv igjen», så vi slipper å skrive dette selv.
- **Testing:** `MockedProvider` lar komponenttestene bruke mockede
GraphQL-svar uten backend.
- **Videre arbeid:** Apollo er laget for GraphQL og håndterer mutasjoner og
oppdatering av cachen når brukere lagrer data.

| Fil | Innhold |
| ------------------------------- | -------------------------------------------- |
| `src/graphql/client.ts` | Apollo-klienten og adressen til API-et |
| `src/graphql/listingQueries.ts` | Spørringene for katalogen og detaljvisningen |
| `src/test/listingMocks.ts` | Mockede GraphQL-svar til testene |

Katalogen henter bare feltene annonsekortet viser, og 24 annonser per side.
Én side er omtrent 4 kB. Resten av opplysningene hentes når brukeren åpner en
annonse. Gjeldende side ligger i URL-en som `?side=2`.

**Samme adresser lokalt og på VM-en:** Appen har `base: '/project2/'` i
`vite.config.ts`, og klienten sender spørringer til `/project2/api/graphql`.
På VM-en sender Apache dette videre til backenden. Lokalt gjør Vite det samme
med en proxy til `http://localhost:3001`. Annonsebildene ligger i
`public/assets/listings/` og har samme adresse som `image_url` i databasen.

### Frontend og backend sammen

Første gang: sett opp PostgreSQL, `backend/.env`, migrasjon og seed som
beskrevet i [Lokal backend-utvikling](#lokal-backend-utvikling). Deretter, i
to terminaler:

```bash
# Terminal 1: backend på port 3001
cd backend
npm run dev

# Terminal 2: frontend på port 5173
npm run dev
```

Åpne <http://localhost:5173/project2/>. Kjør `npm run seed:large` i `backend/`
for å se katalogen med 10 010 annonser.

## Backend og database

Backend ligger i `backend/` og bruker PostgreSQL. Apollo Server 5 eksponerer
Expand Down Expand Up @@ -226,9 +278,9 @@ Forvent **10 010** rader i `listings` etter første kjøring, og «0 nye» om du
kjører den på nytt. Tar under ett sekund for 10 000 annonser. Datakilde,
fordelinger og målinger står i [docs/datasett.md](docs/datasett.md).

**Bilder og deploy:** Seed lagrer `image_url` under `/project2/assets/listings/`.
Det gjelder VM/Apache senere — se [deploy/apache/README.md](deploy/apache/README.md).
Lokal frontend bruker fortsatt mock og importerte SVG-er.
**Bilder:** Seed lagrer `image_url` under `/project2/assets/listings/`. Filene
ligger i `public/assets/listings/`, så Vite serverer dem på samme sti både ved
lokal utvikling og i bygget.

### GraphQL lokalt

Expand Down Expand Up @@ -347,15 +399,17 @@ backend/
src/server.ts Apollo Server (GraphQL)
src/
types/ Felles datatyper, for eksempel Listing
data/ Mockdata. Byttes med GraphQL-backend senere
graphql/ Apollo-klient og GraphQL-spørringer
data/ Eksempelannonser til designreferansen og testene
utils/ Formatering av pris og etiketter, filtrering og sortering
assets/ Illustrasjoner til eksempelannonsene
styles/ Designvariabler og grunnstiler
components/ Gjenbrukbare komponenter, én mappe per komponent
pages/ Én mappe per side
hooks/ Små felles hooks, for eksempel usePageTitle
design/ Designreferanse, bare under utvikling
test/ Felles oppsett for testene
test/ Felles oppsett, mockede GraphQL-svar og renderApp for testene
public/
assets/listings/ Illustrasjoner til annonsene, samme sti som image_url
```

Komponenter i `components/` får data gjennom props og importerer ikke
Expand All @@ -368,7 +422,7 @@ Rutene er definert i `src/App.tsx` med React Router.

| Rute | Side | Innhold nå |
| ---------------------- | ------------------- | ------------------------------------------- |
| `/` | `CatalogPage` | Annonsekort med søk, filtre og sortering |
| `/` | `CatalogPage` | Annonser fra GraphQL, én side om gangen |
| `/annonser/:listingId` | `ListingDetailPage` | Alle opplysninger og leieforespørsel (demo) |
| `/lei-ut` | `RentOutPage` | Skjema for å legge ut verktøy (demo) |
| Alle andre | `NotFoundPage` | «Fant ikke siden» |
Expand All @@ -379,6 +433,11 @@ hopp-lenke automatisk.

## Søk, filtre og sortering

> **Midlertidig skjult.** Katalogen henter nå én side om gangen fra serveren.
> Søk, filtre og sortering i nettleseren ville bare virket på de 24 annonsene
> som er lastet, ikke på hele katalogen. De er derfor tatt ut av katalogen til
> serveren støtter dem. Logikken og testene under ligger fortsatt i `src/`.
Katalogen kan snevres inn med fritekstsøk, kategori og gratis utlån. Søket
leter i tittel og beskrivelse og skiller ikke mellom store og små bokstaver.
Filtrene kan kombineres, og et tomt søk viser alle annonser.
Expand Down
2 changes: 1 addition & 1 deletion backend/src/seed/listingImageUrl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Offentlig bildesti for annonser etter deploy under /project2.
* Filnavn tilsvarer SVG-ene i frontend src/assets/listings/.
* Filnavn tilsvarer SVG-ene i frontend public/assets/listings/.
*/

const DEFAULT_APP_BASE = '/project2';
Expand Down
2 changes: 1 addition & 1 deletion backend/src/seed/listingTexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ export const manualOnlyBrands: ReadonlySet<string> = new Set([
]);

/**
* Bilder som finnes i frontend (src/assets/listings). De brukes bare på
* Bilder som finnes i frontend (public/assets/listings). De brukes bare på
* verktøytyper bildet faktisk viser, slik at en vinkelsliper ikke får bilde av
* en drill. Alle andre annonser vises med plassholder.
*/
Expand Down
44 changes: 21 additions & 23 deletions deploy/apache/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ Apache-oppsett. **Ikke aktiver på VM ennå** — krever bygget frontend med rik

## Vite: base path og statiske filer

| Tema | Status i repo nå | Krav ved VM-deploy |
| ------------------------ | -------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `base` | Standard `/` i `vite.config.ts` | Sett `base: '/project2/'` i eget frontend-issue før produksjonsbuild |
| React Router | `basename={import.meta.env.BASE_URL}` i `main.tsx` | Følger automatisk Vite `base` |
| JS/CSS fra build | Hashede filer under `dist/assets/` | URL-er blir `/project2/assets/…` når `base` er satt |
| SVG i mock | Importert i bundle | Fungerer i dagens prototype |
| SVG i seed (`image_url`) | `/project2/assets/listings/*.svg` | **Ikke** fra Vite-build — egen Apache-`Alias` i `t36-project2.conf` mot `src/assets/listings/` |
| Tema | Status i repo nå | Krav ved VM-deploy |
| ---------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `base` | `/project2/` i `vite.config.ts` | Ingen endring |
| React Router | `basename={import.meta.env.BASE_URL}` i `main.tsx` | Følger automatisk Vite `base` |
| JS/CSS fra build | Hashede filer under `dist/assets/` | URL-er blir `/project2/assets/…` når `base` er satt |
| Annonsebilder | `public/assets/listings/*.svg` | Blir med i bygget under `/project2/assets/listings/`, samme sti som `image_url` i databasen |

Bygg (etter `base` er satt):

Expand Down Expand Up @@ -48,9 +47,9 @@ Valgfritt senere: `headers` for cache på statiske filer.

## Filer

| Fil | Formål |
| ------------------- | -------------------------------------------------- |
| `t36-project2.conf` | Egen site: statisk SPA, listing-SVG, reverse proxy |
| Fil | Formål |
| ------------------- | --------------------------------------- |
| `t36-project2.conf` | Egen site: statisk SPA og reverse proxy |

Plassholdere:

Expand Down Expand Up @@ -82,13 +81,12 @@ Uten `mod_rewrite` vil direkte navigering til f.eks. `/project2/annonser/listing
gi 404 fra Apache. `RewriteRule` i `t36-project2.conf` sender ukjente stier til
`/project2/index.html` når fil/mappe ikke finnes.

## Listing-SVG (seed)
## Annonsebilder

Seed lagrer `image_url` som `/project2/assets/listings/drill.svg` osv.
**Mer spesifikk** `Alias` for `/project2/assets/listings` kommer **før** generell
`Alias /project2`, slik at SVG ikke overskygges av SPA-fallback.

Filene ligger i repo: `src/assets/listings/*.svg` (samme navn som i seed).
Seed lagrer `image_url` som `/project2/assets/listings/drill.svg` osv. Filene
ligger i `public/assets/listings/`, så Vite kopierer dem til
`dist/assets/listings/`, og de serveres av samme `Alias /project2` som resten
av appen. Det trengs ingen egen `Alias` for bildene.

## Installasjon på VM (når klart — ikke kjør enable før dere er klare)

Expand Down Expand Up @@ -139,10 +137,10 @@ ikke gi Apache 404.

## Feilsøking

| Symptom | Mulig årsak |
| ---------------------------------- | --------------------------------------------------- |
| `configtest` feiler på `ProxyPass` | `proxy` / `proxy_http` ikke enabled |
| SPA 404 ved refresh | `rewrite` ikke enabled, eller feil `RewriteBase` |
| SVG 404 | Feil `__REPO_ROOT__`, eller listing-`Alias` mangler |
| API 502/503 | Backend ikke startet på 3001 |
| `/project2` ødelegger P1 | Feil site aktivert — bruk kun `t36-project2.conf` |
| Symptom | Mulig årsak |
| ---------------------------------- | ------------------------------------------------- |
| `configtest` feiler på `ProxyPass` | `proxy` / `proxy_http` ikke enabled |
| SPA 404 ved refresh | `rewrite` ikke enabled, eller feil `RewriteBase` |
| SVG 404 | Frontend er bygget uten `public/assets/listings` |
| API 502/503 | Backend ikke startet på 3001 |
| `/project2` ødelegger P1 | Feil site aktivert — bruk kun `t36-project2.conf` |
13 changes: 2 additions & 11 deletions deploy/apache/t36-project2.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,8 @@
#
# Ikke rediger prosjekt 1s vhost. Aktiver med a2ensite t36-project2.conf.

# Seed/API-bilder: løse SVG-er (Vite bygger dem ikke ut til denne stien)
<IfModule mod_alias.c>
Alias /project2/assets/listings __REPO_ROOT__/src/assets/listings
</IfModule>

<Directory "__REPO_ROOT__/src/assets/listings">
Require all granted
Options -Indexes
</Directory>

# Frontend (Vite build med base: '/project2/')
# Frontend (Vite build med base: '/project2/'). Annonsebildene ligger i
# public/assets/listings og blir med i bygget under /project2/assets/listings.
<IfModule mod_alias.c>
Alias /project2 __DIST_DIR__
</IfModule>
Expand Down
21 changes: 21 additions & 0 deletions docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,27 @@ bekreftelsen sier at ingenting er sendt eller lagret. Bekreftelsen ligger i et
`role="status"`-område, så skjermlesere leser den opp. Bruk `key`, slik at
skjemaet nullstilles når brukeren går til en annen annonse.

### Pagination

«Forrige» og «Neste» mellom sidene, med «Side 2 av 418» mellom. Sidene er
lenker, så siden ligger i URL-en og «Tilbake» i nettleseren virker. På første
og siste side vises teksten uten lenke og med `aria-disabled`, så den ikke får
fokus. Komponenten vises ikke når alt får plass på én side.

```tsx
<Pagination page={2} totalPages={418} pageLink={catalogPageLink} />
```

### QueryError

Feilmelding når data ikke kan hentes fra backenden, med knappen «Prøv igjen».
Den har `role="alert"`, så skjermlesere leser den med en gang, og rød ramme i
tillegg til teksten.

```tsx
<QueryError what="annonsene" onRetry={() => void refetch()} />
```

### ListingGrid

Responsivt rutenett med ett `ListingCard` per annonse. Annonsene vises i den
Expand Down
1 change: 1 addition & 0 deletions docs/ki-bruk.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ ser hva vi bør være oppmerksomme på.
| #35 | Undersøkte eksisterende backend, utleieskjema og `listings`-tabell; foreslo og kontrollerte Apollo Server 5, GraphQL-schema, resolvere, PostgreSQL-repository med parameterisert SQL, servervalidering som frontend, og Vitest-integrasjonstester mot egen testdatabase | Typecheck og `npm test` i `backend/` mot lokal database med `_test`-suffiks. README, testing.md og deploy-maler oppdatert. Frontend uendret. PR og merge gjøres av gruppen |
| #39 | Søkte etter åpne datasett om verktøy på nettet (Wikidata, Open Products Facts, Geonorge) og foreslo Wikidata for verktøytyper og merker. Veiledet oppsett av lokal PostgreSQL-database. Skrev skript som henter verktøymodeller fra Wikidata (`import:tools`), migrasjon for `created_at`, generator for varierte annonser med tester og `seed:large` som setter inn datasettet | Wikidata-kategoriene ga mye støy, så verktøytypene velges med faste ID-er. Stikkprøver av genererte annonser viste tekster, tilbehør og merker som ikke passet verktøyet (for eksempel «båndsliper» som «passer til å skru opp hyller»), og dette ble rettet før commit. Migrasjon testet med up/down mot lokal database. |
| #42 | Laget paginering i `listings` med validering, `createdAt` i skjemaet, indeks for sorteringen og API-tester, og målte spørringene med `EXPLAIN ANALYZE` | Kjørt mot ekte PostgreSQL 18 lokalt med 10 010 og 50 010 annonser. En test for rekkefølge ved like tidspunkt bestod også med feil sortering, fordi seed-annonsene har ulike tidspunkt. Testen ble skrevet om med egne testannonser og kontrollert ved å innføre feilen med vilje. Målingene viste at sider langt bak blir tregere med `OFFSET`, og det er dokumentert |
| #43 | Hjalp med oppsett av Apollo Client, katalog med sider, detaljvisning mot GraphQL og mockede GraphQL-svar i testene | Gruppen testet hele flyten lokalt mot PostgreSQL med 10 010 annonser, også feiltilstanden med backenden stoppet, og koden gjennomgås i PR før merge |
Loading