3 Komitmen 515f723fb1 ... 2890a87a46

Pembuat SHA1 Pesan Tanggal
  yusuf 2890a87a46 feat: add db:push script and api.hanoman.co.id to trusted origins 1 bulan lalu
  YusufSyam 13f132b9f9 feat: update cors * csrf 1 bulan lalu
  YusufSyam 19b89f48af chore: added deployment instruction 1 bulan lalu

+ 4 - 0
.env.example

@@ -4,6 +4,10 @@ DATABASE_URL=postgresql://postgres:password123@127.0.0.1:5432/postgres
 PAYLOAD_SECRET=your_payload_secret
 NODE_ENV=development
 
+# Public URL of this Payload app (no trailing slash). Used for admin links; Payload also adds it to CSRF allowlist.
+# Example production: https://api.hanoman.co.id — optional for pure localhost dev.
+# PAYLOAD_SERVER_URL=
+
 # Contact endpoint configuration
 CONTACT_TO_EMAIL=team@example.com
 FRONTEND_ORIGIN=http://localhost:3000

+ 380 - 0
docs/DEPLOY-FRONTEND-PM2-NGINX.md

@@ -0,0 +1,380 @@
+# Deploy frontend (PM2 + Nginx) dengan backend & database di Docker
+
+Panduan ini menjelaskan pola deployment di **VPS**: **PostgreSQL + Payload (Next.js backend)** tetap di **Docker**, sementara **frontend** (aplikasi Next.js terpisah) di-build, dijalankan dengan **PM2**, dan dihadapkan ke internet lewat **Nginx** (HTTPS).
+
+> **Catatan:** Repositori frontend diasumsikan **terpisah** dari repositori backend (`hanoman-website-be`). Sesuaikan path dan nama folder dengan proyek Anda.
+
+---
+
+## 1. Gambaran arsitektur
+
+```
+Internet
+   │
+   ▼
+┌──────────────────┐
+│  Nginx :80 / :443│  (SSL, reverse proxy)
+└────────┬─────────┘
+         │
+    ┌────┴────┐
+    │         │
+    ▼         ▼
+┌────────┐  ┌─────────────────────────────┐
+│ PM2    │  │ Docker Compose               │
+│ Next   │  │  • postgres:5432           │
+│ FE     │  │  • payload → host :3000      │
+│ :3001  │  │  • volume ./media → /app/media│
+└────────┘  └─────────────────────────────┘
+```
+
+- **Frontend (PM2):** misalnya `http://127.0.0.1:3001` (hanya localhost; Nginx yang memproksi ke domain publik).
+- **Backend (Docker):** `payload` memetakan **`3000:3000`** — di host, API Payload tersedia di `http://127.0.0.1:3000` (REST di `/api/...`).
+
+---
+
+## 2. Prasyarat di VPS
+
+- OS umumnya **Ubuntu 22.04/24.04 LTS** (instruksi berikut memakai `apt`).
+- **Docker** + **Docker Compose** plugin sudah terpasang dan service backend + DB sudah jalan dengan `docker compose` (lihat README repositori backend).
+- **Domain** sudah mengarah (A record) ke IP VPS — misalnya:
+  - `www.example.com` → frontend
+  - `api.example.com` → backend Payload (disarankan subdomain terpisah agar jelas dan tidak bentrok route `/api` di Next frontend).
+
+---
+
+## 3. Bagian A — Pastikan backend Docker berjalan
+
+Di mesin VPS, di direktori repositori **backend**:
+
+```bash
+cd /path/to/hanoman-website-be
+docker compose --env-file .env -f docker-compose.yml up -d
+```
+
+Pastikan:
+
+- `.env` berisi `DATABASE_URL`, `PAYLOAD_SECRET`, kredensial Postgres, dll.
+- **`DATABASE_URL` di dalam Docker** harus memakai hostname **`postgres`** (nama service di Compose), bukan `127.0.0.1`, agar container `payload` bisa konek ke DB.
+- Folder **`media/`** di host ter-mount ke container (lihat `docker-compose.yml`) agar upload media tidak hilang.
+
+**Deploy pertama / database baru:** dengan `NODE_ENV=production`, Payload **tidak** menjalankan push schema otomatis. Setelah Postgres sehat, jalankan sekali (dari folder backend di VPS):
+
+```bash
+docker compose --profile db-push run --rm db-push
+```
+
+Atau dari mesin yang bisa reach DB dengan env yang sama: `pnpm run db:push`. Baru setelah tabel ada, buka admin dan buat user pertama.
+
+Uji cepat dari VPS:
+
+```bash
+curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3000/api/
+```
+
+Jika backend sehat, Anda akan mendapat respons HTTP (bukan koneksi ditolak).
+
+---
+
+## 4. Bagian B — Node.js, pnpm/yarn, dan PM2
+
+### 4.1 Versi Node
+
+Samakan **major** Node dengan yang dipakai proyek (cek `engines` di `package.json` frontend). Contoh memakai Node 22 via **nvm** (disarankan):
+
+```bash
+curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
+source ~/.bashrc
+nvm install 22
+nvm use 22
+node -v
+```
+
+### 4.2 PM2 (global)
+
+```bash
+npm install -g pm2
+# opsional: startup otomatis setelah reboot
+pm2 startup
+# jalankan perintah yang di-print oleh pm2 startup (sudo env ...)
+```
+
+---
+
+## 5. Bagian C — Build dan jalankan frontend
+
+### 5.1 Kloning dan dependensi
+
+```bash
+sudo mkdir -p /var/www
+sudo chown $USER:$USER /var/www
+cd /var/www
+git clone <URL-REPO-FRONTEND> hanoman-frontend
+cd hanoman-frontend
+```
+
+Pasang paket (contoh **pnpm**; sesuaikan jika pakai yarn/npm):
+
+```bash
+corepack enable
+corepack prepare pnpm@latest --activate
+pnpm install --frozen-lockfile
+```
+
+### 5.2 Environment production (frontend)
+
+Buat file **`.env.production`** (atau `.env` sesuai konvensi Next) di root frontend. Minimal:
+
+```env
+# URL publik backend Payload (tanpa slash di akhir)
+# Gunakan https://api.example.com jika Nginx sudah memproksi ke :3000
+NEXT_PUBLIC_PAYLOAD_API_URL=https://api.example.com
+```
+
+Sesuaikan dengan cara frontend Anda menyusun URL:
+
+- Jika kode memakai `NEXT_PUBLIC_PAYLOAD_API_URL + '/api/posts'` → isi **tanpa** `/api` di akhir.
+- Jika kode memakai `NEXT_PUBLIC_PAYLOAD_API_URL + '/posts'` → isi **dengan** `https://api.example.com/api`.
+
+**Build-time:** `NEXT_PUBLIC_*` di-embed saat `next build`; setelah mengubahnya, **harus build ulang**.
+
+### 5.3 Build
+
+```bash
+pnpm run build
+```
+
+### 5.4 PM2 — file `ecosystem.config.cjs`
+
+Di root frontend (mis. `/var/www/hanoman-frontend`), buat `ecosystem.config.cjs`:
+
+```javascript
+module.exports = {
+  apps: [
+    {
+      name: 'hanoman-fe',
+      cwd: '/var/www/hanoman-frontend',
+      script: 'node_modules/next/dist/bin/next',
+      args: 'start -p 3001 -H 127.0.0.1',
+      instances: 1,
+      autorestart: true,
+      max_memory_restart: '512M',
+      env: {
+        NODE_ENV: 'production',
+      },
+    },
+  ],
+}
+```
+
+> Port **3001** dipakai agar tidak bentrok dengan backend di **3000**. Sesuaikan jika Anda memakai port lain.
+
+Jalankan:
+
+```bash
+cd /var/www/hanoman-frontend
+pm2 start ecosystem.config.cjs
+pm2 save
+```
+
+Uji lokal:
+
+```bash
+curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3001/
+```
+
+---
+
+## 6. Bagian D — Nginx (reverse proxy + HTTPS)
+
+### 6.1 Instalasi
+
+```bash
+sudo apt update
+sudo apt install -y nginx
+```
+
+### 6.2 Dua server block (disarankan)
+
+**a) Backend — `api.example.com` → `127.0.0.1:3000`**
+
+Buat `/etc/nginx/sites-available/hanoman-api`:
+
+```nginx
+server {
+    listen 80;
+    server_name api.example.com;
+
+    # Admin upload media — naikkan jika perlu (default Nginx 1m sering terlalu kecil)
+    client_max_body_size 50M;
+
+    location / {
+        proxy_pass http://127.0.0.1:3000;
+        proxy_http_version 1.1;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection "upgrade";
+        proxy_read_timeout 300s;
+        proxy_send_timeout 300s;
+    }
+}
+```
+
+**b) Frontend — `www.example.com` → `127.0.0.1:3001`**
+
+Buat `/etc/nginx/sites-available/hanoman-www`:
+
+```nginx
+server {
+    listen 80;
+    server_name www.example.com example.com;
+
+    client_max_body_size 20M;
+
+    location / {
+        proxy_pass http://127.0.0.1:3001;
+        proxy_http_version 1.1;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection "upgrade";
+        proxy_read_timeout 120s;
+    }
+}
+```
+
+Aktifkan situs:
+
+```bash
+sudo ln -sf /etc/nginx/sites-available/hanoman-api /etc/nginx/sites-enabled/
+sudo ln -sf /etc/nginx/sites-available/hanoman-www /etc/nginx/sites-enabled/
+sudo rm -f /etc/nginx/sites-enabled/default
+sudo nginx -t
+sudo systemctl reload nginx
+```
+
+### 6.3 SSL (Let’s Encrypt)
+
+```bash
+sudo apt install -y certbot python3-certbot-nginx
+sudo certbot --nginx -d api.example.com -d www.example.com -d example.com
+```
+
+Certbot akan mengubah blok `listen 443 ssl` dan jadwal renew otomatis.
+
+Setelah HTTPS aktif, pastikan **`NEXT_PUBLIC_PAYLOAD_API_URL`** memakai **`https://api.example.com`** lalu **build ulang** frontend dan restart PM2:
+
+```bash
+cd /var/www/hanoman-frontend
+pnpm run build
+pm2 restart hanoman-fe
+```
+
+---
+
+## 7. Firewall (opsional tapi disarankan)
+
+```bash
+sudo ufw allow OpenSSH
+sudo ufw allow 'Nginx Full'
+sudo ufw enable
+sudo ufw status
+```
+
+Port **3000** dan **3001** tidak perlu dibuka ke publik jika hanya diakses lewat Nginx di localhost.
+
+---
+
+## 8. Backend: URL publik, CORS, CSRF, dan form kontak
+
+Konfigurasi aktual ada di `src/payload.config.ts` (bukan `cors: '*'`).
+
+### 8.1 `PAYLOAD_SERVER_URL` (`.env` backend)
+
+Set ke URL publik API, **tanpa** slash di akhir, mis.:
+
+```env
+PAYLOAD_SERVER_URL=https://api.example.com
+```
+
+Ini dipakai Payload untuk admin, redirect, dan perilaku URL absolut. Setelah mengubahnya, **build ulang image** dan `docker compose up -d --build`.
+
+### 8.2 `trustedBrowserOrigins` (kode backend)
+
+Array **`trustedBrowserOrigins`** dipakai untuk **`cors`** dan **`csrf`**. Origin browser yang mengakses REST/GraphQL atau mengirim cookie admin **harus** terdaftar, mis.:
+
+- `https://www.example.com` (situs frontend)
+- `https://example.com` (jika dipakai)
+- `https://api.example.com` (jika admin dibuka dari subdomain yang sama)
+
+Tambahkan origin demo/production di array itu, commit, lalu deploy image baru. Jika origin tidak ada di daftar, fetch dari frontend atau login admin bisa gagal dengan error CORS/CSRF.
+
+### 8.3 `FRONTEND_ORIGIN` (`.env` backend)
+
+Endpoint **`POST /api/contact`** memakai **`FRONTEND_ORIGIN`** untuk header CORS. Isi persis origin frontend, mis. `https://www.example.com` (skema + host, tanpa path).
+
+### 8.4 Cookie admin (opsional)
+
+Jika admin di `api.example.com` dan frontend di `www.example.com`, cookie default **scoped per host**. Untuk skenario subdomain kompleks, pertimbangkan `COOKIE_DOMAIN` di `.env` (lihat `src/collections/Users.ts`) — hanya set jika Anda paham dampaknya terhadap keamanan cookie.
+
+---
+
+## 9. Alur update (deploy ulang frontend)
+
+```bash
+cd /var/www/hanoman-frontend
+git pull
+pnpm install --frozen-lockfile
+# edit .env.production jika perlu
+pnpm run build
+pm2 restart hanoman-fe
+```
+
+Backend + DB:
+
+```bash
+cd /path/to/hanoman-website-be
+git pull
+docker compose --env-file .env -f docker-compose.yml up -d --build
+```
+
+---
+
+## 10. Troubleshooting singkat
+
+| Gejala | Hal yang dicek |
+|--------|----------------|
+| 502 Bad Gateway ke FE | `pm2 status`, `curl http://127.0.0.1:3001/` |
+| 502 ke API | `docker compose ps`, `curl http://127.0.0.1:3000/api/` |
+| Frontend tidak bisa fetch API | `NEXT_PUBLIC_*` salah atau belum rebuild; URL harus **https** setelah Certbot |
+| CORS / CSRF / fetch API dari browser gagal | Tambahkan origin frontend (dan API jika perlu) ke `trustedBrowserOrigins` di backend; set `PAYLOAD_SERVER_URL`; deploy ulang image backend |
+| Admin redirect aneh atau mixed content | `PAYLOAD_SERVER_URL` harus `https://...` sama dengan domain Nginx |
+| `relation "users" does not exist` | DB baru tanpa schema: jalankan `docker compose --profile db-push run --rm db-push` (lihat §3) |
+| Media backend 404 | Volume `./media` di Docker dan isi folder `media/` di host |
+| Upload admin gagal (413) | Naikkan `client_max_body_size` di Nginx untuk `api.*` |
+
+---
+
+## 11. Ringkasan port di host
+
+| Layanan | Port (localhost) | Publik |
+|--------|-------------------|--------|
+| Payload (Docker) | `3000` | Via Nginx `api.example.com` |
+| Frontend (PM2) | `3001` | Via Nginx `www.example.com` |
+| PostgreSQL (Docker) | `5432` (hanya jika perlu; jangan expose ke internet tanpa kebutuhan) | — |
+
+## 12. Checklist singkat sebelum dianggap selesai
+
+- [ ] Backend: `.env` lengkap (`PAYLOAD_SECRET`, `DATABASE_URL` dengan host `postgres`, `PAYLOAD_SERVER_URL`, `FRONTEND_ORIGIN` untuk contact).
+- [ ] Backend: origin production ada di `trustedBrowserOrigins` + image sudah di-build ulang.
+- [ ] Deploy pertama: `db-push` sudah dijalankan jika DB kosong.
+- [ ] Frontend: `.env.production` + `pnpm build` setelah setiap ubah `NEXT_PUBLIC_*`.
+- [ ] Nginx: SSL aktif, `client_max_body_size` cukup untuk upload admin.
+- [ ] Firewall: hanya SSH + HTTP(S); port 3000/3001 tidak perlu publik.
+- [ ] `chmod 600 .env` (backend & frontend) di server.
+
+Dokumen ini fokus ke **frontend + PM2 + Nginx** dan integrasi dengan backend Docker; backup DB, rotasi secret, dan hardening lanjutan mengikuti kebijakan Anda.

+ 569 - 0
docs/README-DEPLOY-HANOMAN-SAME-DOMAIN.md

@@ -0,0 +1,569 @@
+# Deploy Hanoman — skenario A (IP / VPS sama, domain tetap)
+
+Panduan ini untuk **mengganti situs yang sudah ada** di **`hanoman.co.id`** dengan stack baru (frontend Next terpisah + backend Payload + Postgres di Docker), **tanpa pindah server** dan **tanpa mengganti IP** yang sudah dipakai domain tersebut.
+
+Asumsi:
+
+- Record DNS **`hanoman.co.id`** dan **`www.hanoman.co.id`** sudah mengarah ke **VPS yang sama** tempat Anda mengerjakan deploy.
+- Repositori **frontend** terpisah dari **backend** (`hanoman-website-be`).
+- Target URL publik setelah selesai:
+
+| Peran | URL |
+|-------|-----|
+| Situs pengunjung (Next.js frontend) | `https://hanoman.co.id` dan/atau `https://www.hanoman.co.id` |
+| API Payload, admin, REST `/api/...` | `https://api.hanoman.co.id` |
+
+Frontend memanggil backend lewat variabel `NEXT_PUBLIC_*` yang mengarah ke **`https://api.hanoman.co.id`**, bukan ke path `/api` di domain utama — menghindari bentrok dengan route Next di root domain.
+
+---
+
+## 1. Gambaran arsitektur di VPS
+
+```
+Internet
+   │
+   ▼
+┌────────────────────────┐
+│  Nginx :80 / :443      │  SSL (Let’s Encrypt), reverse proxy
+└───────────┬────────────┘
+            │
+       ┌────┴─────┐
+       ▼          ▼
+┌─────────────┐  ┌──────────────────────────────────┐
+│ PM2         │  │ Docker Compose                    │
+│ Next.js FE  │  │  • postgres                       │
+│ 127.0.0.1   │  │  • payload → 127.0.0.1:3000       │
+│ :3001       │  │  • volume ./media → /app/media    │
+└─────────────┘  └──────────────────────────────────┘
+```
+
+- **Nginx** menerima `hanoman.co.id` / `www` → memproksi ke **`http://127.0.0.1:3001`** (frontend).
+- **Nginx** menerima `api.hanoman.co.id` → memproksi ke **`http://127.0.0.1:3000`** (Payload).
+- Port **3000** dan **3001** cukup listen di localhost; yang terbuka ke internet adalah **80** dan **443** (Nginx).
+
+---
+
+## 2. Prasyarat di VPS
+
+- OS: **Ubuntu 22.04 atau 24.04 LTS** (perintah di bawah memakai **`apt`**).
+- **Docker** + **Docker Compose** (subperintah `docker compose`).
+- **Git**, **curl**, **ca-certificates**.
+- Akses pengguna ke **`sudo`**.
+- **Domain:** **A record** berikut mengarah ke **IP VPS yang sama**:
+  - `hanoman.co.id`
+  - `www.hanoman.co.id`
+  - **`api.hanoman.co.id`** (tambahkan jika belum ada).
+
+### 2.1 Cek cepat (sudah terpasang atau belum)
+
+Jalankan di VPS:
+
+```bash
+lsb_release -a 2>/dev/null || cat /etc/os-release
+command -v sudo && sudo -n true 2>/dev/null && echo "sudo: OK" || echo "sudo: perlu password atau belum ada"
+docker --version 2>/dev/null || echo "Docker: belum terpasang"
+docker compose version 2>/dev/null || echo "docker compose: belum terpasang"
+git --version 2>/dev/null || echo "Git: belum terpasang"
+curl --version 2>/dev/null | head -1 || echo "curl: belum terpasang"
+```
+
+### 2.2 Instalasi paket dasar (curl, git, sudo)
+
+Hanya jika belum ada (misalnya image VPS minimal tanpa `curl`/`git`):
+
+```bash
+apt update
+apt install -y sudo curl ca-certificates git
+```
+
+Jika Anda **bukan** root, pakai:
+
+```bash
+sudo apt update
+sudo apt install -y curl ca-certificates git
+```
+
+### 2.3 Instalasi Docker Engine + Docker Compose (plugin)
+
+**Opsi A — dari repositori Ubuntu (cukup untuk panduan ini)**
+
+```bash
+sudo apt update
+sudo apt install -y docker.io docker-compose-v2
+sudo systemctl enable --now docker
+sudo usermod -aG docker "$USER"
+```
+
+Keluar dari SSH dan masuk lagi (atau jalankan `newgrp docker`) agar grup **`docker`** aktif tanpa `sudo` untuk perintah `docker`.
+
+Cek:
+
+```bash
+docker --version
+docker compose version
+```
+
+Jika paket **`docker-compose-v2`** tidak ditemukan (distro lama), gunakan **Opsi B**.
+
+**Opsi B — skrip resmi Docker (Engine + Compose plugin, versi lebih baru)**
+
+```bash
+curl -fsSL https://get.docker.com -o /tmp/get-docker.sh
+sudo sh /tmp/get-docker.sh
+sudo usermod -aG docker "$USER"
+rm /tmp/get-docker.sh
+```
+
+Lalu **logout/login** atau `newgrp docker`, lalu:
+
+```bash
+docker --version
+docker compose version
+```
+
+### 2.4 Uji Docker tanpa sudo
+
+```bash
+docker run --rm hello-world
+```
+
+Jika error permission denied, pastikan user sudah di grup `docker` dan sesi login sudah diperbarui.
+
+### 2.5 Alat lain yang dipakai di panduan ini
+
+| Kebutuhan | Kapan muncul di panduan | Instal jika belum ada |
+|-----------|-------------------------|------------------------|
+| **Nginx** | Bagian reverse proxy | `sudo apt install -y nginx` |
+| **Certbot** | SSL Let’s Encrypt | `sudo apt install -y certbot python3-certbot-nginx` |
+| **UFW** | Firewall | `sudo apt install -y ufw` |
+| **build-essential** (opsional) | Beberapa `npm install` native | `sudo apt install -y build-essential` |
+
+### 2.6 Node.js (nvm, PM2)
+
+Node dan PM2 dijelaskan di **§4**; tidak wajib di §2 kecuali Anda ingin menginstal lebih dulu. Ringkas:
+
+```bash
+# nvm — ikuti §4.1
+# PM2 global — setelah Node ada:
+npm install -g pm2
+```
+
+---
+
+## 3. Persiapan kode backend (`hanoman-website-be`)
+
+### 3.1 Origin yang dipercaya (CORS / CSRF)
+
+File `src/payload.config.ts` memakai array **`trustedBrowserOrigins`** untuk **`cors`** dan **`csrf`**. Setiap origin browser yang mengakses API atau admin **harus** ada di daftar ini.
+
+Pastikan array tersebut memuat minimal:
+
+- `https://hanoman.co.id`
+- `https://www.hanoman.co.id`
+- `https://api.hanoman.co.id` (admin Payload dibuka dari subdomain ini)
+
+Simpan, commit, dan gunakan commit ini saat build image di VPS.
+
+### 3.2 Clone backend di VPS
+
+```bash
+sudo mkdir -p /opt
+sudo chown $USER:$USER /opt
+cd /opt
+git clone <URL-GIT-BACKEND> hanoman-website-be
+cd hanoman-website-be
+```
+
+### 3.3 File `.env` backend (di server)
+
+Buat atau salin `.env` di root `hanoman-website-be`. Isi wajar (sesuaikan password dan secret):
+
+```env
+# Postgres — dipakai service postgres di Docker Compose
+POSTGRES_DB=hanoman
+POSTGRES_USER=postgres
+POSTGRES_PASSWORD=ganti_password_kuat
+POSTGRES_HOST=postgres
+POSTGRES_PORT=5432
+
+# Koneksi dari container payload ke container postgres (hostname = nama service Compose)
+DATABASE_URL=postgresql://postgres:ganti_password_kuat@postgres:5432/hanoman
+
+PAYLOAD_SECRET=ganti_secret_panjang_acak
+NODE_ENV=production
+
+# URL publik API (tanpa slash di akhir)
+PAYLOAD_SERVER_URL=https://api.hanoman.co.id
+
+# Origin frontend untuk CORS endpoint kontak (skema + host, tanpa path)
+FRONTEND_ORIGIN=https://hanoman.co.id
+```
+
+Catatan:
+
+- **`DATABASE_URL`** untuk container **wajib** memakai hostname **`postgres`**, bukan `127.0.0.1`.
+- **`FRONTEND_ORIGIN`** harus sama dengan origin yang dipakai pengunjung (jika kanonis Anda `https://www.hanoman.co.id`, isi itu).
+- Variabel untuk email kontak / SMTP (jika dipakai) tetap sesuai kebutuhan proyek Anda.
+
+Amankan file:
+
+```bash
+chmod 600 .env
+```
+
+### 3.4 Jalankan Postgres dan Payload
+
+```bash
+cd /opt/hanoman-website-be
+docker compose --env-file .env -f docker-compose.yml up -d --build
+```
+
+Tunggu container `postgres` sehat, lalu cek:
+
+```bash
+docker compose ps
+```
+
+### 3.5 Buat tabel database (sekali, jika DB baru)
+
+Dengan **`NODE_ENV=production`**, Payload **tidak** mendorong schema otomatis. Setelah Postgres jalan, dari folder backend (dengan `pnpm` dan dependency terpasang):
+
+```bash
+cd /opt/hanoman-website-be
+corepack enable && corepack prepare pnpm@latest --activate
+pnpm install --frozen-lockfile
+pnpm run db:push
+```
+
+Perintah `db:push` menjalankan script yang memuat Payload dengan **`NODE_ENV=development`** hanya untuk proses tersebut, sehingga schema Drizzle diterapkan ke database.
+
+### 3.6 Uji backend di localhost VPS
+
+```bash
+curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3000/api/
+```
+
+Respons HTTP (bukan connection refused) berarti proses `payload` mendengarkan.
+
+### 3.7 Buat user admin
+
+Buka di browser (setelah langkah Nginx + SSL di bawah selesai, atau sementara dengan tunnel):  
+`https://api.hanoman.co.id/admin`  
+dan buat akun admin pertama.  
+(Jika SSL belum ada, Anda bisa uji sementara dari VPS dengan `curl` ke localhost — untuk login admin biasanya browser + HTTPS.)
+
+---
+
+## 4. Node.js, pnpm, dan PM2 (untuk frontend)
+
+### 4.1 Node (disarankan pakai nvm)
+
+Samakan **major** Node dengan `engines` di `package.json` frontend (misalnya 22):
+
+```bash
+curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
+source ~/.bashrc
+nvm install 22
+nvm use 22
+node -v
+```
+
+### 4.2 PM2 global
+
+```bash
+npm install -g pm2
+pm2 startup
+```
+
+Jalankan perintah `sudo` yang dikeluarkan `pm2 startup` agar PM2 hidup lagi setelah reboot.
+
+---
+
+## 5. Frontend — build dan PM2
+
+### 5.1 Clone dan install
+
+```bash
+cd /opt
+git clone <URL-GIT-FRONTEND> hanoman-frontend
+cd hanoman-frontend
+corepack enable
+corepack prepare pnpm@latest --activate
+pnpm install --frozen-lockfile
+```
+
+### 5.2 Environment production frontend
+
+Buat **`.env.production`** di root frontend. Contoh (sesuaikan nama variabel dengan yang dipakai kode frontend Anda):
+
+```env
+NEXT_PUBLIC_PAYLOAD_API_URL=https://api.hanoman.co.id
+```
+
+Aturan praktis:
+
+- Tanpa **slash** di akhir URL basis (`https://api.hanoman.co.id`).
+- Sesuaikan dengan cara kode menyusun path: jika kode menambahkan `'/api/posts'`, basis URL **tanpa** `/api`; jika kode menambahkan `'/posts'` ke basis yang sudah berisi `/api`, sesuaikan.
+
+Setiap mengubah variabel **`NEXT_PUBLIC_*`**, wajib **`pnpm run build`** ulang.
+
+### 5.3 Build
+
+```bash
+pnpm run build
+```
+
+### 5.4 PM2 — `ecosystem.config.cjs`
+
+Di `/opt/hanoman-frontend`, buat file `ecosystem.config.cjs`:
+
+```javascript
+module.exports = {
+  apps: [
+    {
+      name: 'hanoman-fe',
+      cwd: '/opt/hanoman-frontend',
+      script: 'node_modules/next/dist/bin/next',
+      args: 'start -p 3001 -H 127.0.0.1',
+      instances: 1,
+      autorestart: true,
+      max_memory_restart: '512M',
+      env: {
+        NODE_ENV: 'production',
+      },
+    },
+  ],
+}
+```
+
+Jalankan:
+
+```bash
+cd /opt/hanoman-frontend
+pm2 start ecosystem.config.cjs
+pm2 save
+```
+
+Uji:
+
+```bash
+curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3001/
+```
+
+---
+
+## 6. DNS untuk `api.hanoman.co.id`
+
+Di panel DNS (Cloudflare, registrar, dll.):
+
+- Tambah **A** record: **`api.hanoman.co.id`** → **IP VPS yang sama** dengan `hanoman.co.id`.
+- TTL bisa 300 detik saat pengujian.
+
+Tunggu propagasi (cek dengan `dig api.hanoman.co.id +short`).
+
+---
+
+## 7. Nginx — reverse proxy
+
+### 7.1 Pasang Nginx
+
+```bash
+sudo apt update
+sudo apt install -y nginx
+```
+
+### 7.2 Cadangkan konfigurasi situs lama (jika ada)
+
+```bash
+sudo cp -a /etc/nginx/sites-available/default /etc/nginx/sites-available/default.bak.$(date +%F) 2>/dev/null || true
+```
+
+Jika Anda punya file lain untuk domain lama, cadangkan juga sebelum mengganti isinya.
+
+### 7.3 Server block — API (`api.hanoman.co.id`)
+
+Buat `/etc/nginx/sites-available/hanoman-api`:
+
+```nginx
+server {
+    listen 80;
+    server_name api.hanoman.co.id;
+
+    client_max_body_size 50M;
+
+    location / {
+        proxy_pass http://127.0.0.1:3000;
+        proxy_http_version 1.1;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection "upgrade";
+        proxy_read_timeout 300s;
+        proxy_send_timeout 300s;
+    }
+}
+```
+
+### 7.4 Server block — frontend (`hanoman.co.id` dan `www`)
+
+Buat `/etc/nginx/sites-available/hanoman-www`:
+
+```nginx
+server {
+    listen 80;
+    server_name hanoman.co.id www.hanoman.co.id;
+
+    client_max_body_size 20M;
+
+    location / {
+        proxy_pass http://127.0.0.1:3001;
+        proxy_http_version 1.1;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection "upgrade";
+        proxy_read_timeout 120s;
+    }
+}
+```
+
+### 7.5 Aktifkan situs dan uji konfigurasi
+
+```bash
+sudo ln -sf /etc/nginx/sites-available/hanoman-api /etc/nginx/sites-enabled/
+sudo ln -sf /etc/nginx/sites-available/hanoman-www /etc/nginx/sites-enabled/
+sudo rm -f /etc/nginx/sites-enabled/default
+sudo nginx -t
+sudo systemctl reload nginx
+```
+
+---
+
+## 8. SSL (Let’s Encrypt)
+
+```bash
+sudo apt install -y certbot python3-certbot-nginx
+sudo certbot --nginx \
+  -d api.hanoman.co.id \
+  -d hanoman.co.id \
+  -d www.hanoman.co.id
+```
+
+Ikuti prompt. Certbot akan menambahkan `listen 443 ssl` dan pengaturan sertifikat.
+
+Setelah HTTPS aktif:
+
+1. Pastikan **`.env.production`** frontend memakai **`https://api.hanoman.co.id`** (bukan `http`).
+2. Build ulang frontend dan restart PM2:
+
+```bash
+cd /opt/hanoman-frontend
+pnpm run build
+pm2 restart hanoman-fe
+```
+
+3. Pastikan **`PAYLOAD_SERVER_URL`** di backend **`https://api.hanoman.co.id`**, lalu rebuild dan jalankan ulang container backend jika Anda mengubah `.env`:
+
+```bash
+cd /opt/hanoman-website-be
+docker compose --env-file .env -f docker-compose.yml up -d --build
+```
+
+---
+
+## 9. Firewall
+
+```bash
+sudo ufw allow OpenSSH
+sudo ufw allow 'Nginx Full'
+sudo ufw enable
+sudo ufw status
+```
+
+Tidak perlu membuka port **3000** dan **3001** ke publik jika semua lalu lintas lewat Nginx.
+
+---
+
+## 10. Cutover dari situs lama (skenario A)
+
+1. **Selesaikan** langkah backend (Docker), `db:push`, frontend (PM2), Nginx, dan SSL **sebelum** memutuskan mengganti traffic produksi, jika memungkinkan uji dengan hosts file lokal atau subdomain uji.
+2. **Cadangkan** file Nginx lama yang melayani `hanoman.co.id`.
+3. **Hentikan** proses lama yang memakai port **3001** (atau port lain yang bentrok) — misalnya PM2 app lama: `pm2 delete nama-app-lama`.
+4. **Reload Nginx** hanya jika `nginx -t` sukses.
+
+Downtime sering hanya **beberapa detik** saat `reload`. Jika perlu halaman maintenance, bisa sementara `return 503` di Nginx (opsional).
+
+---
+
+## 11. Memperbarui deploy nanti
+
+**Frontend:**
+
+```bash
+cd /opt/hanoman-frontend
+git pull
+pnpm install --frozen-lockfile
+# edit .env.production jika perlu
+pnpm run build
+pm2 restart hanoman-fe
+```
+
+**Backend:**
+
+```bash
+cd /opt/hanoman-website-be
+git pull
+docker compose --env-file .env -f docker-compose.yml up -d --build
+```
+
+Setelah mengubah **`trustedBrowserOrigins`** atau **`PAYLOAD_SERVER_URL`**, selalu **build ulang image** backend.
+
+---
+
+## 12. Troubleshooting
+
+| Gejala | Yang dicek |
+|--------|------------|
+| 502 ke frontend | `pm2 status`, `curl http://127.0.0.1:3001/` |
+| 502 ke API | `docker compose ps`, `curl http://127.0.0.1:3000/api/` |
+| CORS / CSRF / fetch gagal | Origin `https://hanoman.co.id`, `https://www.hanoman.co.id`, `https://api.hanoman.co.id` ada di `trustedBrowserOrigins`; `PAYLOAD_SERVER_URL=https://api.hanoman.co.id` |
+| Mixed content / redirect aneh di admin | Semua URL publik memakai **`https://`**, bukan `http` |
+| `relation "users" does not exist` | Jalankan **`pnpm run db:push`** sekali saat DB masih kosong |
+| Media 404 | Folder **`media/`** di host ter-mount ke container (`./media:/app/media`) dan file benar-benar ada di disk |
+| Upload admin gagal (413) | Naikkan `client_max_body_size` di blok `api.hanoman.co.id` |
+
+---
+
+## 13. Ringkasan port di host
+
+| Layanan | Alamat lokal | Publik |
+|---------|----------------|--------|
+| Payload (Docker) | `127.0.0.1:3000` | `https://api.hanoman.co.id` |
+| Frontend (PM2) | `127.0.0.1:3001` | `https://hanoman.co.id` / `https://www.hanoman.co.id` |
+| PostgreSQL | `127.0.0.1:5432` (opsional dari host) | Jangan expose ke internet tanpa kebutuhan |
+
+---
+
+## 14. Checklist sebelum dianggap selesai
+
+- [ ] A record `api.hanoman.co.id` mengarah ke IP VPS yang sama.
+- [ ] `.env` backend: `DATABASE_URL` memakai host `postgres`, `PAYLOAD_SERVER_URL`, `FRONTEND_ORIGIN`, `PAYLOAD_SECRET`.
+- [ ] `trustedBrowserOrigins` memuat ketiga origin HTTPS di atas.
+- [ ] `pnpm run db:push` sudah dijalankan untuk DB baru.
+- [ ] `.env.production` frontend memakai `https://api.hanoman.co.id` dan build sudah dijalankan setelah perubahan `NEXT_PUBLIC_*`.
+- [ ] Nginx + Certbot untuk ketiga hostname.
+- [ ] `ufw` aktif; SSH dan Nginx Full diizinkan.
+- [ ] `chmod 600` untuk `.env` backend dan file rahasia frontend.
+
+---
+
+## 15. Rollback kasar
+
+- Simpan salinan konfigurasi Nginx sebelum cutover.
+- Untuk mengembalikan perilaku lama: restore file Nginx, `sudo nginx -t && sudo systemctl reload nginx`, dan jalankan kembali proses/PM2 versi lama jika masih ada di server.
+
+Backup database Postgres (`pg_dump`) disarankan sebelum migrasi besar atau perubahan schema produksi.

