docs: move docs directory and render plantuml svgs
This commit is contained in:
parent
590daacb76
commit
66650d63ac
18 changed files with 583 additions and 4 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -20,8 +20,7 @@ deploy/.last-image
|
|||
creds/
|
||||
k3s.yaml
|
||||
|
||||
# Local-only documentation
|
||||
docs-local/
|
||||
# Local-only documentation (now tracked in docs/)
|
||||
|
||||
/lib/generated/prisma
|
||||
|
||||
|
|
@ -29,6 +28,7 @@ docs-local/
|
|||
bin/
|
||||
lib/python*/
|
||||
lib64/
|
||||
lib64
|
||||
pyvenv.cfg
|
||||
tsconfig.tsbuildinfo
|
||||
.lock
|
||||
|
|
|
|||
|
|
@ -39,13 +39,14 @@
|
|||
- Amenities expanded: electric vehicle charging (free/paid) and air conditioning; cover image selectable per listing and used in cards.
|
||||
- Home page shows a rolling feed of latest listings; navbar + CTA link to browse.
|
||||
- Listing creation form captures address details, coordinates, amenities (incl. EV/AC), and cover image choice.
|
||||
- Documentation moved to `docs/`; PlantUML diagrams rendered to SVG and embedded in docs pages (draw.io sources kept for architecture/infra).
|
||||
- HTTPS redirect middleware applied to staging/prod ingress.
|
||||
- FI/EN localization with navbar language toggle; UI strings translated; Approvals link shows pending count badge.
|
||||
- Soft rejection/removal states for users/listings with timestamps; owner listing removal; login redirects home; listing visibility hides removed/not-published.
|
||||
- Profile page now allows editing name and password (email immutable).
|
||||
- Docs: Added local docs in `docs-local/` (tracked, not shipped) with HTML + PlantUML sequences + draw.io diagrams. Ignored from deploy via runtime paths; kept in git.
|
||||
- Docs: Added docs in `docs/` (tracked, not shipped) with HTML + PlantUML sequences + draw.io diagrams. Ignored from deploy via runtime paths; kept in git.
|
||||
|
||||
To resume:
|
||||
1) If desired, render diagrams locally: PlantUML in `docs-local/plantuml`, draw.io in `docs-local/drawio`.
|
||||
1) If desired, render diagrams locally: PlantUML in `docs/plantuml`, draw.io in `docs/drawio`.
|
||||
2) Keep registry health in mind; current pushes work (`1763994382` deployed).
|
||||
3) Future app work: translations polish, more listing fields, admin tooling, or registry hardening.
|
||||
|
|
|
|||
80
docs/architecture.html
Normal file
80
docs/architecture.html
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<!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>Components</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).</li>
|
||||
<li><strong>Mail</strong>: SMTP (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>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Layers Diagram</h2>
|
||||
<p>Source: <code>docs/drawio/architecture.drawio</code>. Edit with draw.io and export locally.</p>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Domain Model Snapshot</h2>
|
||||
<div class="diagram">
|
||||
<pre><code class="language-mermaid">erDiagram
|
||||
USER ||--o{ LISTING : owns
|
||||
USER ||--o{ LISTING : approves
|
||||
LISTING ||--|{ LISTINGTRANSLATION : has
|
||||
LISTING ||--o{ LISTINGIMAGE : has
|
||||
|
||||
USER {
|
||||
string id
|
||||
string email
|
||||
string passwordHash
|
||||
Role role
|
||||
UserStatus status
|
||||
datetime emailVerifiedAt
|
||||
datetime approvedAt
|
||||
datetime rejectedAt
|
||||
datetime removedAt
|
||||
}
|
||||
LISTING {
|
||||
string id
|
||||
ListingStatus status
|
||||
datetime approvedAt
|
||||
datetime rejectedAt
|
||||
datetime removedAt
|
||||
string country
|
||||
string region
|
||||
string city
|
||||
}
|
||||
LISTINGTRANSLATION {
|
||||
string id
|
||||
string slug
|
||||
string title
|
||||
string locale
|
||||
}
|
||||
LISTINGIMAGE {
|
||||
string id
|
||||
string url
|
||||
}
|
||||
</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Auth Flow (High-Level)</h2>
|
||||
<p>See PlantUML source: <code>docs/plantuml/auth-register-login.puml</code>. Render locally with PlantUML.</p>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
76
docs/build.html
Normal file
76
docs/build.html
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Build & Deploy</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Build & Deploy Pipeline</h1>
|
||||
<div class="meta">Node/Next build, Docker multi-stage, registry push, kubectl rollout.</div>
|
||||
</header>
|
||||
<main class="grid">
|
||||
<section class="card">
|
||||
<h2>Build Inputs</h2>
|
||||
<ul>
|
||||
<li>Source: Next.js app with TypeScript and Prisma.</li>
|
||||
<li>Env: <code>.env</code> (local), K8s Secret <code>lomavuokraus-web-secrets</code> in cluster.</li>
|
||||
<li>Prisma schema: <code>prisma/schema.prisma</code>, migrations in <code>prisma/migrations/</code>.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>NPM Scripts</h2>
|
||||
<ul>
|
||||
<li><code>npm run lint</code> → <code>next lint</code></li>
|
||||
<li><code>npm run build</code> → <code>next build</code> (used inside Docker and locally)</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Docker Image</h2>
|
||||
<ul>
|
||||
<li>Multi-stage Dockerfile:
|
||||
<ul>
|
||||
<li>deps: npm ci</li>
|
||||
<li>builder: copy source, <code>npx prisma generate</code>, <code>npm run build</code></li>
|
||||
<li>runner: Node 20 bookworm-slim, copy standalone + static</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Tags: numeric (git SHA-derived) + <code>:latest</code>.</li>
|
||||
<li>Scan: Trivy runs post-build if available.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Deploy Scripts</h2>
|
||||
<ul>
|
||||
<li><code>deploy/build.sh</code> → build image, write <code>deploy/.last-image</code>.</li>
|
||||
<li><code>deploy/push.sh</code> → push image.</li>
|
||||
<li><code>deploy/deploy.sh</code> → envsubst <code>k8s/app.yaml</code>, kubectl apply, rollout.</li>
|
||||
<li>Environment wrappers:
|
||||
<ul>
|
||||
<li><code>deploy/deploy-staging.sh</code></li>
|
||||
<li><code>deploy/deploy-prod.sh</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Config & Env Vars</h2>
|
||||
<ul>
|
||||
<li>From ConfigMap (public): <code>NEXT_PUBLIC_SITE_URL</code>, <code>NEXT_PUBLIC_API_BASE</code>, <code>APP_ENV</code>.</li>
|
||||
<li>From Secret: DB URL, AUTH_SECRET, SMTP, DKIM, etc.</li>
|
||||
<li>App env resolution: <code>process.env.*</code> in Next server code.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Pipeline Diagram</h2>
|
||||
<p>For visuals, edit/export <code>docs/drawio/architecture.drawio</code> or create a dedicated pipeline page in draw.io.</p>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
52
docs/drawio/architecture.drawio
Normal file
52
docs/drawio/architecture.drawio
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<mxfile host="app.diagrams.net">
|
||||
<diagram id="architecture" name="Architecture">
|
||||
<mxGraphModel dx="923" dy="570" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1200" pageHeight="900" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="browser" value="Browser" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#26A69A;fontColor=#ffffff" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="80" width="120" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="next" value="Next.js App" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#3949AB;fontColor=#ffffff" vertex="1" parent="1">
|
||||
<mxGeometry x="260" y="80" width="140" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="apiRoutes" value="API Routes (Auth, Listings, Admin)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#5E35B1;fontColor=#ffffff" vertex="1" parent="1">
|
||||
<mxGeometry x="260" y="170" width="200" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="prisma" value="Prisma Client" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#00897B;fontColor=#ffffff" vertex="1" parent="1">
|
||||
<mxGeometry x="520" y="170" width="140" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="db" value="PostgreSQL" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#8D6E63;fontColor=#ffffff" vertex="1" parent="1">
|
||||
<mxGeometry x="720" y="170" width="140" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="smtp" value="SMTP / DKIM" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#F4511E;fontColor=#ffffff" vertex="1" parent="1">
|
||||
<mxGeometry x="720" y="270" width="140" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="cookie" value="JWT session_token" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#0097A7;fontColor=#ffffff" vertex="1" parent="1">
|
||||
<mxGeometry x="480" y="80" width="140" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="flow1" edge="1" source="browser" target="next" style="endArrow=block;strokeColor=#26A69A" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="flow2" edge="1" source="next" target="apiRoutes" style="endArrow=block;strokeColor=#3949AB" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="flow3" edge="1" source="apiRoutes" target="prisma" style="endArrow=block;strokeColor=#5E35B1" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="flow4" edge="1" source="prisma" target="db" style="endArrow=block;strokeColor=#00897B" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="flow5" edge="1" source="apiRoutes" target="smtp" style="endArrow=block;strokeColor=#F4511E" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="flow6" edge="1" source="next" target="cookie" style="endArrow=block;strokeColor=#0097A7" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="flow7" edge="1" source="cookie" target="browser" style="endArrow=block;strokeColor=#0097A7" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
64
docs/drawio/infra.drawio
Normal file
64
docs/drawio/infra.drawio
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<mxfile host="app.diagrams.net">
|
||||
<diagram id="infra" name="Infrastructure">
|
||||
<mxGraphModel dx="923" dy="570" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1200" pageHeight="900" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="cluster" value="k3s Cluster" style="swimlane;childLayout=stackLayout;horizontal=1;startSize=20;" vertex="1" parent="1">
|
||||
<mxGeometry x="140" y="140" width="700" height="420" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="traefik" value="Traefik Ingress" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1E88E5;fontColor=#ffffff" vertex="1" parent="cluster">
|
||||
<mxGeometry x="40" y="60" width="140" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="svc" value="Service: lomavuokraus-web" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#3949AB;fontColor=#ffffff" vertex="1" parent="cluster">
|
||||
<mxGeometry x="240" y="60" width="180" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="pod" value="Deployment (Next.js pods)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#5E35B1;fontColor=#ffffff" vertex="1" parent="cluster">
|
||||
<mxGeometry x="480" y="60" width="180" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="cm" value="ConfigMap + Secret" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#00897B;fontColor=#ffffff" vertex="1" parent="cluster">
|
||||
<mxGeometry x="480" y="170" width="180" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="cert" value="cert-manager
ClusterIssuers" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#00838F;fontColor=#ffffff" vertex="1" parent="cluster">
|
||||
<mxGeometry x="240" y="170" width="180" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="browser" value="Browser" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#26A69A;fontColor=#ffffff" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="70" width="100" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="registry" value="Registry
registry.halla-aho.net" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#6D4C41;fontColor=#ffffff" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="340" width="150" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="db" value="PostgreSQL
46.62.203.202" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#8D6E63;fontColor=#ffffff" vertex="1" parent="1">
|
||||
<mxGeometry x="860" y="220" width="160" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="smtp" value="SMTP
smtp.sohva.org" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#F4511E;fontColor=#ffffff" vertex="1" parent="1">
|
||||
<mxGeometry x="860" y="320" width="160" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="registry-edge" edge="1" source="registry" target="pod" style="endArrow=block;strokeColor=#6D4C41" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="browser-edge" edge="1" source="browser" target="traefik" style="endArrow=block;strokeColor=#26A69A" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="traefik-svc-edge" edge="1" source="traefik" target="svc" style="endArrow=block;strokeColor=#1E88E5" parent="cluster">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="svc-pod-edge" edge="1" source="svc" target="pod" style="endArrow=block;strokeColor=#3949AB" parent="cluster">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="pod-db-edge" edge="1" source="pod" target="db" style="endArrow=block;strokeColor=#8D6E63" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="pod-smtp-edge" edge="1" source="pod" target="smtp" style="endArrow=block;strokeColor=#F4511E" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="cert-traefik-edge" edge="1" source="cert" target="traefik" style="endArrow=block;strokeColor=#00838F" parent="cluster">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="cm-pod-edge" edge="1" source="cm" target="pod" style="endArrow=block;strokeColor=#00897B" parent="cluster">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
34
docs/index.html
Normal file
34
docs/index.html
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Lomavuokraus Docs</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Lomavuokraus Documentation</h1>
|
||||
<div class="meta">Docs tracked in git, not deployed with the app.</div>
|
||||
</header>
|
||||
<main class="grid">
|
||||
<div class="card">
|
||||
<h2>Contents</h2>
|
||||
<ul>
|
||||
<li><a href="./infra.html">Infrastructure</a></li>
|
||||
<li><a href="./build.html">Build & Deploy</a></li>
|
||||
<li><a href="./architecture.html">Logical Architecture</a></li>
|
||||
<li><a href="./sequences.html">Feature Sequences</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Notes</h3>
|
||||
<ul>
|
||||
<li>Docs live in <code>docs/</code> (tracked, not shipped).</li>
|
||||
<li>Sequence diagrams: PlantUML sources in <code>docs/plantuml</code>.</li>
|
||||
<li>Architecture/infra diagrams: draw.io sources in <code>docs/drawio</code>.</li>
|
||||
<li>Generate locally: PlantUML CLI/JAR, draw.io desktop/CLI exports.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
74
docs/infra.html
Normal file
74
docs/infra.html
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Infrastructure</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Infrastructure Overview</h1>
|
||||
<div class="meta">
|
||||
Hetzner k3s cluster, Traefik ingress, cert-manager TLS, private registry, staging/prod namespaces.
|
||||
</div>
|
||||
</header>
|
||||
<main class="grid">
|
||||
<section class="card">
|
||||
<h2>Cluster & Namespaces</h2>
|
||||
<ul>
|
||||
<li>Single-node k3s (Hetzner hel1 cx22) at <code>157.180.66.64</code>.</li>
|
||||
<li>Namespaces: <code>lomavuokraus-prod</code>, <code>lomavuokraus-staging</code>.</li>
|
||||
<li>Ingress controller: Traefik (k3s default).</li>
|
||||
<li>cert-manager v1.15.3 with ClusterIssuers:
|
||||
<ul>
|
||||
<li><code>letsencrypt-prod</code> (ACME prod)</li>
|
||||
<li><code>letsencrypt-staging</code> (ACME staging for test certs)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>DNS: <code>lomavuokraus.fi</code>, <code>staging.lomavuokraus.fi</code>, <code>api.lomavuokraus.fi</code> -> cluster IP.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Registry</h2>
|
||||
<ul>
|
||||
<li>Private registry: <code>registry.halla-aho.net/thalla/lomavuokraus-web</code>.</li>
|
||||
<li>Credentials stored outside repo (<code>creds/</code>), image pull secret <code>registry-halla</code> in staging/prod namespaces.</li>
|
||||
<li>Images tagged with git SHA-derived numeric tag and <code>:latest</code>.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>App Manifests</h2>
|
||||
<ul>
|
||||
<li><code>k8s/app.yaml</code> templated via envsubst in deploy scripts.</li>
|
||||
<li>Objects:
|
||||
<ul>
|
||||
<li>ConfigMap: <code>lomavuokraus-web-config</code> (public env).</li>
|
||||
<li>Deployment: 2 replicas, container port 3000, liveness/readiness on <code>/api/health</code>.</li>
|
||||
<li>Service: ClusterIP on port 80.</li>
|
||||
<li>Ingress: Traefik class, TLS via cert-manager, HTTPS redirect middleware.</li>
|
||||
<li>Traefik Middleware: <code>https-redirect</code> to force HTTPS.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Secrets: <code>lomavuokraus-web-secrets</code> in cluster (not in repo).</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Runtime Environment</h2>
|
||||
<ul>
|
||||
<li>Next.js 14.2.33 (App Router) running via Node.js 20 in Docker.</li>
|
||||
<li>PostgreSQL DB at <code>46.62.203.202</code> (DATABASE_URL in .env, not committed).</li>
|
||||
<li>SMTP: smtp.sohva.org, DKIM key under <code>creds/dkim/...</code>.</li>
|
||||
<li>Session auth: signed JWT cookie <code>session_token</code>; roles: USER, ADMIN, USER_MODERATOR, LISTING_MODERATOR.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Traffic Flow Diagram</h2>
|
||||
<p>Source: <code>docs/drawio/infra.drawio</code> (edit with draw.io, export PNG locally).</p>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
19
docs/plantuml/auth-register-login.puml
Normal file
19
docs/plantuml/auth-register-login.puml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
@startuml
|
||||
title User registration, verification, login, approval
|
||||
actor User
|
||||
participant "Next API" as API
|
||||
database Postgres as DB
|
||||
participant SMTP as Mail
|
||||
actor Admin
|
||||
|
||||
User -> API: POST /api/auth/register\n(email, password, name)
|
||||
API -> DB: create User (status=PENDING)\ncreate VerificationToken
|
||||
API -> Mail: send verification email
|
||||
User -> API: POST /api/auth/verify (token)
|
||||
API -> DB: set emailVerifiedAt
|
||||
Admin -> API: POST /api/admin/users/approve
|
||||
API -> DB: set status=ACTIVE, approvedAt
|
||||
User -> API: POST /api/auth/login
|
||||
API -> DB: validate password + status
|
||||
API --> User: session_token cookie (JWT)
|
||||
@enduml
|
||||
1
docs/plantuml/auth-register-login.svg
Normal file
1
docs/plantuml/auth-register-login.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
18
docs/plantuml/listing-create-approve.puml
Normal file
18
docs/plantuml/listing-create-approve.puml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
@startuml
|
||||
title Listing creation and admin approval
|
||||
actor Owner
|
||||
participant "Next API" as API
|
||||
database Postgres as DB
|
||||
actor Admin
|
||||
actor User
|
||||
|
||||
Owner -> API: POST /api/listings\n(slug, details, images)
|
||||
API -> DB: create Listing\nstatus=PENDING (or PUBLISHED if auto)
|
||||
Admin -> API: GET /api/admin/pending
|
||||
API -> DB: fetch pending listings
|
||||
Admin -> API: POST /api/admin/listings/approve\n(action=approve|reject|remove)
|
||||
API -> DB: update status, timestamps
|
||||
User -> API: GET /listings/[slug]
|
||||
API -> DB: fetch translation\nstatus=PUBLISHED and not removed
|
||||
API --> User: render listing
|
||||
@enduml
|
||||
1
docs/plantuml/listing-create-approve.svg
Normal file
1
docs/plantuml/listing-create-approve.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
18
docs/plantuml/listing-removal.puml
Normal file
18
docs/plantuml/listing-removal.puml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
@startuml
|
||||
title Listing removal by owner or moderator
|
||||
actor Owner
|
||||
participant "Next API" as API
|
||||
database Postgres as DB
|
||||
actor Moderator
|
||||
|
||||
Owner -> API: POST /api/listings/remove (listingId)
|
||||
API -> DB: verify owner or moderator\nfetch listing
|
||||
API -> DB: set status=REMOVED\npublished=false\nremovedAt/by
|
||||
Moderator -> API: POST /api/admin/listings/approve\n(action=remove)
|
||||
API -> DB: same status change if via admin path
|
||||
Owner -> API: GET /api/listings/mine
|
||||
API -> DB: fetch listings for owner (includes REMOVED)
|
||||
Public -> API: GET /listings/[slug]
|
||||
API -> DB: filter out removed listings
|
||||
API --> Public: not found if removed
|
||||
@enduml
|
||||
1
docs/plantuml/listing-removal.svg
Normal file
1
docs/plantuml/listing-removal.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
10
docs/plantuml/profile-update.puml
Normal file
10
docs/plantuml/profile-update.puml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
@startuml
|
||||
title Profile update (name/password)
|
||||
actor User
|
||||
participant "Next API" as API
|
||||
database Postgres as DB
|
||||
|
||||
User -> API: PATCH /api/me\n(name?, password?)
|
||||
API -> DB: update name/passwordHash\n(email immutable)
|
||||
API --> User: updated profile payload
|
||||
@enduml
|
||||
1
docs/plantuml/profile-update.svg
Normal file
1
docs/plantuml/profile-update.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.3 KiB |
47
docs/sequences.html
Normal file
47
docs/sequences.html
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Feature Sequences</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Sequence Diagrams</h1>
|
||||
<div class="meta">PlantUML sources for key flows; render locally.</div>
|
||||
</header>
|
||||
<main class="grid">
|
||||
<section class="card">
|
||||
<h2>User Registration & Verification</h2>
|
||||
<p>PlantUML: <code>docs/plantuml/auth-register-login.puml</code></p>
|
||||
<img src="./plantuml/auth-register-login.svg" alt="User registration and verification sequence" class="diagram-img" />
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Listing Creation & Approval</h2>
|
||||
<p>PlantUML: <code>docs/plantuml/listing-create-approve.puml</code></p>
|
||||
<img src="./plantuml/listing-create-approve.svg" alt="Listing creation and approval sequence" class="diagram-img" />
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Listing Removal by Owner/Moderator</h2>
|
||||
<p>PlantUML: <code>docs/plantuml/listing-removal.puml</code></p>
|
||||
<img src="./plantuml/listing-removal.svg" alt="Listing removal sequence" class="diagram-img" />
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Profile Update (Name/Password)</h2>
|
||||
<p>PlantUML: <code>docs/plantuml/profile-update.puml</code></p>
|
||||
<img src="./plantuml/profile-update.svg" alt="Profile update sequence" class="diagram-img" />
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Rendering instructions</h2>
|
||||
<ul>
|
||||
<li>PlantUML: <code>plantuml docs/plantuml/*.puml</code> (local PlantUML/Java or Docker).</li>
|
||||
<li>Draw.io: open <code>docs/drawio/*.drawio</code> in the desktop app to edit/export PNG/SVG locally.</li>
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
82
docs/style.css
Normal file
82
docs/style.css
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
body {
|
||||
font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #0f172a;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
a {
|
||||
color: #38bdf8;
|
||||
}
|
||||
header {
|
||||
padding: 24px 32px;
|
||||
background: linear-gradient(135deg, #1e293b, #0f172a 60%);
|
||||
border-bottom: 1px solid #1f2937;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
color: #f8fafc;
|
||||
}
|
||||
main {
|
||||
padding: 24px 32px 48px;
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
max-width: 1200px;
|
||||
}
|
||||
.card {
|
||||
background: #111827;
|
||||
border: 1px solid #1f2937;
|
||||
border-radius: 14px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.meta {
|
||||
color: #cbd5e1;
|
||||
font-size: 14px;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.two-col {
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
}
|
||||
pre {
|
||||
background: #0b1220;
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
border: 1px solid #1f2937;
|
||||
}
|
||||
code {
|
||||
font-family: "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
.diagram {
|
||||
background: #0b1220;
|
||||
border: 1px solid #1f2937;
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
background: #38bdf8;
|
||||
color: #0f172a;
|
||||
padding: 4px 8px;
|
||||
border-radius: 999px;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
}
|
||||
ul {
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.diagram-img {
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
margin-top: 10px;
|
||||
border: 1px solid #1f2937;
|
||||
border-radius: 10px;
|
||||
background: #0b1220;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue