Crate ROM Handbook Archive

Crate ROM Handbook Archive

Project ini dibuat sebagai archive. Tujuan awalnya sederhana: menyimpan sebanyak mungkin data dari ROM Handbook agar tetap bisa dicari dan dibaca kembali. Saat ini web ini belum dimaksudkan sebagai pengganti penuh ROM Handbook, melainkan sebagai tempat preservasi data, pencarian, dan eksplorasi relasi.

Selain data terstruktur, project ini juga menyimpan raw HTML snapshot. Alasannya, tidak semua data mudah diparse sejak awal. Dengan menyimpan HTML mentah, data yang belum sempat diproses masih bisa dibaca ulang dan diparse di masa depan.


💡
Cara Crawler Dan Database Bekerja

Bagian crawler ditulis dengan Python. Crawler mengambil halaman list, membuka halaman detail, lalu memproses HTML untuk mengambil data penting. Data itu kemudian disimpan ke SQLite. Data yang dicrawl tidak hanya satu jenis. Ada crawler untuk cards, equipments, headwears, monsters, skills, mounts, pets, buffs, formulas, jobs, materials, furniture, dan beberapa kategori lain. Setiap jenis data punya struktur sendiri, tapi alurnya mirip: ambil halaman, parse, normalisasi URL asset, simpan data utama, simpan relasi, lalu update index.

Database archive memakai tabel domain seperti cards, equipments, monsters, skills, dan lainnya. Selain itu ada tabel things sebagai index umum agar item dari berbagai tipe bisa dibuka lewat satu endpoint detail.

Tantangan terbesar crawler bukan hanya mengambil data, tapi memahami relasi. Misalnya sebuah card bisa punya craft material, dropped by monster, skill terkait, atau formula. Beberapa sumber detail juga belum selalu jelas. Kalau ada orang yang tahu asal data tertentu, informasi itu bisa membantu archive ini menjadi lebih lengkap.

romhandbook.com mempunyai detail page berbeda untuk semua jenis item dengan slug /things/. Sehingga saya membuat things sebagai index umum. Dengan begitu frontend bisa membuka /things/:id, lalu backend menentukan item itu sebenarnya card, mount, headwear, material, atau tipe lain.
TABLE_CONFIGS = [
    {"table": "cards", "type": "card"},
    {"table": "equipments", "type": "equipment"},
    {"table": "headwears", "type": "headwear"},
    {"table": "mounts", "type": "mount"},
    {"table": "pet_eggs", "type": "pet_egg"},
    {"table": "crafting_materials", "type": "crafting_material"},
]
conn.execute(
    """
    INSERT INTO things (
        id,
        type,
        name,
        image,
        detail_url
    )
    VALUES (?, ?, ?, ?, ?)

    ON CONFLICT(id) DO UPDATE SET
        type = excluded.type,
        name = excluded.name,
        image = excluded.image,
        detail_url = excluded.detail_url
    """,
    (
        item["id"],
        item["type"],
        item["name"],
        item["image"],
        item["detail_url"],
    ),
)
💡
Backend API Dan Frontend UI

Backend dibuat dengan Go dan Gin. Backend membaca archive database dan menyajikannya sebagai API. Ada endpoint untuk list data, detail data, search, random snapshot, formula graph, dan data health.

Backend juga punya database aplikasi terpisah untuk fitur komunitas seperti login Discord, session, profile, comments, reports, feature requests, points, dan rank. Jadi secara konsep ada dua database: satu untuk data archive, satu untuk data aplikasi.

Frontend dibuat dengan Next.js, TypeScript, dan Tailwind. UI-nya fokus untuk browsing data archive: list page, detail page, search, relation section, raw HTML viewer, formula viewer, graph explorer, comments, profile, dan admin/data health dashboard.

Beberapa fitur penting frontend:

  • halaman archive untuk card, equipment, headwear, monster, pet, mount, skill, buff, formula, dan job;
  • halaman detail unified lewat things;
  • related formula widget;
  • raw HTML viewer untuk melihat snapshot asli;
  • comments dan profile;
  • archive notice popup untuk menjelaskan tujuan project kepada pengunjung.

Untuk request biasa, rate limit memakai IP. Tapi untuk fitur login atau komunitas, limiter bisa memakai user session. Kalau user belum login, sistem fallback ke IP.
func (limiter *RateLimiter) keyForRequest(
    c *gin.Context,
    group string,
    identity IdentityType,
    appDB *sql.DB,
) string {
    switch identity {
    case IdentityUser, IdentityAdmin:
        if appDB != nil {
            userID, err := session.LookupUserID(c, appDB)

            if err == nil && userID != "" {
                return fmt.Sprintf(
                    "%s:%s:%s",
                    identity,
                    group,
                    userID,
                )
            }
        }
    }

    return fmt.Sprintf(
        "ip:%s:%s",
        group,
        clientIP(c),
    )
}
Karena archive menyimpan raw HTML snapshot, asset path lama perlu ditulis ulang agar tetap bisa dirender dari asset CDN. Dengan cara ini, snapshot halaman lama masih bisa dibuka di frontend modern.
function rewriteArchiveAssetUrls(
    html: string
) {
    const assetBaseUrl =
        process.env.NEXT_PUBLIC_ASSET_BASE_URL

    if (!assetBaseUrl) {
        return html
    }

    const normalizedBaseUrl =
        assetBaseUrl.replace(/\/$/, "")

    return html
        .replaceAll(
            'src="/assets/',
            `src="${normalizedBaseUrl}/assets/`
        )
        .replaceAll(
            'href="/assets/',
            `href="${normalizedBaseUrl}/assets/`
        )
}
Snapshot HTML ditampilkan lewat iframe, bukan langsung inject ke DOM utama. Ini membuat tampilan archive lebih terisolasi dari aplikasi utama.
<iframe
    srcDoc={fullHtml}
    sandbox="
        allow-same-origin
    "
    className="
        w-full
        h-[1200px]
        bg-white
    "
/>
💡
Deployment, Kendala, Dan Roadmap

Frontend dan static assets dijalankan lewat Cloudflare, sedangkan backend berjalan di server sendiri. Karena backend self-hosted dan data cukup banyak, beberapa halaman bisa terasa lambat tergantung koneksi, cold start, query, atau beban server.

Kendala utama project ini:

  • data ROM Handbook besar dan formatnya tidak selalu konsisten;
  • beberapa relasi belum sempurna;
  • sumber detail data tertentu masih perlu dilacak;
  • asset dan raw HTML perlu dijaga agar tetap bisa dirender;
  • performa backend perlu terus dipantau karena berjalan di server sendiri.

Roadmap ke depan:

  • memperbaiki kualitas relasi data;
  • menambah sumber detail jika ditemukan;
  • meningkatkan search dan formula graph;
  • optimasi asset/CDN;
  • Docker support;
  • AI assistant untuk tanya jawab archive;
  • kontribusi komunitas lewat bug report, feature request, atau GitHub.

Project ini bukan dibuat untuk mengambil alih karya pihak lain, tapi untuk menjaga agar data yang sudah terlanjur penting tidak hilang begitu saja.