lomavuokraus/docs/architecture.html
Tero Halla-aho 23b18c75bd
Some checks are pending
CI / checks (push) Waiting to run
docs: expand Next.js App Router notes
2026-02-04 10:17:54 +02:00

112 lines
5.2 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Logical Architecture</title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<header>
<h1>Logical Architecture</h1>
<div class="meta">Next.js App Router, Prisma/Postgres, role-based auth, email verification, approvals.</div>
</header>
<main class="grid">
<section class="card">
<h2>Component map</h2>
<div class="diagram">
<pre class="mermaid">
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
</pre>
</div>
<div class="callout">Edit the Mermaid block above to evolve the architecture.</div>
</section>
<section class="card">
<h2>Domain model</h2>
<div class="diagram">
<pre class="mermaid">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
}
</pre>
</div>
</section>
<section class="card">
<h2>Key notes</h2>
<ul>
<li><strong>Web</strong>: Next.js app (App Router), server-rendered pages, client hooks for auth state.</li>
<li><strong>API routes</strong>: Authentication, admin approvals, listings CRUD (soft-delete), profile update.</li>
<li><strong>Data</strong>: Postgres via Prisma (models: User, Listing, ListingTranslation, ListingImage, VerificationToken); listing images stored as bytes + metadata and served through <code>/api/images/:id</code>.</li>
<li><strong>Caching</strong>: Varnish sidecar caches <code>/api/images/*</code> (24h) and <code>/_next/static</code> assets (7d) before requests reach Next.js.</li>
<li><strong>Mail</strong>: SMTP (smtp.lomavuokraus.fi CNAME to smtp.sohva.org) + DKIM signing for verification emails.</li>
<li><strong>Auth</strong>: Email/password, verified+approved requirement, JWT session cookie (<code>session_token</code>), roles.</li>
</ul>
<h3>How the Next.js App Router is wired here</h3>
<ul>
<li><strong>Rendering model</strong>: Server Components by default for pages under <code>app/</code>; Client Components opt in with <code>"use client"</code> (forms, maps, language toggle). This keeps most data fetching server-side and ships minimal JS.</li>
<li><strong>Routes</strong>: File-system routing in <code>app/</code>; notable paths: <code>app/page.tsx</code> (home), <code>app/browse/page.tsx</code> (search + map), <code>app/listings/[slug]/page.tsx</code> (detail), and admin routes under <code>app/admin</code>. API handlers live in <code>app/api/*/route.ts</code> (REST-like endpoints used by forms and fetch calls).</li>
<li><strong>Data fetching</strong>: Server Components call Prisma directly; where client state is needed, pages expose lightweight fetchers that hit the API routes. Revalidation uses <code>fetch(..., { cache: 'no-store' })</code> for sensitive pages (profile/admin) and ISR for mostly-read content (listing details + home feed).</li>
<li><strong>Mutations</strong>: Forms post to API route handlers (e.g., auth, listing create/edit, approvals). Responses trigger router refresh on the client via <code>useRouter().refresh()</code> so the server-rendered data is re-fetched without a full page reload.</li>
<li><strong>Auth enforcement</strong>: <code>middleware.ts</code> 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.</li>
<li><strong>Assets & images</strong>: Static assets are served from <code>/public</code> and cached by Varnish and Next.js. Listing images are streamed through <code>/api/images/:id</code>; the handler sets cache headers while respecting auth where required.</li>
</ul>
</section>
</main>
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true, theme: 'dark' });
</script>
</body>
</html>