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

## Om prototypen

Dette er første innlevering: en klikkbar prototype som viser idéen og hvordan
løsningen er tenkt brukt. Den har ingen backend, database eller innlogging.
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.

**Datalag (innlevering 2):** PostgreSQL, migrasjon og seed ligger i `backend/`.
GraphQL og kobling fra React kommer i senere issues. Se
[Backend og database](#backend-og-database).

### Idé

Expand Down Expand Up @@ -44,9 +49,9 @@ 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.
- **Ingenting lagres.** Det finnes ingen backend, database eller innlogging.
Søk og filtre virker bare på eksempelannonsene i nettleseren, og det som
er fylt ut i skjemaene, forsvinner når siden lastes inn på nytt.
- **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å.

### Data

Expand All @@ -65,15 +70,13 @@ I den ferdige løsningen ligger dataene i en database:
| Vurdering | Leietakerens vurdering etter et gjennomført leieforhold |

Annonser, bookinger og vurderinger er de brukergenererte dataene som lagres.
For å teste søk og blaing i store resultatsett fyller vi databasen med
genererte eksempelannonser via et seed-skript. Om vi henter produktdata fra en
ekstern kilde, er ikke avklart.
Eksempelannonsene seedes i PostgreSQL via `backend/` (samme innhold som mock).
Om vi henter produktdata fra en ekstern kilde, er ikke avklart.

### Planlagt videre

- **Backend og database:** En egen GraphQL-backend i Node og TypeScript med
PostgreSQL. Begge kjører direkte på gruppens VM, uten Docker. Appen blir
tilgjengelig via Apache under `/project2`, og backenden på port 3001.
- **GraphQL og VM:** API i Node og TypeScript, deploy på gruppens VM uten
Docker. Appen under Apache `/project2`, backend på port 3001.
- **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
Expand Down Expand Up @@ -129,6 +132,58 @@ npm ci # installer avhengigheter nøyaktig etter package-lock.json
npm run dev # start utviklingsserver på http://localhost:5173
```

## Backend og database

Backend ligger i `backend/` og bruker PostgreSQL. GraphQL kommer i senere
issues; foreløpig finnes migrasjon og seed for eksempelannonsene.

### Installasjon

```bash
cd backend
npm ci
cp .env.example .env
```

Rediger `.env` og sett `DATABASE_URL` til PostgreSQL-instansen du bruker
(lokalt eller på gruppens VM). Valgfritt: `PUBLIC_APP_BASE` styrer bildestier
i seed (standard `/project2`).

### Migrasjon og seed

Kjør migrasjon før seed, begge fra `backend/`:

```bash
npm run migrate
npm run seed
```

`npm run seed` er idempotent: eksisterende rader med samme `id` hoppes over
(`ON CONFLICT DO NOTHING`). Kjør seed på nytt uten duplikater.

Seed fyller de 10 annonsene fra `src/data/mockListings.ts` (id `listing-001` til
`listing-010`). `image_url` lagres som offentlige stier under
`/project2/assets/listings/` (filnavn som i `src/assets/listings/`), uten at
backend importerer SVG-filer.

**Bilder og deploy:** `npm run build` i frontend pakker SVG-ene inn i
JavaScript-bundelen. Den lager **ikke** løse filer på
`/project2/assets/listings/`. For at seedede URL-er skal fungere når klienten
senere leser fra databasen, må SVG-ene serveres **separat** på den stien, for
eksempel ved at Apache peker `/project2/assets/listings/` til en kopi av
`src/assets/listings/` på VM. Dette er ikke en del av Vite-bygget alene.

### Verifisere data

Etter migrasjon og seed:

```bash
psql "$DATABASE_URL" -c "SELECT id, title, daily_price FROM listings ORDER BY id;"
```

Forvent **10 rader**. Kjør `npm run seed` en gang til og sjekk at antall
fortsatt er 10.

## Kommandoer

| Kommando | Hva den gjør |
Expand All @@ -143,6 +198,8 @@ npm run dev # start utviklingsserver på http://localhost:5173
| `npm run format` | Formaterer alle filer med Prettier |
| `npm run format:check` | Sjekker formatering uten å endre filer |

Backend (`cd backend`): `npm run migrate`, `npm run seed`, `npm run typecheck`.

ESLint finner feil i koden, mens Prettier bestemmer formateringen.
`eslint-config-prettier` ligger sist i `eslint.config.js` og slår av
ESLint-regler som ellers ville kollidert med Prettier. Byggfiler,
Expand All @@ -154,6 +211,11 @@ kontroller som er gjort i nettleser, står i [docs/testing.md](docs/testing.md).
## Mappestruktur

```
backend/
migrations/ Versjonerte PostgreSQL-migrasjoner
src/db/ Tilkoblingspool
src/scripts/ Seed og andre engangsskript
src/seed/ Seed-data for eksempelannonser
src/
types/ Felles datatyper, for eksempel Listing
data/ Mockdata. Byttes med GraphQL-backend senere
Expand Down
5 changes: 5 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# PostgreSQL-tilkobling. Kopier til .env og tilpass verdien.
DATABASE_URL=postgresql://bruker:passord@localhost:5432/verktoyutlan

# Valgfritt: rotsti når frontend er deployet (standard /project2).
# PUBLIC_APP_BASE=/project2
4 changes: 4 additions & 0 deletions backend/.node-pg-migraterc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"migrations-dir": "migrations",
"migration-file-language": "ts"
}
48 changes: 48 additions & 0 deletions backend/migrations/1791895214550_create-listings-table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { ColumnDefinitions, MigrationBuilder } from 'node-pg-migrate';

export const shorthands: ColumnDefinitions | undefined = undefined;

const LISTING_CATEGORIES = [
'powerTools',
'handTools',
'garden',
'painting',
'measuring',
'cleaning',
] as const;

const LISTING_CONDITIONS = ['likeNew', 'good', 'worn'] as const;

function sqlInList(values: readonly string[]): string {
return values.map((value) => `'${value}'`).join(', ');
}

export async function up(pgm: MigrationBuilder): Promise<void> {
pgm.createTable('listings', {
id: { type: 'text', primaryKey: true },
title: { type: 'text', notNull: true },
category: { type: 'text', notNull: true },
description: { type: 'text', notNull: true },
area: { type: 'text', notNull: true },
daily_price: { type: 'integer', notNull: true },
condition: { type: 'text', notNull: true },
accessories: { type: 'text[]', notNull: true, default: '{}' },
image_url: { type: 'text', notNull: false },
});

pgm.addConstraint('listings', 'listings_category_check', {
check: `category IN (${sqlInList(LISTING_CATEGORIES)})`,
});

pgm.addConstraint('listings', 'listings_condition_check', {
check: `condition IN (${sqlInList(LISTING_CONDITIONS)})`,
});

pgm.addConstraint('listings', 'listings_daily_price_check', {
check: 'daily_price >= 0',
});
}

export async function down(pgm: MigrationBuilder): Promise<void> {
pgm.dropTable('listings');
}
Loading