Technical Assessment β Fullstack Engineer Internship Β· Sevima
- Tentang Proyek
- Arsitektur Sistem
- Tech Stack
- Struktur Folder
- Prasyarat & Instalasi
- Konfigurasi Environment
- Menjalankan Secara Lokal
- Docker Compose (Production)
- Dokumentasi API
- Panduan Testing Manual (Browser)
- CI/CD Pipeline
- Database Design
- Fitur AI β Natural Language Builder
- WebSocket Real-Time Events
- Trade-offs & Rencana Perbaikan
- Keputusan Implementasi
FlowForge adalah workflow orchestration engine multi-tenant real-time yang dibangun sebagai technical assessment untuk posisi Fullstack Engineer Internship di Sevima. Proyek ini mensimulasikan peran founding engineer yang membangun platform otomasi workflow β kombinasi eksekusi model Zapier dan GitHub Actions.
| Fitur | Deskripsi | Status |
|---|---|---|
| DAG Execution Engine | Parse, validasi, dan eksekusi workflow berbasis Directed Acyclic Graph dengan topological sort (Kahn's algorithm) | β |
| Multi-Tenant Isolation | Setiap tenant terisolasi penuh via Prisma Proxy yang menyuntikkan tenantId otomatis ke semua query |
β |
| Workflow Versioning | Setiap update workflow membuat versi baru; rollback ke versi manapun | β |
| Async Queue Execution | BullMQ + Redis untuk job queue dengan retry exponential backoff & timeout | β |
| Real-Time Dashboard | Socket.IO WebSocket untuk notifikasi perubahan status step secara live | β |
| DAG Visualizer | React Flow untuk render graph interaktif dengan color-coded status node | β |
| AI Workflow Builder | Natural Language β DAG JSON via Google Gemini API dengan validasi & retry korektif | β |
| Rate Limiting | Redis sliding window per-tenant (default 100 req/mnt) | β |
| Webhook Trigger | Trigger workflow via URL token publik | β |
| JWT Auth + RBAC | Access token (15m) + Refresh token (7d) + role Admin/Editor/Viewer | β |
| Swagger UI | Dokumentasi API interaktif auto-generated | β |
| Docker Compose | Full stack siap pakai dengan satu perintah | β |
βββββββββββββββββββββββββββββββββββββββ
β React Dashboard (Vite) β
β (Auth Β· Workflows Β· Runs Β· AI) β
βββββββββββββββββ¬ββββββββββββββββββββββ
β REST + WebSocket (Socket.IO)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β NestJS API Gateway β
β β
β βββββββββββ ββββββββββββ ββββββββββββ ββββββββββββββββ β
β β Auth β β Workflowsβ β Runs β β AI β β
β βJWT+RBAC β β CRUD+Ver β β History β β NL Builder β β
β βββββββββββ ββββββββββββ ββββββββββββ ββββββββββββββββ β
β β
β βββββββββββββββββββ ββββββββββββββββββββββββββββββββ β
β β TenantGuard β β Rate Limiter (Redis window) β β
β β (Prisma Proxy) β β Webhook Controller β β
β βββββββββββββββββββ ββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β WebSocket Gateway (Socket.IO) β β
β β room: tenant:{tenantId}:run:{runId} β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββ¬βββββββββββββββββββββββ¬βββββββββββββββββββββββ
β β
βββββββββββΌβββββββββ ββββββββββΌβββββββββββββ
β PostgreSQL 16 β β BullMQ (Redis 7) β
β (Prisma ORM) β β Job Queue β
β tenants, users, β β retry/backoff β
β workflows, runs β ββββββββββ¬βββββββββββββ
βββββββββββββββββββββ β
βΌ
ββββββββββββββββββββββββββββββ
β DAG Execution Worker β
β 1. Parse definition_json β
β 2. Topological sort (Kahn) β
β 3. Execute layer by layer β
β (parallel per layer) β
β 4. Retry w/ exp. backoff β
β 5. Emit WebSocket events β
ββββββββββββ¬ββββββββββββ¬βββββββ
β β
ββββββββββββΌβββ βββββββΌβββββββββββ
β MongoDB 7 β β External APIs β
β (exec logs, β β (HTTP step, β
β append-only)β β Gemini API) β
βββββββββββββββ βββββββββββββββββ-β
Client β POST /workflows/:id/trigger
β
API: buat workflow_run (status: queued) β push job BullMQ
β
Worker: dequeue job β parse DAG β topological sort
β
Worker: eksekusi step per layer (step dalam layer = paralel)
β (per step)
Worker: catat log β MongoDB
Worker: update step_run β PostgreSQL
Worker: emit event β WebSocket room
β
Dashboard: terima event β update node color β re-render
| Layer | Teknologi | Versi | Alasan |
|---|---|---|---|
| Runtime | Node.js | 22.x | LTS terbaru, performa terbaik, ESM native |
| Backend Framework | NestJS | 11.x | Modular, DI, built-in validation |
| Language | TypeScript | 5.7 | Type-safety lintas layer |
| ORM | Prisma | 5.22 | Type-safe queries, migration otomatis |
| Database Relasional | PostgreSQL | 16 | ACID, JSONB, relasi workflow/tenant |
| Log Store | MongoDB | 7 | Write-heavy, skema fleksibel |
| Cache & Queue Broker | Redis | 7 | BullMQ + rate limiting |
| Job Queue | BullMQ | 5.x | Retry, backoff, delay built-in |
| Real-Time | Socket.IO | 4.x | WebSocket + polling fallback |
| Auth | JWT + Passport | - | Access 15m + Refresh 7d |
| Frontend | React + Vite | 18 / 5 | Fast HMR, React Flow, TanStack Query |
| Styling | TailwindCSS + Vanilla CSS | - | Estetika Minimalist Editorial premium, performa optimal |
| DAG Visualizer | React Flow | - | Render graph interaktif |
| State/Fetch | TanStack Query | - | Cache, optimistic update |
| AI | Google Gemini 3.5 Flash / Fallbacks | - | Generasi JSON DAG tangguh dengan array model cadangan |
| API Docs | Swagger/OpenAPI | - | Auto-generate dari NestJS decorator |
| CI/CD | GitHub Actions | - | Lint β Test β Build β Docker |
| Container | Docker multi-stage | - | Builder + Runner stage |
flowforge/
βββ apps/
β βββ api/ # NestJS Backend
β β βββ src/
β β β βββ auth/ # JWT strategy, guards, RBAC decorator
β β β βββ tenants/ # Tenant module
β β β βββ users/ # User management
β β β βββ workflows/ # CRUD + versioning + webhook
β β β βββ runs/ # Trigger, status, history, health
β β β βββ execution/ # DAG parser, topo-sort, executor core
β β β β βββ dag-parser.ts # Validasi DAG (cycle, orphan, type)
β β β β βββ topo-sort.ts # Kahn's algorithm
β β β β βββ step-executor.ts # HTTP, script, delay, condition
β β β βββ queue/ # BullMQ producer/consumer
β β β βββ websocket/ # Socket.IO gateway
β β β βββ ai/ # Natural language workflow builder
β β β βββ common/ # Pipes, filters, interceptors, guards
β β β βββ main.ts
β β βββ prisma/
β β β βββ schema.prisma # Skema lengkap (6 model)
β β β βββ migrations/ # Riwayat migrasi otomatis
β β βββ test/ # Integration & E2E tests
β β β βββ auth.e2e-spec.ts
β β β βββ workflows.e2e-spec.ts
β β β βββ tenant-isolation.e2e-spec.ts
β β β βββ trigger.e2e-spec.ts
β β β βββ rate-limit.e2e-spec.ts
β β βββ .env.example # Template variabel environment
β β βββ Dockerfile # Multi-stage build
β βββ web/ # React Frontend
β βββ src/
β β βββ components/ # DAG canvas, run history, health panel
β β βββ pages/ # Login, Dashboard, Workflows, Runs, AI
β β βββ hooks/ # useWorkflowSocket, useAuth
β β βββ lib/ # API client, Socket client
β βββ Dockerfile
βββ .github/
β βββ workflows/
β βββ ci.yml # CI Pipeline
βββ audits/ # Laporan audit berkala perbaikan sistem
βββ docs/
β βββ infra-design.md # Desain infrastruktur AWS
βββ docker-compose.yml # Full stack (6 services)
βββ FlowForge_Final_Report.md # Laporan akhir komprehensif assessment
βββ REVIEW.md # Code review assessment
βββ CHANGELOG-DECISIONS.md # Log keputusan implementasi
βββ 01-PRD-FlowForge.md # Product Requirements Document
βββ 02-INSTALL-GUIDE.md # Panduan instalasi lengkap
βββ 03-AGENT-EXECUTION-GUIDE.md # Panduan eksekusi agent
π Panduan instalasi lebih lengkap tersedia di
02-INSTALL-GUIDE.md
π§ Prasyarat Sistem (klik untuk expand)
| Tool | Versi Minimum | Keterangan |
|---|---|---|
| Node.js | 22.x | Digunakan untuk API & frontend |
| npm | 11.x | Bundled dengan Node 22 |
| Git | 2.x | Version control |
| PostgreSQL | 16 | Database relasional utama |
| MongoDB | 7 | Store log eksekusi |
| Redis | 7 | Queue & rate limiting |
| Docker | 24.x (opsional) | Untuk mode production |
| Docker Compose | v2.x (opsional) | Full stack dengan satu perintah |
Windows: Direkomendasikan menggunakan Laragon yang sudah membundel PostgreSQL, MongoDB, dan Redis. Atau gunakan WSL2 + Docker Desktop.
π₯ Langkah 1: Clone Repository
git clone https://github.com/Himdeunn/flowforge.git
cd flowforgeπ¦ Langkah 2: Install Dependensi
Backend (NestJS API):
cd apps/api
npm installFrontend (React + Vite):
cd apps/web
npm installποΈ Langkah 3: Setup Database (Prisma Migration)
Pastikan PostgreSQL berjalan dan file .env sudah dikonfigurasi (lihat bagian Konfigurasi Environment), lalu jalankan:
cd apps/api
# Jalankan migrasi database
npx prisma migrate dev
# Generate Prisma Client
npx prisma generateVerifikasi tabel berhasil dibuat:
# PostgreSQL (via Laragon atau Docker)
psql -U flowforge -d flowforge -c "\dt"
# Output yang diharapkan: tenants, users, workflow_definitions, workflow_versions, workflow_runs, step_runsBuat file .env di dalam apps/api/ berdasarkan template yang tersedia:
cp apps/api/.env.example apps/api/.envπ Daftar Lengkap Variabel Environment (klik untuk expand)
Edit apps/api/.env dan isi nilai yang sesuai:
# βββ Database (PostgreSQL via Prisma) βββββββββββββββββββββββββββββββββββββββββ
DATABASE_URL="postgresql://flowforge:flowforge@localhost:5432/flowforge"
# βββ MongoDB (Execution Log Store) ββββββββββββββββββββββββββββββββββββββββββββ
MONGODB_URI="mongodb://localhost:27017/flowforge"
# βββ Redis (BullMQ Queue + Rate Limiting) βββββββββββββββββββββββββββββββββββββ
REDIS_HOST="localhost"
REDIS_PORT=6379
# βββ JWT Authentication ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
JWT_ACCESS_SECRET="ganti-dengan-secret-panjang-dan-acak"
JWT_REFRESH_SECRET="ganti-dengan-secret-lain-yang-berbeda"
JWT_ACCESS_EXPIRES_IN="15m"
JWT_REFRESH_EXPIRES_IN="7d"
# βββ Google Gemini AI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Wajib diisi untuk menggunakan fitur AI Natural Language Builder
# Dukung rotasi hingga 5 key untuk menghindari rate limit
GEMINI_API_KEY="masukkan-api-key-anda-disini"
GEMINI_API_KEY_2="api-key-cadangan-2-opsional"
GEMINI_API_KEY_3="api-key-cadangan-3-opsional"
GEMINI_API_KEY_4="api-key-cadangan-4-opsional"
GEMINI_API_KEY_5="api-key-cadangan-5-opsional"
# βββ Aplikasi βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
PORT=3000
NODE_ENV="development"
# βββ Worker Control βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Set true untuk menonaktifkan BullMQ worker (berguna di environment test)
# Di docker-compose: service 'api' β DISABLE_WORKER=true, service 'worker' β DISABLE_WORKER=false
DISABLE_WORKER="false"
β οΈ PENTING: Jangan pernah commit file.envke Git. File ini sudah terdaftar di.gitignore.
π Cara Mendapatkan Gemini API Key
- Buka Google AI Studio
- Login dengan akun Google
- Klik "Create API Key"
- Pilih project Google Cloud yang sudah ada atau buat baru
- Salin API key dan tempelkan ke variabel
GEMINI_API_KEYdi file.env
Untuk menghindari rate limit, bisa menyediakan hingga 5 API key dari akun berbeda. Sistem akan melakukan rotasi otomatis saat satu key terkena limit.
βΆοΈ Langkah 1: Jalankan Backend API (NestJS)
cd apps/api
npm run start:devServer akan berjalan di: http://localhost:3000
Endpoint yang tersedia setelah server berjalan:
- API Base URL:
http://localhost:3000/api/v1 - Swagger UI:
http://localhost:3000/api/docs - Health Check:
http://localhost:3000/api/v1/health
βΆοΈ Langkah 2: Jalankan Frontend Dashboard (React + Vite)
Buka terminal baru, lalu:
cd apps/web
npm run devDashboard akan berjalan di: http://localhost:5173
π΄ Pastikan Service Pendukung Berjalan
Sebelum menjalankan API, pastikan ketiga service ini aktif:
PostgreSQL (Laragon atau Docker):
# Via Docker
docker run --name flowforge-postgres \
-e POSTGRES_USER=flowforge \
-e POSTGRES_PASSWORD=flowforge \
-e POSTGRES_DB=flowforge \
-p 5432:5432 -d postgres:16-alpineRedis (Laragon atau Docker):
# Via Docker
docker run --name flowforge-redis -p 6379:6379 -d redis:7-alpineMongoDB (Laragon atau Docker):
# Via Docker
docker run --name flowforge-mongo -p 27017:27017 -d mongo:7Jika menggunakan Laragon di Windows, cukup klik Start All di Laragon Panel dan pastikan PostgreSQL, MongoDB, dan Redis sudah aktif.
Untuk menjalankan seluruh stack (API + Worker + Frontend + Database) dengan satu perintah:
# Dari root repository
docker compose up --buildπ Detail Service Docker Compose (klik untuk expand)
| Service | Image | Port | Deskripsi |
|---|---|---|---|
api |
flowforge/api (multi-stage) |
3000 | NestJS API (DISABLE_WORKER=true) |
worker |
flowforge/api (multi-stage) |
β | BullMQ Worker (DISABLE_WORKER=false) |
web |
flowforge/web (Nginx) |
5173 | React frontend |
postgres |
postgres:16-alpine |
5432 | Database relasional |
mongodb |
mongo:7 |
27017 | Store log eksekusi |
redis |
redis:7-alpine |
6379 | Queue & rate limiting |
π Verifikasi Docker Compose Berjalan
# Cek semua container berjalan
docker compose ps
# Cek health status
docker compose ps --format "table {{.Name}}\t{{.Status}}"
# Cek log API
docker compose logs api -f
# Test endpoint health
curl http://localhost:3000/api/v1/healthπ Menghentikan & Membersihkan
# Hentikan semua container
docker compose down
# Hentikan dan hapus volume (DATA AKAN HILANG)
docker compose down -v
# Rebuild image
docker compose up --build --force-recreateSwagger UI Interaktif: http://localhost:3000/api/docs
π Auth Endpoints
| Method | Endpoint | Akses | Deskripsi |
|---|---|---|---|
POST |
/api/v1/auth/register |
Publik | Registrasi tenant baru + user Admin |
POST |
/api/v1/auth/login |
Publik | Login, return accessToken + refreshToken |
POST |
/api/v1/auth/refresh |
Publik (refresh token) | Perbarui access token |
POST |
/api/v1/auth/logout |
Semua role | Invalidasi refresh token |
Contoh Register:
curl -X POST http://localhost:3000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"tenantName": "Acme Corp",
"tenantSlug": "acme-corp",
"email": "admin@acme.com",
"password": "Password123!"
}'Contoh Login:
curl -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "admin@acme.com", "password": "Password123!"}'
# Response: { "accessToken": "eyJ...", "refreshToken": "eyJ..." }π Workflow Endpoints
| Method | Endpoint | Role | Deskripsi |
|---|---|---|---|
GET |
/api/v1/workflows |
Semua | List workflow (cursor pagination) |
POST |
/api/v1/workflows |
Admin, Editor | Buat workflow baru dengan definisi DAG |
GET |
/api/v1/workflows/:id |
Semua | Detail workflow + versi aktif |
PUT |
/api/v1/workflows/:id |
Admin, Editor | Update β membuat versi baru otomatis |
DELETE |
/api/v1/workflows/:id |
Admin | Hapus workflow (soft-delete) |
GET |
/api/v1/workflows/:id/versions |
Semua | Riwayat semua versi |
POST |
/api/v1/workflows/:id/versions/:vId/rollback |
Admin, Editor | Rollback ke versi tertentu |
POST |
/api/v1/workflows/:id/trigger |
Admin, Editor | Trigger manual eksekusi |
POST |
/api/v1/webhooks/:token/trigger |
Publik (token) | Trigger via webhook URL |
Contoh Create Workflow:
curl -X POST http://localhost:3000/api/v1/workflows \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Order Processing Pipeline",
"description": "Validate β Process β Notify",
"definitionJson": {
"nodes": [
{ "id": "validate", "type": "http", "config": { "url": "https://api.example.com/validate", "method": "POST" } },
{ "id": "process", "type": "delay", "config": { "durationMs": 2000 } },
{ "id": "notify", "type": "script", "config": { "script": "output.message = \"Order processed: \" + steps.validate.output.orderId;" } }
],
"edges": [
{ "from": "validate", "to": "process" },
{ "from": "process", "to": "notify" }
]
}
}'π Run Endpoints
| Method | Endpoint | Role | Deskripsi |
|---|---|---|---|
GET |
/api/v1/runs |
Semua | List run (filter: ?status=failed&createdAfter=...) |
GET |
/api/v1/runs/:id |
Semua | Detail run + status setiap step |
GET |
/api/v1/runs/:id/logs |
Semua | Log eksekusi per step dari MongoDB |
GET |
/api/v1/runs/health-summary |
Semua | Agregat 24 jam (active, success rate, avg duration) |
π€ AI Endpoint
| Method | Endpoint | Role | Deskripsi |
|---|---|---|---|
POST |
/api/v1/ai/generate-workflow |
Admin, Editor | Deskripsi natural language β DAG JSON |
Contoh Request:
curl -X POST http://localhost:3000/api/v1/ai/generate-workflow \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"description": "Tunggu 3 detik, kemudian ambil data dari https://httpbin.org/get, lalu cek apakah status responsenya 200"}'Contoh Response:
{
"nodes": [
{ "id": "wait", "type": "delay", "config": { "durationMs": 3000 } },
{ "id": "fetch", "type": "http", "config": { "url": "https://httpbin.org/get", "method": "GET" } },
{ "id": "check", "type": "script", "config": { "script": "output.ok = steps.fetch.status === 200;" } }
],
"edges": [
{ "from": "wait", "to": "fetch" },
{ "from": "fetch", "to": "check" }
]
}π Header Rate Limit
Setiap response dari endpoint yang terproteksi menyertakan header berikut:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1720000000
Jika melebihi limit (100 request/menit per tenant):
HTTP 429 Too Many Requests
{"statusCode": 429, "message": "Rate limit exceeded. Try again in X seconds."}
Panduan ini memandu Anda melalui seluruh fitur FlowForge secara end-to-end melalui browser, tanpa perlu alat tambahan.
Tahap 1: Registrasi & Login Akun Tenant
- Buka browser dan navigasi ke
http://localhost:5173 - Pilih tab "Create Account" (Register)
- Isi formulir:
- Organization Name:
Acme Corp - Slug (ID unik):
acme-corp - Email:
admin@acme.com - Password:
Password123!
- Organization Name:
- Klik "Create Account"
- β Berhasil jika: diredirect ke halaman Dashboard dan muncul nama tenant di sidebar
Tahap 2: Melihat System Health Dashboard
- Setelah login, Anda berada di halaman Dashboard
- Perhatikan 4 kartu statistik agregat 24 jam:
- Active Runs β jumlah run yang sedang berjalan
- Success Rate β persentase run berhasil
- Avg Duration β rata-rata durasi eksekusi (ms)
- Total Runs β total run dalam 24 jam terakhir
- Di bagian bawah, terdapat daftar Recent Runs (awalnya kosong)
- β Data di-cache selama 30 detik, refresh otomatis setiap 30 detik
Tahap 3: Membuat Workflow via AI Natural Language Builder
- Klik "AI Builder" di sidebar kiri
- Di kolom "Describe Your Workflow", ketik prompt dalam bahasa alami:
"Tunggu 2 detik, kemudian ambil data dari https://httpbin.org/get, dan gunakan script untuk mengecek apakah status responsenya 200"
- Klik tombol "β¨ Generate DAG"
- Tunggu beberapa detik β sistem akan:
- Mengirim prompt ke Gemini API
- Menerima DAG JSON terstruktur
- Memvalidasi DAG (cycle detection, node type check)
- Menampilkan hasil di panel kanan
- Review nodes yang dihasilkan di panel kanan
- Klik "πΎ Save as Workflow", masukkan nama:
Order Check Pipeline - β Berhasil jika: muncul notifikasi sukses dan workflow tersimpan
Tahap 4: Mengelola & Men-trigger Workflow
- Klik "Workflows" di sidebar
- Anda akan melihat kartu workflow yang baru dibuat dengan info:
- Nama workflow
- Versi aktif (
v1) - Jumlah step
- Klik tombol "βΆ Trigger" pada kartu workflow
- β
Berhasil jika: muncul notifikasi "Run started!" dan status berubah menjadi
queued
Tahap 5: Memantau Eksekusi Real-Time
- Klik "Run History" di sidebar
- Pilih run terbaru dari daftar di panel kiri
- Di panel tengah, grafik DAG akan dirender menggunakan React Flow
- Amati perubahan warna node secara real-time (via WebSocket):
- Abu-abu =
pending(belum berjalan) - Kuning/Amber =
running(sedang berjalan) - Hijau =
success(berhasil) - Merah =
failed(gagal)
- Abu-abu =
- Di bagian bawah, panel "Execution Logs" menampilkan log real-time dari MongoDB:
- Timestamp setiap event
- Durasi per step
- Detail error jika ada
- β Berhasil jika: node berubah warna tanpa perlu refresh halaman
Tahap 6: Membuat Versi Baru & Rollback
- Kembali ke halaman "Workflows"
- Klik "Edit" pada workflow Anda
- Tambahkan step baru atau ubah konfigurasi step yang ada
- Klik "Save" β sistem otomatis membuat versi baru (
v2) - Klik "Versions" untuk melihat riwayat versi
- Klik "Rollback" pada
v1untuk kembali ke versi sebelumnya - β
Berhasil jika:
current_version_idkembali ke versi 1
Tahap 7: Uji Isolasi Multi-Tenant
- Buka tab browser baru / mode incognito
- Navigasi ke
http://localhost:5173dan buat akun tenant baru:- Organization Name:
Beta Company - Slug:
beta-company - Email:
admin@beta.com - Password:
Password123!
- Organization Name:
- Login sebagai
admin@beta.com - β
Berhasil jika: Anda tidak melihat workflow milik
Acme Corpdi daftar workflow tenant Beta β membuktikan isolasi tenant bekerja
Tahap 8: Uji Rate Limiting
Buka terminal dan jalankan perintah berikut (pastikan ACCESS_TOKEN sudah diisi):
# Kirim 105 request berturut-turut
for i in {1..105}; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
http://localhost:3000/api/v1/workflows)
echo "Request $i: $STATUS"
doneβ
Berhasil jika: request ke-101 dan seterusnya mengembalikan HTTP 429 Too Many Requests
Pipeline CI berjalan otomatis setiap push ke branch master, main, atau develop, dan setiap Pull Request ke master/main.
Push / PR
β
βΌ
ββββββββββββ ββββββββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββββββ
β 1. Lint βββββΆβ 2. Unit & E2E Tests βββββΆβ 3. TS Build βββββΆβ 4. Docker Build+Push β
β ESLint β β Jest + Prisma migrateβ β nest build β β ghcr.io/api:sha β
ββββββββββββ ββββββββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββββββ
(PostgreSQL + MongoDB
+ Redis via Services)
π Detail Setiap Job CI (klik untuk expand)
Job 1: Lint
- Node.js 22, npm ci
- Menjalankan ESLint pada seluruh source TypeScript
Job 2: Unit & Integration Tests
- Spin up PostgreSQL 16, MongoDB 7, Redis 7 sebagai GitHub Actions Service
- Jalankan Prisma migrations:
npx prisma migrate deploy - Unit tests:
npm run test - E2E tests:
npm run test:e2e(Auth, Workflows, Tenant Isolation, Trigger, Rate Limit)
Job 3: TypeScript Build
npm run buildβ compile TypeScript kedist/- Memastikan tidak ada type error
Job 4: Docker Build & Push (hanya di master/main)
- Build multi-stage Docker image
- Push ke GitHub Container Registry:
ghcr.io/himdeunn/flowforge/api:latestdanghcr.io/himdeunn/flowforge/api:{sha}
PostgreSQL Schema β 6 Model Relasional (klik untuk expand)
tenants βββββββββββββββββββββββββββββββββββββββββββββββββββ
βββ id uuid (PK)
βββ name varchar(255) NOT NULL
βββ slug varchar(100) UNIQUE NOT NULL
βββ created_at timestamptz DEFAULT now()
users ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββ id uuid (PK)
βββ tenant_id uuid (FK β tenants.id)
βββ email varchar(255) NOT NULL
βββ password_hash varchar(255) NOT NULL
βββ role enum(admin, editor, viewer)
βββ created_at timestamptz DEFAULT now()
βββ UNIQUE(tenant_id, email)
workflow_definitions βββββββββββββββββββββββββββββββββββββββ
βββ id uuid (PK)
βββ tenant_id uuid (FK β tenants.id)
βββ name varchar(255) NOT NULL
βββ description text
βββ current_version_id uuid (FK β workflow_versions.id) nullable
βββ webhook_token varchar(64) UNIQUE nullable
βββ cron_expression varchar(100) nullable
βββ is_active boolean DEFAULT true
βββ created_by uuid (FK β users.id)
βββ created_at timestamptz
βββ updated_at timestamptz
workflow_versions ββββββββββββββββββββββββββββββββββββββββββ
βββ id uuid (PK)
βββ workflow_id uuid (FK β workflow_definitions.id)
βββ version_number integer NOT NULL
βββ definition_json jsonb NOT NULL β DAG: nodes[] + edges[]
βββ created_by uuid (FK β users.id)
βββ created_at timestamptz
βββ UNIQUE(workflow_id, version_number)
workflow_runs ββββββββββββββββββββββββββββββββββββββββββββββ
βββ id uuid (PK)
βββ workflow_id uuid (FK β workflow_definitions.id)
βββ version_id uuid (FK β workflow_versions.id)
βββ tenant_id uuid (FK β tenants.id)
βββ status enum(queued, running, completed, failed, timed_out)
βββ triggered_by enum(manual, cron, webhook)
βββ started_at timestamptz
βββ completed_at timestamptz
βββ INDEX(tenant_id, created_at DESC) β optimasi run history query
step_runs ββββββββββββββββββββββββββββββββββββββββββββββββββ
βββ id uuid (PK)
βββ run_id uuid (FK β workflow_runs.id)
βββ step_id varchar(100) NOT NULL β node.id dari DAG
βββ status enum(pending, running, success, failed, timed_out, skipped)
βββ attempts integer DEFAULT 0
βββ started_at timestamptz
βββ completed_at timestamptz
MongoDB Collection β Execution Logs (klik untuk expand)
Collection: execution_logs
Indexes: { runId: 1, stepId: 1, timestamp: -1 }
Document Schema:
{
"_id": ObjectId,
"runId": string, // FK ke workflow_runs.id
"stepId": string, // node.id dari DAG
"level": "info" | "warn" | "error",
"message": string,
"data": object, // Output/error detail, fleksibel per step type
"attempt": number, // Attempt ke-berapa (untuk retry)
"timestamp": Date
}
Alasan MongoDB untuk log: Volume tinggi, write-heavy, skema output per step-type berbeda-beda (HTTP step punya statusCode/headers, script step punya output, delay step punya durationMs). Tidak ada JOIN kompleks yang diperlukan. Lebih efisien disimpan sebagai dokumen append-only.
Cara Kerja Internal (klik untuk expand)
User Input (deskripsi teks)
β
βΌ
POST /api/v1/ai/generate-workflow
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β AI Service β
β 1. Format prompt dengan template β
β yang menyertakan: β
β - Jenis step yang valid β
β - Format JSON yang diharapkan β
β - Contoh DAG valid β
β 2. Kirim ke Gemini 2.5 Flash β
β (dengan key rotation otomatis) β
β 3. Parse response JSON β
β 4. Validasi dengan DAG Parser β
β (cycle detection, type check) β
β 5. Jika invalid β retry hingga 2x β
β 6. Jika masih invalid β return 422 β
βββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
Response: DAG JSON valid
Tipe Step yang Didukung:
| Tipe | Konfigurasi |
|---|---|
http |
url, method, headers, body |
script |
script (JavaScript sandboxed) |
delay |
durationMs |
condition |
expression (evaluasi steps.stepId.output.field) |
Rotasi API Key Otomatis:
Sistem mengelola array dari GEMINI_API_KEY hingga GEMINI_API_KEY_5. Jika satu key terkena rate limit (HTTP 429 dari Gemini API), sistem otomatis beralih ke key berikutnya, tanpa interupsi bagi pengguna.
Koneksi WebSocket menggunakan Socket.IO di endpoint /ws.
Detail Event & Room Structure (klik untuk expand)
Join Room:
// Client join room untuk memantau run tertentu
socket.emit('join:run', { runId: 'uuid-run-id' });Events yang Dikirim Server:
| Event | Payload | Keterangan |
|---|---|---|
run:started |
{ runId, workflowId, startedAt } |
Run dimulai, status berubah ke running |
step:status_changed |
{ runId, stepId, status, attempt } |
Step berubah status |
run:completed |
{ runId, completedAt, duration } |
Seluruh run berhasil |
run:error |
{ runId, stepId, error, failedAt } |
Run gagal pada step tertentu |
Room Naming Convention:
tenant:{tenantId}:run:{runId}
Contoh: tenant:550e8400-e29b-41d4-a716-446655440000:run:6ba7b810-9dad-11d1-80b4-00c04fd430c8
Contoh Koneksi (JavaScript):
import { io } from 'socket.io-client';
const socket = io('http://localhost:3000', {
auth: { token: 'Bearer eyJ...' }
});
socket.emit('join:run', { runId: 'your-run-id' });
socket.on('step:status_changed', (data) => {
console.log(`Step ${data.stepId} β ${data.status}`);
// Update node color di React Flow
});| Area | Keputusan Saat Ini | Trade-off | Rencana Perbaikan |
|---|---|---|---|
| Script Sandboxing | Node.js vm module |
Ringan, tapi tidak mencegah sandbox breakout sepenuhnya | Migrate ke isolated-vm untuk production security |
| WebSocket Auth | Join room via runId query param |
Mudah diimplementasikan, kurang aman | Validasi Bearer token di WebSocket handshake |
| Refresh Token Storage | Redis (bukan httpOnly cookie) | Lebih mudah di-test, tapi lebih rentan XSS jika client tidak hati-hati | Migrate ke httpOnly cookie + CSRF token |
| Rate Limiting | Manual Redis sliding window | Full kontrol per-tenant, tapi lebih banyak kode | Pertimbangkan @nestjs/throttler dengan custom storage adapter |
| Worker Process | In-process di development | Mudah deploy, tapi tidak bisa scale worker terpisah | Di production: container worker terpisah (sudah ada di docker-compose) |
| Cron Scheduling | Disimpan sebagai cron_expression di DB |
Cron hanya trigger jika API/worker berjalan | Pertimbangkan BullMQ repeatable jobs atau dedicated scheduler |
| Frontend Styling | TailwindCSS + Vanilla CSS | Sangat fleksibel, build step terintegrasi di Vite | Selesai (Diimplementasikan estetika Minimalist Editorial premium) |
Setiap keputusan yang menyimpang dari PRD atau menambahkan detail implementasi yang tidak eksplisit disebutkan di PRD didokumentasikan di CHANGELOG-DECISIONS.md.
Ringkasan Keputusan Utama (klik untuk expand)
| Task | Keputusan | Alasan Singkat |
|---|---|---|
| Task 1.1 | Node.js 22 (bukan 20 sesuai PRD) | Node 22 tersedia di mesin dev, CI diselaraskan ke versi yang sama |
| Task 1.1 | Laragon untuk DB dev (bukan Docker) | Docker WSL2 lambat untuk hot-reload |
| Task 1.2 | JSONB (bukan JSON biasa) | Mendukung indexing dan query lebih efisien |
| Task 2.1 | Refresh token di Redis | Lebih mudah di-invalidate & di-test dibanding httpOnly cookie |
| Task 2.2 | vm module untuk script step |
isolated-vm butuh native binding yang sulit di semua deployment |
| Task 2.4 | BullMQ via host/port config | Menghindari TypeScript conflict dua versi ioredis |
| Task 2.5 | Prisma Proxy (bukan middleware) | Lebih transparan, tidak perlu register per-model |
| Task 2.6 | Redis sliding window manual | @nestjs/throttler tidak support custom key per-tenant |
| Task 3.2 | TailwindCSS + Vanilla CSS | Awalnya menggunakan Vanilla CSS, namun kemudian dimigrasi ke TailwindCSS + Shadcn-inspired Minimalist Editorial untuk visual premium tingkat lanjut |
| Task 3.6 | CI NODE_VERSION dari 20 ke 22 | npm v11 (Node 22) menghasilkan lockfile format berbeda dengan npm v10 (Node 20) |
Lihat CHANGELOG-DECISIONS.md untuk detail penuh setiap keputusan.
FlowForge β Dibangun dengan β€οΈ untuk Technical Assessment Sevima
NestJS Β· React Β· PostgreSQL Β· MongoDB Β· Redis Β· BullMQ Β· Socket.IO Β· Gemini AI