+ 1 - 0
package.json

@@ -13,6 +13,7 @@
     "lint": "cross-env NODE_OPTIONS=--no-deprecation next lint",
     "payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
     "seed": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/seed.ts",
+    "db:push": "cross-env NODE_OPTIONS=--no-deprecation NODE_ENV=development tsx src/scripts/push-db-schema.ts",
     "start": "cross-env NODE_OPTIONS=--no-deprecation next start -H 0.0.0.0 -p 3000",
     "test": "pnpm run test:int && pnpm run test:e2e",
     "test:e2e": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" pnpm exec playwright test",

+ 21 - 1
src/payload.config.ts

@@ -18,6 +18,24 @@ const dirname = path.dirname(filename)
 
 /** Upload collection `media` uses Payload default local storage (`./media` relative to the app cwd). */
 
+/**
+ * Trusted browser `Origin` values for:
+ * - `cors`: REST/GraphQL dari frontend
+ * - `csrf`: pengambilan JWT dari cookie (admin / sesi) — Origin harus ada di daftar ini
+ */
+const trustedBrowserOrigins: string[] = [
+  'http://localhost:3000',
+  'http://localhost:3001',
+  'http://127.0.0.1:3000',
+  'http://127.0.0.1:3001',
+  'https://hanoman.co.id',
+  'https://www.hanoman.co.id',
+  'https://api.hanoman.co.id',
+]
+
+/** URL publik backend (mis. https://api.hanoman.co.id). Kosongkan di lokal jika akses lewat localhost:3000. */
+const serverURL = process.env.PAYLOAD_SERVER_URL?.trim() ?? ''
+
 export default buildConfig({
   admin: {
     user: Users.slug,
@@ -37,7 +55,9 @@ export default buildConfig({
     },
   }),
   sharp,
-  cors: '*',
+  ...(serverURL ? { serverURL } : {}),
+  cors: trustedBrowserOrigins,
+  csrf: trustedBrowserOrigins,
   // Rate limiting is implemented in src/middleware.ts
   // Configuration: 500 requests per 15 minutes, trustProxy: true
 })

+ 20 - 0
src/scripts/push-db-schema.ts

@@ -0,0 +1,20 @@
+/**
+ * Satu kali: terapkan schema Payload ke Postgres (tabel users, media, …).
+ * Jalankan dengan NODE_ENV=development (lihat script `db:push` di package.json).
+ */
+import 'dotenv/config'
+import { getPayload } from 'payload'
+
+import config from '../payload.config'
+
+async function main() {
+  const payloadConfig = await config
+  const payload = await getPayload({ config: payloadConfig })
+  console.log('Database schema push finished.')
+  await payload.destroy()
+}
+
+main().catch((err) => {
+  console.error(err)
+  process.exit(1)
+})