diff --git a/.gitignore b/.gitignore
index 8af99a7..457ba4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/PROGRESS.md b/PROGRESS.md
index 1180f89..fc7206d 100644
--- a/PROGRESS.md
+++ b/PROGRESS.md
@@ -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.
diff --git a/docs/architecture.html b/docs/architecture.html
new file mode 100644
index 0000000..3c7da6c
--- /dev/null
+++ b/docs/architecture.html
@@ -0,0 +1,80 @@
+
+
+
+
+ Logical Architecture
+
+
+
+
+ Logical Architecture
+ Next.js App Router, Prisma/Postgres, role-based auth, email verification, approvals.
+
+
+
+ Components
+
+ 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).
+ Mail : SMTP (smtp.sohva.org) + DKIM signing for verification emails.
+ Auth : Email/password, verified+approved requirement, JWT session cookie (session_token), roles.
+
+
+
+
+ Layers Diagram
+ Source: docs/drawio/architecture.drawio. Edit with draw.io and export locally.
+
+
+
+ Domain Model Snapshot
+
+
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
+ }
+
+
+
+
+
+ Auth Flow (High-Level)
+ See PlantUML source: docs/plantuml/auth-register-login.puml. Render locally with PlantUML.
+
+
+
+
diff --git a/docs/build.html b/docs/build.html
new file mode 100644
index 0000000..da92956
--- /dev/null
+++ b/docs/build.html
@@ -0,0 +1,76 @@
+
+
+
+
+ Build & Deploy
+
+
+
+
+ Build & Deploy Pipeline
+ Node/Next build, Docker multi-stage, registry push, kubectl rollout.
+
+
+
+ Build Inputs
+
+ Source: Next.js app with TypeScript and Prisma.
+ Env: .env (local), K8s Secret lomavuokraus-web-secrets in cluster.
+ Prisma schema: prisma/schema.prisma, migrations in prisma/migrations/.
+
+
+
+
+ NPM Scripts
+
+ npm run lint → next lint
+ npm run build → next build (used inside Docker and locally)
+
+
+
+
+ Docker Image
+
+ Multi-stage Dockerfile:
+
+ deps: npm ci
+ builder: copy source, npx prisma generate, npm run build
+ runner: Node 20 bookworm-slim, copy standalone + static
+
+
+ Tags: numeric (git SHA-derived) + :latest.
+ Scan: Trivy runs post-build if available.
+
+
+
+
+ Deploy Scripts
+
+ deploy/build.sh → build image, write deploy/.last-image.
+ deploy/push.sh → push image.
+ deploy/deploy.sh → envsubst k8s/app.yaml, kubectl apply, rollout.
+ Environment wrappers:
+
+ deploy/deploy-staging.sh
+ deploy/deploy-prod.sh
+
+
+
+
+
+
+ Config & Env Vars
+
+ From ConfigMap (public): NEXT_PUBLIC_SITE_URL, NEXT_PUBLIC_API_BASE, APP_ENV.
+ From Secret: DB URL, AUTH_SECRET, SMTP, DKIM, etc.
+ App env resolution: process.env.* in Next server code.
+
+
+
+
+ Pipeline Diagram
+ For visuals, edit/export docs/drawio/architecture.drawio or create a dedicated pipeline page in draw.io.
+
+
+
+
diff --git a/docs/drawio/architecture.drawio b/docs/drawio/architecture.drawio
new file mode 100644
index 0000000..6619d7a
--- /dev/null
+++ b/docs/drawio/architecture.drawio
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/drawio/infra.drawio b/docs/drawio/infra.drawio
new file mode 100644
index 0000000..c127fc2
--- /dev/null
+++ b/docs/drawio/infra.drawio
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..7dd8381
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,34 @@
+
+
+
+
+ Lomavuokraus Docs
+
+
+
+
+
+
+
+
Notes
+
+ Docs live in docs/ (tracked, not shipped).
+ Sequence diagrams: PlantUML sources in docs/plantuml.
+ Architecture/infra diagrams: draw.io sources in docs/drawio.
+ Generate locally: PlantUML CLI/JAR, draw.io desktop/CLI exports.
+
+
+
+
+
diff --git a/docs/infra.html b/docs/infra.html
new file mode 100644
index 0000000..03958f7
--- /dev/null
+++ b/docs/infra.html
@@ -0,0 +1,74 @@
+
+
+
+
+ Infrastructure
+
+
+
+
+ Infrastructure Overview
+
+ Hetzner k3s cluster, Traefik ingress, cert-manager TLS, private registry, staging/prod namespaces.
+
+
+
+
+ Cluster & Namespaces
+
+ Single-node k3s (Hetzner hel1 cx22) at 157.180.66.64.
+ Namespaces: lomavuokraus-prod, lomavuokraus-staging.
+ Ingress controller: Traefik (k3s default).
+ cert-manager v1.15.3 with ClusterIssuers:
+
+ letsencrypt-prod (ACME prod)
+ letsencrypt-staging (ACME staging for test certs)
+
+
+ DNS: lomavuokraus.fi, staging.lomavuokraus.fi, api.lomavuokraus.fi -> cluster IP.
+
+
+
+
+ Registry
+
+ Private registry: registry.halla-aho.net/thalla/lomavuokraus-web.
+ Credentials stored outside repo (creds/), image pull secret registry-halla in staging/prod namespaces.
+ Images tagged with git SHA-derived numeric tag and :latest.
+
+
+
+
+ App Manifests
+
+ k8s/app.yaml templated via envsubst in deploy scripts.
+ Objects:
+
+ ConfigMap: lomavuokraus-web-config (public env).
+ Deployment: 2 replicas, container port 3000, liveness/readiness on /api/health.
+ Service: ClusterIP on port 80.
+ Ingress: Traefik class, TLS via cert-manager, HTTPS redirect middleware.
+ Traefik Middleware: https-redirect to force HTTPS.
+
+
+ Secrets: lomavuokraus-web-secrets in cluster (not in repo).
+
+
+
+
+ Runtime Environment
+
+ Next.js 14.2.33 (App Router) running via Node.js 20 in Docker.
+ PostgreSQL DB at 46.62.203.202 (DATABASE_URL in .env, not committed).
+ SMTP: smtp.sohva.org, DKIM key under creds/dkim/....
+ Session auth: signed JWT cookie session_token; roles: USER, ADMIN, USER_MODERATOR, LISTING_MODERATOR.
+
+
+
+
+ Traffic Flow Diagram
+ Source: docs/drawio/infra.drawio (edit with draw.io, export PNG locally).
+
+
+
+
diff --git a/docs/plantuml/auth-register-login.puml b/docs/plantuml/auth-register-login.puml
new file mode 100644
index 0000000..8e1b32b
--- /dev/null
+++ b/docs/plantuml/auth-register-login.puml
@@ -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
diff --git a/docs/plantuml/auth-register-login.svg b/docs/plantuml/auth-register-login.svg
new file mode 100644
index 0000000..8b4e09b
--- /dev/null
+++ b/docs/plantuml/auth-register-login.svg
@@ -0,0 +1 @@
+User registration, verification, login, approval User registration, verification, login, approval User Next API Postgres SMTP Admin User User Next API Next API Postgres Postgres SMTP SMTP Admin Admin POST /api/auth/register (email, password, name) create User (status=PENDING) create VerificationToken send verification email POST /api/auth/verify (token) set emailVerifiedAt POST /api/admin/users/approve set status=ACTIVE, approvedAt POST /api/auth/login validate password + status session_token cookie (JWT)
\ No newline at end of file
diff --git a/docs/plantuml/listing-create-approve.puml b/docs/plantuml/listing-create-approve.puml
new file mode 100644
index 0000000..5396aa8
--- /dev/null
+++ b/docs/plantuml/listing-create-approve.puml
@@ -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
diff --git a/docs/plantuml/listing-create-approve.svg b/docs/plantuml/listing-create-approve.svg
new file mode 100644
index 0000000..e9d3880
--- /dev/null
+++ b/docs/plantuml/listing-create-approve.svg
@@ -0,0 +1 @@
+Listing creation and admin approval Listing creation and admin approval Owner Next API Postgres Admin User Owner Owner Next API Next API Postgres Postgres Admin Admin User User POST /api/listings (slug, details, images) create Listing status=PENDING (or PUBLISHED if auto) GET /api/admin/pending fetch pending listings POST /api/admin/listings/approve (action=approve|reject|remove) update status, timestamps GET /listings/[slug] fetch translation status=PUBLISHED and not removed render listing
\ No newline at end of file
diff --git a/docs/plantuml/listing-removal.puml b/docs/plantuml/listing-removal.puml
new file mode 100644
index 0000000..14dddc6
--- /dev/null
+++ b/docs/plantuml/listing-removal.puml
@@ -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
diff --git a/docs/plantuml/listing-removal.svg b/docs/plantuml/listing-removal.svg
new file mode 100644
index 0000000..e3a0259
--- /dev/null
+++ b/docs/plantuml/listing-removal.svg
@@ -0,0 +1 @@
+Listing removal by owner or moderator Listing removal by owner or moderator Owner Next API Postgres Moderator Public Owner Owner Next API Next API Postgres Postgres Moderator Moderator Public Public POST /api/listings/remove (listingId) verify owner or moderator fetch listing set status=REMOVED published=false removedAt/by POST /api/admin/listings/approve (action=remove) same status change if via admin path GET /api/listings/mine fetch listings for owner (includes REMOVED) GET /listings/[slug] filter out removed listings not found if removed
\ No newline at end of file
diff --git a/docs/plantuml/profile-update.puml b/docs/plantuml/profile-update.puml
new file mode 100644
index 0000000..5422bc4
--- /dev/null
+++ b/docs/plantuml/profile-update.puml
@@ -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
diff --git a/docs/plantuml/profile-update.svg b/docs/plantuml/profile-update.svg
new file mode 100644
index 0000000..7bb6c2d
--- /dev/null
+++ b/docs/plantuml/profile-update.svg
@@ -0,0 +1 @@
+Profile update (name/password) Profile update (name/password) User Next API Postgres User User Next API Next API Postgres Postgres PATCH /api/me (name?, password?) update name/passwordHash (email immutable) updated profile payload
\ No newline at end of file
diff --git a/docs/sequences.html b/docs/sequences.html
new file mode 100644
index 0000000..fb51e09
--- /dev/null
+++ b/docs/sequences.html
@@ -0,0 +1,47 @@
+
+
+
+
+ Feature Sequences
+
+
+
+
+
+
+ User Registration & Verification
+ PlantUML: docs/plantuml/auth-register-login.puml
+
+
+
+
+ Listing Creation & Approval
+ PlantUML: docs/plantuml/listing-create-approve.puml
+
+
+
+
+ Listing Removal by Owner/Moderator
+ PlantUML: docs/plantuml/listing-removal.puml
+
+
+
+
+ Profile Update (Name/Password)
+ PlantUML: docs/plantuml/profile-update.puml
+
+
+
+
+ Rendering instructions
+
+ PlantUML: plantuml docs/plantuml/*.puml (local PlantUML/Java or Docker).
+ Draw.io: open docs/drawio/*.drawio in the desktop app to edit/export PNG/SVG locally.
+
+
+
+
+
diff --git a/docs/style.css b/docs/style.css
new file mode 100644
index 0000000..af53da2
--- /dev/null
+++ b/docs/style.css
@@ -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;
+}