## Component map

    flowchart LR
        Browser["Client browser"] -->|"HTTPS"| Traefik["Traefik ingress"]
        Traefik --> Varnish["Varnish cache\n(static + /api/images/*)"]
        Varnish --> Next["Next.js App Router\nSSR/ISR + API routes"]
        Next --> Auth["Auth/session module\n(JWT cookie)"]
        Next --> Prisma["Prisma ORM"]
        Prisma --> Postgres[(PostgreSQL\nlistings + images)]
        Next --> Mailer["SMTP mailer\nsmtp.lomavuokraus.fi (CNAME) + DKIM"]
        Admin["Admins & moderators"] --> Traefik

Edit the Mermaid block above to evolve the architecture.

## Domain model

    erDiagram
      USER ||--o{ LISTING : owns
      USER ||--o{ LISTING : approves
      LISTING ||--|{ LISTINGTRANSLATION : has
      LISTING ||--o{ LISTINGIMAGE : has

      USER {
        string id
        string email
        string passwordHash
        string role
        string status
        datetime emailVerifiedAt
        datetime approvedAt
        datetime rejectedAt
        datetime removedAt
      }
      LISTING {
        string id
        string status
        datetime approvedAt
        datetime rejectedAt
        datetime removedAt
        string country
        string region
        string city
        float latitude
        float longitude
      }
      LISTINGTRANSLATION {
        string id
        string slug
        string title
        string locale
        string teaser
      }
      LISTINGIMAGE {
        string id
        string url
        string data
        string mimeType
        int size
        string altText
        boolean isCover
        int order
      }

## Key notes

- Web: Next.js app (App Router), server-rendered pages, client hooks for auth state.
- API routes: Authentication, admin approvals, listings CRUD (soft-delete), profile update.
- Data: Postgres via Prisma (models: User, Listing, ListingTranslation, ListingImage, VerificationToken); listing images stored as bytes + metadata and served through `/api/images/:id`.
- Caching: Varnish sidecar caches `/api/images/*` (24h) and `/_next/static` assets (7d) before requests reach Next.js.
- Mail: SMTP (smtp.lomavuokraus.fi CNAME to smtp.sohva.org) + DKIM signing for verification emails.
- Auth: Email/password, verified+approved requirement, JWT session cookie (`session_token`), roles.

### How the Next.js App Router is wired here

- Rendering model: Server Components by default for pages under `app/`; Client Components opt in with `"use client"` (forms, maps, language toggle). This keeps most data fetching server-side and ships minimal JS.
- Routes: File-system routing in `app/`; notable paths: `app/page.tsx` (home), `app/browse/page.tsx` (search + map), `app/listings/[slug]/page.tsx` (detail), and admin routes under `app/admin`. API handlers live in `app/api/*/route.ts` (REST-like endpoints used by forms and fetch calls).
- Data fetching: Server Components call Prisma directly; where client state is needed, pages expose lightweight fetchers that hit the API routes. Revalidation uses `fetch(..., { cache: 'no-store' })` for sensitive pages (profile/admin) and ISR for mostly-read content (listing details + home feed).
- Mutations: Forms post to API route handlers (e.g., auth, listing create/edit, approvals). Responses trigger router refresh on the client via `useRouter().refresh()` so the server-rendered data is re-fetched without a full page reload.
- Auth enforcement: `middleware.ts` checks the session cookie early and guards admin/listing edit areas; protected pages also re-validate the session on the server before rendering. The same session util is shared by API handlers to avoid drift.
- Assets & images: Static assets are served from `/public` and cached by Varnish and Next.js. Listing images are streamed through `/api/images/:id`; the handler sets cache headers while respecting auth where required.