177 lines
5.9 KiB
HTML
177 lines
5.9 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>
|