{"id":"cmvc2mr1p0001qp2oi5pwjqan","slug":"Zpromo","judul":"Zpromo (Zadv)","deskripsi":"Dashboard generate & publish artikel promosi otomatis untuk ekosistem Z (ZPos, ZXGym, ZXRoom, Zomet, ZXParenting). Cron harian generate artikel via Gemini, publish ke markdown zomet-main + API publik untuk landing page tiap app.","parentId":null,"urut":3,"kategori":"web","tag":"[\"zadv\",\"artikel\",\"cron\",\"gemini\",\"seo\",\"landing\"]","status":"aktif","versi":"1.0","konsep":"# Zpromo (Zadv)\n\n## Tujuan\nDashboard otomasi konten promosi untuk ekosistem aplikasi Z. Generate artikel SEO harian via Gemini AI, publish ke zomet-main (markdown) + API publik untuk landing page tiap app.\n\n## Arsitektur\n- Frontend: Next.js 15 + Tailwind, dashboard zpromo.zomet.my.id\n- DB: PostgreSQL (Coolify), Prisma ORM\n- AI: Google Gemini 2.5 Flash untuk generate judul/deskripsi/konten/tags\n- Storage: Foto pool ArtikelFoto, upload via /api/uploads/\n- Publish: GitHub API (zomet-main markdown) + API publik (app landing)\n\n## Cron Harian\n1. POST /api/artikel/trigger (GitHub Actions 07:00 WIB)\n2. publishScheduled() - publish artikel scheduledFor=today\n3. generateSatuArtikel() per app - generate artikel baru today+3 hari\n4. Setiap app punya ZADV_API_URL env -> fetch /api/artikel/publik?appId=X\n\n## Foto Pool\n- Upload foto di dashboard -> ArtikelFoto table\n- generateDraft() ambil foto random, set gambarPath + fotoId\n- Foto accessible via /api/uploads/artikel/xxx.jpeg (HTTP+HTTPS)\n\n## API Endpoints\n- GET /api/artikel/publik?appId=X&limit=N\n- GET /api/artikel/publik/[slug]\n- POST /api/artikel/trigger (X-Cross-App-Secret header)\n- POST /api/artikel/pre-generate\n- GET/POST /api/artikel/foto\n- GET/POST /api/artikel/settings\n- GET /api/artikel/riwayat\n\n## App Mapping (appId)\n| App | appId | Domain |\n|-----|-------|--------|\n| Z1Pos | 4 | z1pos.zomet.my.id |\n| ZXGym | 5 | zxgym.zomet.my.id |\n| ZXRoom | 8 | zxroom.zomet.my.id |\n| Zomet | 9 | www.zomet.my.id |\n| ZXParenting | 10 | zxparenting.zomet.my.id |\n\n## Environment\n- ADMIN_PASSWORD - login dashboard\n- DATABASE_URL - PostgreSQL\n- GITHUB_TOKEN - push markdown ke zomet-main\n- ZADV_PUBLIC_URL - base URL image (https://zpromo.zomet.my.id)\n- GEMINI_API_KEY - AI generate\n- X_CROSS_APP_SECRET - auth trigger","createdAt":"2026-10-17T07:24:37.357Z","updatedAt":"2026-10-17T07:30:21.863Z","bagian":[{"id":"cmvc2xch90003qp2oc31xx69l","blueprintId":"cmvc2mr1p0001qp2oi5pwjqan","judul":"Arsitektur Sistem","konten":"## Komponen Utama\n\n### Frontend (Dashboard)\n- Next.js 15 + Tailwind CSS\n- Domain: zpromo.zomet.my.id\n- Halaman: Dashboard, Foto Pool, Artikel Riwayat, Settings\n\n### Backend API\n- Next.js Route Handlers (App Router)\n- Prisma ORM + PostgreSQL (Coolify managed)\n- Endpoints:\n  - `/api/artikel/publik` — publik, no auth\n  - `/api/artikel/trigger` — cron, X-Cross-App-Secret header\n  - `/api/artikel/foto` — CRUD foto pool\n  - `/api/artikel/settings` — autopost config\n  - `/api/artikel/riwayat` — admin, cookie auth\n\n### Database Schema\n```\nArtikelDraft  — artikel (status: draft|scheduled|published)\nArtikelFoto   — foto pool (url, useCount)\nArtikelIde    — bank ide artikel per app\nPengaturan    — key-value settings\nPromoApp      — daftar app (appId, nama, url, artikelAktif)\n```\n\n### External Integration\n- **GitHub API**: push markdown ke repo djvpri/zomet-main/content/artikel/\n- **Google Gemini**: generate judul, deskripsi, konten, tags\n- **Coolify auto-deploy**: GitHub App webhook trigger rebuild","media":"[]","urut":0,"createdAt":"2026-10-17T07:32:51.693Z","updatedAt":"2026-10-17T07:32:51.693Z"},{"id":"cmvc2xckc0005qp2oqatchrwt","blueprintId":"cmvc2mr1p0001qp2oi5pwjqan","judul":"Flow Generate Artikel","konten":"## Cron Harian (07:00 WIB)\n\n1. **GitHub Actions** trigger `POST /api/artikel/trigger`\n2. **publishScheduled()** — cek artikel `status=scheduled, scheduledFor=today`\n   - Push markdown ke zomet-main GitHub (jika tujuanZomet=true)\n   - Set `status=published, publishedAt=now()`\n3. **generateSatuArtikel()** per app (yang punya web URL)\n   - Ambil ide dari `ArtikelIde` (atau generate via Gemini)\n   - `ambilFoto()` — foto random dari pool, set `gambarPath + fotoId`\n   - Gemini generate konten lengkap (judul, deskripsi, tags, body markdown)\n   - Save ke `ArtikelDraft` dengan `status=scheduled, scheduledFor=today+3`\n4. **Pre-generate** (manual via dashboard)\n   - Generate N hari ke depan sekaligus\n\n## App Landing Fetch\n- Setiap app (ZPos, ZXGym, ZXRoom) punya env `ZADV_API_URL=https://zpromo.zomet.my.id`\n- Landing page fetch `/api/artikel/publik?appId=X&limit=N` (ISR revalidate 300s)\n- `imgUrl()` helper convert relative `/api/uploads/...` → absolute `https://zpromo.zomet.my.id/api/uploads/...`","media":"[]","urut":1,"createdAt":"2026-10-17T07:32:51.804Z","updatedAt":"2026-10-17T07:32:51.804Z"},{"id":"cmvc2xcnc0007qp2oiq6b6uau","blueprintId":"cmvc2mr1p0001qp2oi5pwjqan","judul":"Foto Pool","konten":"## Upload & Storage\n- Upload foto via dashboard → `/api/artikel/foto` (POST multipart)\n- File disimpan di `/app/public/uploads/artikel/` (container volume mount)\n- Record di `ArtikelFoto` table: `{id, url, filename, useCount}`\n- URL format: `/api/uploads/artikel/artikel-<timestamp>-<random>.jpeg`\n\n## Usage di Artikel\n- `ambilFoto()` di `src/lib/artikel.ts` — foto random, prefer useCount=0\n- `generateDraft()` set `gambarPath: foto.url` + `fotoId: foto.id`\n- Increment `useCount` saat foto dipakai\n\n## Akses Publik\n- `/api/uploads/[...path]` route handler serve file statis\n- No auth required\n- Accessible via HTTP + HTTPS (Traefik TLS certresolver)","media":"[]","urut":2,"createdAt":"2026-10-17T07:32:51.912Z","updatedAt":"2026-10-17T07:32:51.912Z"},{"id":"cmvc2xcsx0009qp2oz2p19jyj","blueprintId":"cmvc2mr1p0001qp2oi5pwjqan","judul":"App Mapping","konten":"## Daftar App (PromoApp table)\n\n| appId | Nama | Domain | Artikel Aktif |\n|-------|------|--------|---------------|\n| 1 | ZGold | — | ❌ |\n| 2 | ZBengkel | — | ❌ |\n| 3 | ZBilliar | — | ❌ |\n| 4 | Z1Pos | z1pos.zomet.my.id | ✅ |\n| 5 | ZXGym | zxgym.zomet.my.id | ✅ |\n| 6 | Z-Resto | — | ❌ |\n| 7 | Z-Absen | — | ❌ |\n| 8 | ZXRoom | zxroom.zomet.my.id | ✅ |\n| 9 | Zomet | www.zomet.my.id | ✅ |\n| 10 | ZXParenting | zxparenting.zomet.my.id | ✅ |\n\nHanya app dengan `url` tidak null + `artikelAktif=true` yang di-generate artikelnya.","media":"[]","urut":3,"createdAt":"2026-10-17T07:32:52.114Z","updatedAt":"2026-10-17T07:32:52.114Z"},{"id":"cmvc2xcxh000bqp2osn5zwjx2","blueprintId":"cmvc2mr1p0001qp2oi5pwjqan","judul":"Environment Variables","konten":"## Container Env (Coolify)\n\n| Var | Value | Purpose |\n|-----|-------|---------|\n| `ADMIN_PASSWORD` | (secret) | Login dashboard |\n| `DATABASE_URL` | postgresql://... | PostgreSQL connection |\n| `GITHUB_TOKEN` | github_pat_... | Push markdown ke zomet-main repo |\n| `GEMINI_API_KEY` | AIza.../AQ... | AI generate artikel |\n| `ZADV_PUBLIC_URL` | https://zpromo.zomet.my.id | Base URL untuk image absolute |\n| `X_CROSS_APP_SECRET` | z-ecosystem-admin-2026 | Auth trigger endpoint |\n\n## App Env (ZPos, ZXGym, ZXRoom)\n\n| Var | Value |\n|-----|-------|\n| `ZADV_API_URL` | https://zpromo.zomet.my.id |\n\nSet di Coolify DB `environment_variables` table. Note: Coolify v4.4.6 bug — env tidak auto-generate di `.env`, perlu append manual + `docker compose up -d --force-recreate`.","media":"[]","urut":4,"createdAt":"2026-10-17T07:32:52.277Z","updatedAt":"2026-10-17T07:32:52.277Z"}],"children":[],"parent":null}