Situs ini dikemudikan dua tombol, seperti mesin pemiliknya. Membaca: j dan k menggulir, d dan u setengah layar, gg ke atas, G ke bawah, H dan L pindah jendela, ? membuka panduan. Spasi adalah kunci leader Neovim dan mengurus isi: Spasi lalu h beranda, r riset, p proyek, g gear, a tentang, / tag, atau Spasi lalu angka untuk melompat ke jendela bernomor itu. Home adalah prefix tmux dan mengurus jendela: Home lalu c membuka terminal, Home lalu & menutup jendela, Home lalu spasi ke jendela berikutnya. Saat fokus ada di papan ketik, h j k l memindahkan pilihan antar tombol dan Enter membukanya.

RisetR TentangA Gear — G untuk gear — papan ketik, terminal, editor, homelab.G
    ProyekP Tag — / seperti di vim: cari./
    ×
    Menu

    Marcom Services: API Bun + Elysia untuk Dashboard Marcom

    Backend dashboard Marcom — Elysia di atas runtime Bun, MySQL, JWT, Socket.IO di port terpisah, dan cron kedaluwarsa yang dipanggil lewat HTTP.

    Ini backend yang dipakai dashboard Marcom. Satu proses Bun yang menyajikan REST API dan, di port berbeda, server Socket.IO.

    Kenapa Bun + Elysia

    Yang dicari waktu itu: TypeScript tanpa langkah build, startup cepat di container kecil, dan router yang tidak butuh belasan paket pendukung. Elysia di atas Bun memberi ketiganya — bun run --watch src/index.ts untuk dev, bun src/index.ts untuk produksi, tidak ada tsc di antaranya.

    Susunannya rata, satu controller per entitas:

    const app = new Elysia()
      .use(corsMiddleware)
      .use(errorHandler)
      .use(authController)
      .use(brandController)
      .use(clusterController)
      .use(fiturController)
      .use(jenisController)
      .use(materiController)
      .use(usersController)
      .use(fileRoutes)
      .use(cronController);

    Di bawah controller ada lapisan services/ yang memegang SQL dan models/ untuk bentuk datanya. Query ditulis langsung dengan mysql2 — tidak ada ORM. Untuk skema yang hanya lima tabel inti dengan empat foreign key, ORM cuma menambah lapisan yang harus dihafal.

    Dua port, satu proses

    HTTP API di PORT (default 5001); Socket.IO dinyalakan di atas createServer() sendiri di SOCKET_IO (default 5002). Memisahkan port bikin proxy di depannya jauh lebih gampang diatur: yang satu butuh upgrade WebSocket, yang satu tidak.

    Rate limit yang terpaksa dimatikan

    Di src/index.ts ada blok yang sengaja dikomentari:

    // Global rate limit: temporarily disabled to fix body consumption issue
    // .use(rateLimit({ duration: 60_000, max: 1000, ... }))

    Middleware rate-limit-nya membaca request body untuk keperluannya sendiri, dan setelah itu handler di belakangnya menerima body yang sudah habis dibaca — request POST jadi gagal dengan pesan validasi yang menyesatkan. Butuh waktu lama untuk sadar bahwa yang salah bukan validasinya. Sampai sekarang blok itu tetap dikomentari, bukan dihapus, supaya alasannya tidak hilang.

    Bagian yang lain

    • Auth — JWT, dengan authMiddleware dan rolesMiddleware terpisah supaya “siapa kamu” dan “boleh apa” tidak bercampur di satu tempat.
    • CroncronController menjaga endpoint yang menjalankan pemeriksaan materi kedaluwarsa, dilindungi cronAuth (token sendiri, bukan JWT pengguna). Penjadwalnya di luar; servicenya tetap satu binary.
    • Emailnodemailer lewat emailService, dipakai notificationService untuk mengirim daftar materi yang akan habis.
    • UploadfileRoutes menulis ke UPLOAD_DIR (default ./uploads), foldernya dibuat sendiri saat boot.
    • XSSelysia-xss dipasang global; input dari form materi memang bisa berisi teks bebas.
    • Seedseeder.ts untuk data contoh, seeder-prod.ts terpisah untuk data awal yang benar-benar dipakai. Memisahkan keduanya lebih baik daripada satu skrip dengan flag --production yang suatu hari akan dijalankan tanpa flag.
    id en
    rss gh in