diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..9963f03 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,34 @@ +name: Test + +on: + push: + branches: ["main", "migrasi-db", "development"] + pull_request: + branches: ["main", "migrasi-db", "development"] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.4.1 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Type check + run: pnpm check-types + + - name: Unit tests + run: pnpm test diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..312d9d5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 yudha556 & caya8502 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 15dd016..2d5bfe3 100644 --- a/README.md +++ b/README.md @@ -86,4 +86,8 @@ Planify terbuka untuk kontribusi. Untuk menambahkan fitur baru atau memperbaiki 4. Push branch: git push origin fitur-baru 5. Buat Pull Request -![alt text]() \ No newline at end of file +![alt text]() + +# Lisensi + +[MIT](LICENSE) \ No newline at end of file diff --git a/apps/web-api/.env.example b/apps/web-api/.env.example new file mode 100644 index 0000000..da35826 --- /dev/null +++ b/apps/web-api/.env.example @@ -0,0 +1,26 @@ +# ================================ +# Web API Environment Variables +# ================================ +PORT=4000 +NODE_ENV=development +JWT_SECRET=your-super-secret-jwt-key-min-32-chars + +# Database / Supabase +DATABASE_URL=your_key +SUPABASE_URL=your_key +SUPABASE_SERVICE_ROLE_KEY=your_key + +# AI Engine (Groq) +GROQ_API_KEY=your_key +# GROQ_API_KEY_1= for key rotation system +# GROQ_API_KEY_2=your_key +# GROQ_API_KEY_3=your_key + +# Security +CORS_ORIGIN=http://localhost:3000 +REDIS_URL=redis://localhost:6379 + +# ================================ +# Web App (Frontend) Environment Variables +# ================================ +NEXT_PUBLIC_API_URL=http://localhost:4000/api \ No newline at end of file diff --git a/apps/web-api/.gitignore b/apps/web-api/.gitignore index 809ebc7..d0cb127 100644 --- a/apps/web-api/.gitignore +++ b/apps/web-api/.gitignore @@ -39,3 +39,6 @@ prisma/dev.db-journal # Misc .turbo/ + +# Local Agent Docs +.agents/ \ No newline at end of file diff --git a/apps/web-api/API-Endpoint-Docs.md b/apps/web-api/API-Endpoint-Docs.md index 8023c11..03959e2 100644 --- a/apps/web-api/API-Endpoint-Docs.md +++ b/apps/web-api/API-Endpoint-Docs.md @@ -1,11 +1,422 @@ # **Dokumentasi API Planify** -Dokumen ini menjelaskan endpoint API yang digunakan oleh aplikasi Planify untuk kebutuhan autentikasi, pembuatan dokumentasi berbasis AI, manajemen project, serta ekspor dokumen ke PDF. +Dokumen ini menjelaskan endpoint API yang digunakan oleh aplikasi Planify untuk kebutuhan autentikasi, pembuatan dokumentasi berbasis AI, manajemen project, serta ekspor dokumen ke PDF/Markdown. Dokumentasi ini ditujukan untuk **Frontend Developer** dan pihak lain yang perlu memahami cara berkomunikasi dengan backend Planify. +> **Dokumentasi interaktif (disarankan):** buka `/api/docs` di browser +> (Swagger UI) atau `/api/docs.json` buat spec mentahnya. + +--- + +# **BAGIAN A — API v1 (dipakai sekarang)** + +**Base URL (Development):** + +``` +http://localhost:4000/api/v1 +``` + +**Autentikasi:** +Sebagian besar endpoint membutuhkan autentikasi menggunakan **Bearer Token (JWT)**. + +Token dikirim melalui header: + +``` +Authorization: Bearer +``` + +Endpoint yang **tidak memerlukan autentikasi**: + +* `POST /auth/register` +* `POST /auth/login` +* `GET /ai/status` +* `GET /health` + +**Batasan (rate limit):** + +* Global: **100 request / 15 menit** per IP +* Register & login: **5 request / 15 menit** per IP (anti brute-force) +* Generate AI: **20 request / jam** per IP +* Export PDF/Markdown: **10 request / jam** per IP + +Kalau kena limit, responsenya `429` dengan code `RATE_LIMIT_EXCEEDED`. + +**Validasi input:** +Semua body POST/PUT/PATCH divalidasi pakai Zod. Kalau gagal, +responsenya `400` dengan code `VALIDATION_ERROR` plus detail field yang salah: + +```json +{ + "success": false, + "message": "Validation failed", + "code": "VALIDATION_ERROR", + "error": "[{\"field\":\"body.email\",\"message\":\"Invalid email format\"}]" +} +``` + +**Batas ukuran body:** maksimal **1MB** per request. + +--- + +## **A.1. Health Check** + +``` +GET /health +``` + +Response (200): + +```json +{ "status": "ok" } +``` + +--- + +## **A.2. Autentikasi User** + +### **A.2.1 Registrasi User** + +``` +POST /auth/register +``` + +**Request Body:** + +```json +{ + "email": "user@example.com", + "password": "securePassword123", + "name": "John Doe" +} +``` + +**Aturan validasi:** + +* `email`: wajib, format email valid +* `password`: wajib, minimal 8 karakter +* `name`: opsional, maksimal 100 karakter + +**Response Berhasil (201):** + +```json +{ + "success": true, + "message": "User registered successfully", + "data": { "id": "uuid", "email": "user@example.com", "name": "John Doe" } +} +``` + +**Error:** + +* `VALIDATION_ERROR` — email/password tidak valid +* `AUTH_EMAIL_EXISTS` — email sudah terdaftar + +--- + +### **A.2.2 Login User** + +``` +POST /auth/login +``` + +**Request Body:** + +```json +{ + "email": "user@example.com", + "password": "securePassword123" +} +``` + +**Response Berhasil (200):** + +```json +{ + "success": true, + "message": "Login successful", + "data": { + "token": "eyJhbG...", + "user": { "id": "uuid", "email": "user@example.com", "name": "John Doe" } + } +} +``` + +**Catatan:** + +* Token disimpan di FE (localStorage/state) dan dikirim di header `Authorization` tiap request yang butuh auth. +* `AUTH_INVALID_CREDENTIALS` — email/password salah. + +--- + +### **A.2.3 Profil User Login (butuh token)** + +``` +GET /auth/me +``` + +Response (200): `{ success: true, message: "...", data: { userId, email } }` + +--- + +### **A.2.4 Logout (butuh token)** + +``` +POST /auth/logout +``` + +Token yang dipakai langsung masuk blacklist dan nggak bisa dipakai lagi. +FE wajib hapus token dari localStorage/state setelah logout. + +**Error token:** + +* `TOKEN_MISSING` — tidak ada token di header +* `TOKEN_INVALID` — token tidak valid/kadaluarsa +* `TOKEN_REVOKED` — token sudah di-logout + +--- + +## **A.3. AI & Pembuatan Dokumen** + +> Catatan: generate brief & diagram preview itu **GRATIS**. +> Koin cuma kepotong pas export PDF/Markdown (dan generate diagram terpisah). + +### **A.3.1 Status Layanan AI (publik)** + +``` +GET /ai/status +``` + +Response (200): `{ success: true, data: { available: boolean, provider: "groq" } }` + +--- + +### **A.3.2 Generate Project Brief (butuh token)** + +``` +POST /ai/project-brief +``` + +**Request Body:** + +```json +{ + "projectName": "E-Commerce App", + "projectDescription": "A marketplace platform for small businesses", + "projectType": "webapp", + "documentStyle": "professional", + "targetAudience": "Small business owners", + "keyFeatures": ["Cart", "Checkout"], + "techStack": ["Next.js", "Supabase"], + "includeDiagram": false +} +``` + +**Aturan validasi:** + +* `projectName`: wajib, 1–200 karakter +* `projectDescription`: wajib, minimal 10 karakter, maksimal 5000 +* `projectType`: `webapp` | `mobile` | `research` | `enterprise` +* `documentStyle`: `professional` | `formal` | `concise` + +**Response Berhasil (200):** + +```json +{ + "success": true, + "message": "Project brief generated successfully", + "data": { "...": "FULL_DOCUMENT_JSON" }, + "metadata": { "...": "PROVIDER_INFO", "diagramIncluded": false }, + "coins": 48 +} +``` + +--- + +### **A.3.3 Generate Diagram (butuh token, 2 koin)** + +``` +POST /ai/diagram +``` + +**Request Body:** + +```json +{ + "projectName": "E-Commerce App", + "projectDescription": "A marketplace platform for small businesses", + "techStack": ["Next.js", "Supabase"] +} +``` + +Kalau koin kurang: `402` dengan code `INSUFFICIENT_COINS`. + +--- + +### **A.3.4 Saldo Koin (butuh token)** + +``` +GET /ai/coins +``` + +Response (200): `{ success: true, data: { credits: 50 } }` + +--- + +## **A.4. Manajemen Project (butuh token)** + +### **A.4.1 Daftar Project** + +``` +GET /projects +``` + +Response (200): `{ success: true, data: [ { id, title, description, ... } ] }` + +--- + +### **A.4.2 Detail Project** + +``` +GET /projects/:id +``` + +* `:id` harus UUID valid, kalau nggak: `400 VALIDATION_ERROR`. +* Kalau bukan milik user / tidak ada: `404 NOT_FOUND`. + +--- + +### **A.4.3 Buat Project** + +``` +POST /projects +``` + +**Request Body:** + +```json +{ + "title": "App A", + "description": "Deskripsi singkat", + "projectType": "webapp", + "documentStyle": "professional", + "formData": {}, + "currentStep": 1 +} +``` + +* `title`: wajib, 1–200 karakter. + +Response (201): `{ success: true, message: "Project created", data: {...} }` + +--- + +### **A.4.4 Update Project (auto-save)** + +``` +PUT /projects/:id +``` + +Semua field opsional: `title`, `description`, `projectType`, +`currentStep`, `formData`, `generatedBrief`, `documentStyle`, `status` +(`draft` | `published` | `archived`). + +--- + +### **A.4.5 Hapus Project** + +``` +DELETE /projects/:id +``` + +Response (200): `{ success: true, message: "Project deleted successfully" }` + +--- + +## **A.5. Ekspor Dokumen (butuh token, 1 koin)** + +### **A.5.1 Ekspor ke PDF** + +``` +POST /pdf/brief +``` + +Body: JSON `ProjectBriefOutput` (field `title` wajib). +Response: file PDF (`Content-Type: application/pdf`), header `X-Coins-Remaining` berisi sisa koin. + +### **A.5.2 Ekspor ke Markdown** + +``` +POST /markdown/brief +``` + +Body: JSON `ProjectBriefOutput` (field `title` wajib). +Response: file `.md` (`Content-Type: text/markdown`), header `X-Coins-Remaining` berisi sisa koin. + +--- + +## **A.6. Riwayat Aktivitas (butuh token)** + +``` +GET /history?limit=20&offset=0 +``` + +* `limit`: default 20, maksimal 100. +* Response: `{ success: true, data: [...], pagination: { total, limit, offset } }` + +--- + +## **A.7. Profil User (butuh token)** + +``` +GET /user/me +``` + +Response (200): `{ success: true, data: { id, email, fullname, credits, created_at } }` + --- +## **A.8. Format Error Standar** + +Semua error punya format: + +```json +{ + "success": false, + "message": "", + "code": "" +} +``` + +**Daftar code:** + +| Code | Arti | +|------|------| +| `VALIDATION_ERROR` | Input tidak valid (lihat detail field) | +| `RATE_LIMIT_EXCEEDED` | Kebanyakan request, coba lagi nanti | +| `TOKEN_MISSING` / `TOKEN_INVALID` / `TOKEN_REVOKED` | Masalah token auth | +| `AUTH_MISSING_FIELDS` | Email/password kosong | +| `AUTH_EMAIL_EXISTS` | Email sudah terdaftar | +| `AUTH_INVALID_CREDENTIALS` | Email/password salah | +| `INSUFFICIENT_COINS` | Koin kurang (HTTP 402) | +| `NOT_FOUND` | Data tidak ditemukan (HTTP 404) | +| `INTERNAL_ERROR` | Error server (HTTP 500) | + +> Di mode development, response error menyertakan stack trace di field `error`. +> Di production, stack trace disembunyikan. + +--- + +# **BAGIAN B — LEGACY (deprecated, jangan dipakai buat fitur baru)** + +> Endpoint di bawah ini adalah dokumentasi lama **tanpa prefix versi** +> (`/api/...` bukan `/api/v1/...`). Masih bisa diakses dan otomatis +> ngasih header `Deprecation: true`, tapi **sebagian isinya sudah basi** +> (mis. `/ai/generate`, `/ai/regenerate-section`, dan `/pdf/generate` +> sudah tidak ada di kode — gantinya ada di Bagian A). +> +> Buat integrasi baru, selalu pakai **Bagian A**. + ## **1. Informasi Umum** **Base URL (Development):** @@ -356,4 +767,4 @@ File PDF akan otomatis terunduh melalui browser. Dokumentasi ini dibuat untuk memudahkan integrasi frontend dengan backend Planify serta menjadi referensi utama dalam pengembangan fitur lanjutan. ---- \ No newline at end of file +--- diff --git a/apps/web-api/README.md b/apps/web-api/README.md index afb31c3..787516b 100644 --- a/apps/web-api/README.md +++ b/apps/web-api/README.md @@ -2,39 +2,84 @@ **Backend API untuk Planify** 🚀 -Planify API menyediakan endpoint untuk berinteraksi dengan AI engine, menerima input dari user, dan menghasilkan dokumen proyek lengkap. API ini digunakan oleh frontend `apps/web` untuk memproses data proyek dan memanggil AI engine. +REST API buat generate dokumen proyek berbasis AI: autentikasi user, +generate brief + diagram (Groq), sistem koin, export PDF/Markdown, +manajemen project, dan riwayat aktivitas. Datanya di Supabase. --- -## 🛠️ Teknologi - -- Node.js + TypeScript -- Framework: [Express / Fastify / Next.js API Routes] (sesuaikan implementasi) -- Monorepo: Turborepo + pnpm -- AI Engine Integration (LLM / OpenAI API) - ---- -# Cara Jalanin API +## Cara Jalanin ```bash -# Masuk ke folder API -cd apps/api - -# Install dependencies +# Dari root repo pnpm install -# Jalankan server +# Jalanin API (http://localhost:4000) +pnpm --filter web-api dev + +# Atau masuk ke foldernya dulu +cd apps/web-api pnpm dev ``` -# Struktur Folder +| Script | Buat apa | +|--------|----------| +| `pnpm dev` | Jalanin server (hot-reload) | +| `pnpm build` | Compile TypeScript ke `dist/` | +| `pnpm test` | Jalanin unit test (Vitest, 40 test) | +| `pnpm check-types` | Cek type error (`tsc --noEmit`, src + tests) | + +Dari root repo juga bisa: `pnpm test` dan `pnpm check-types` +(jalan via Turbo ke semua package yang punya script itu). + +--- + +## Environment Variables + +Copy `.env.example` jadi `.env`, terus isi: + +| Var | Buat apa | +|-----|----------| +| `PORT` / `NODE_ENV` / `JWT_SECRET` | Server + auth (wajib, tanpa default di production) | +| `SUPABASE_URL` / `SUPABASE_SERVICE_ROLE_KEY` | Database + auth | +| `GROQ_API_KEY` | AI engine | +| `CORS_ORIGIN` | Domain frontend yang dibolehin | +| `REDIS_URL` | Blacklist token (opsional, fallback ke in-memory) | + +--- + +## Endpoint + +Base URL: `http://localhost:4000/api/v1` (endpoint lama `/api/...` +masih jalan tapi deprecated). + +Dokumentasi lengkap: [`API-Endpoint-Docs.md`](./API-Endpoint-Docs.md), +atau buka Swagger UI di `/api/docs` pas server jalan. + +--- + +## Struktur Folder + ```bash -api/ -├─ routes/ # Semua route API (generate, suggest, dll) -├─ controllers/ # Logic untuk memproses request -├─ services/ # Integrasi AI engine, helper functions -├─ middlewares/ # Middleware (auth, logging, error handling) -├─ utils/ # Utility functions -├─ package.json # Dependencies & scripts -└─ tsconfig.json # TypeScript configuration -``` \ No newline at end of file +src/ +├─ app.ts # Setup Express (helmet, cors, rate-limit, dsb) +├─ server.ts # Entry point + graceful shutdown +├─ config/ # env, supabase, redis +├─ routes/ # Definisi route per fitur + routes/v1 (agregator versi) +├─ controllers/ # Handler request/response +├─ services/ # Bisnis logic (auth, ai, coin, pdf, markdown, ...) +├─ middlewares/ # auth, rate-limit, validation, error handler +├─ validators/ # Schema Zod per endpoint +├─ utils/ # logger, app-error, token-blacklist, async-handler +├─ docs/ # Setup Swagger +└─ types/ # Deklarasi tipe tambahan +tests/ # Unit test (Vitest) +``` + +--- + +## Keamanan (ringkas) + +Rate limiting, Helmet headers, validasi Zod di semua endpoint tulis, +CORS terbatas, JWT secret wajib di production, blacklist token via Redis, +logging JSON per-request, limit body 1MB, XSS + HPP guard. \ No newline at end of file diff --git a/apps/web-api/package.json b/apps/web-api/package.json index 6e9602f..5737708 100644 --- a/apps/web-api/package.json +++ b/apps/web-api/package.json @@ -3,7 +3,9 @@ "private": true, "scripts": { "dev": "ts-node-dev --respawn --transpile-only --ignore-watch node_modules src/server.ts", - "build": "tsc" + "build": "tsc", + "test": "vitest run", + "check-types": "tsc --noEmit -p tsconfig.check.json" }, "dependencies": { "@prisma/client": "^6.0.0", @@ -12,18 +14,34 @@ "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.22.1", + "express-rate-limit": "^8.7.0", "groq-sdk": "^0.37.0", + "helmet": "^8.3.0", + "hpp": "^0.2.3", + "ioredis": "^6.0.0", "jsonwebtoken": "^9.0.3", + "pino": "^10.3.1", + "pino-http": "^11.0.0", "prisma": "^6.0.0", - "puppeteer": "^24.36.1" + "puppeteer": "^24.36.1", + "swagger-jsdoc": "^6.3.0", + "swagger-ui-express": "^5.0.1", + "xss-clean": "^0.1.4", + "zod": "^3.25.76" }, "devDependencies": { "@types/bcryptjs": "^3.0.0", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", + "@types/express-rate-limit": "^6.0.2", + "@types/hpp": "^0.2.7", "@types/jsonwebtoken": "^9.0.10", "@types/node": "^20.11.0", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.8", + "pino-pretty": "^13.1.3", "ts-node-dev": "^2.0.0", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vitest": "^5.0.0" } } \ No newline at end of file diff --git a/apps/web-api/public/index.html b/apps/web-api/public/index.html index 11fc7bc..eeb7ae7 100644 --- a/apps/web-api/public/index.html +++ b/apps/web-api/public/index.html @@ -327,6 +327,13 @@ + +