From a3ce7f8008429acf08eb25a6d5f7340b7b4166a2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tristan=20Dani=C3=ABl=20Maat?= <tm@tlater.net>
Date: Sat, 15 Mar 2025 03:02:31 +0800
Subject: [PATCH] feat(card_db): Import card database from edopro GitHub repo

---
 Cargo.lock                                    | 295 +++++++++++++++++-
 Cargo.toml                                    |  11 +-
 index.html                                    |   5 +-
 .../2025-03-13-161839_create_cards/down.sql   |   1 -
 .../2025-03-13-161839_create_cards/up.sql     |   7 -
 src/bin/database_worker.rs                    |  14 +
 src/components/card.rs                        |   8 +-
 src/database.rs                               |  78 ++---
 src/database/models.rs                        |  95 ++++--
 src/database/schema.rs                        |  43 ++-
 src/database/worker.rs                        | 140 +++++++++
 src/lib.rs                                    |   3 +-
 src/main.rs                                   |  39 ++-
 src/utils.rs                                  |  60 ++++
 14 files changed, 691 insertions(+), 108 deletions(-)
 delete mode 100644 migrations/2025-03-13-161839_create_cards/down.sql
 delete mode 100644 migrations/2025-03-13-161839_create_cards/up.sql
 create mode 100644 src/bin/database_worker.rs
 create mode 100644 src/database/worker.rs
 create mode 100644 src/utils.rs

diff --git a/Cargo.lock b/Cargo.lock
index 4a701ef..4a72acf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -122,6 +122,15 @@ version = "0.22.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
 
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "bitflags"
 version = "2.9.0"
@@ -403,15 +412,22 @@ dependencies = [
  "diesel-derive-enum",
  "diesel_migrations",
  "futures",
+ "futures-util",
+ "gloo",
+ "gloo-net 0.6.0",
  "leptos",
+ "log",
  "serde",
  "serde-wasm-bindgen",
  "serde_json",
  "sqlite-wasm-rs",
  "thiserror 2.0.12",
+ "thiserror-ext",
+ "time",
  "url",
  "wasm-bindgen",
  "wasm-bindgen-futures",
+ "wasm-logger",
 ]
 
 [[package]]
@@ -624,6 +640,109 @@ version = "0.31.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
 
+[[package]]
+name = "gloo"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d15282ece24eaf4bd338d73ef580c6714c8615155c4190c781290ee3fa0fd372"
+dependencies = [
+ "gloo-console",
+ "gloo-dialogs",
+ "gloo-events",
+ "gloo-file",
+ "gloo-history",
+ "gloo-net 0.5.0",
+ "gloo-render",
+ "gloo-storage",
+ "gloo-timers",
+ "gloo-utils",
+ "gloo-worker",
+]
+
+[[package]]
+name = "gloo-console"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a17868f56b4a24f677b17c8cb69958385102fa879418052d60b50bc1727e261"
+dependencies = [
+ "gloo-utils",
+ "js-sys",
+ "serde",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-dialogs"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4748e10122b01435750ff530095b1217cf6546173459448b83913ebe7815df"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-events"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27c26fb45f7c385ba980f5fa87ac677e363949e065a083722697ef1b2cc91e41"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-file"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f"
+dependencies = [
+ "futures-channel",
+ "gloo-events",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-history"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6"
+dependencies = [
+ "getrandom 0.2.15",
+ "gloo-events",
+ "gloo-utils",
+ "serde",
+ "serde-wasm-bindgen",
+ "serde_urlencoded",
+ "thiserror 1.0.69",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-net"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-sink",
+ "gloo-utils",
+ "http 0.2.12",
+ "js-sys",
+ "pin-project",
+ "serde",
+ "serde_json",
+ "thiserror 1.0.69",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
 [[package]]
 name = "gloo-net"
 version = "0.6.0"
@@ -634,7 +753,7 @@ dependencies = [
  "futures-core",
  "futures-sink",
  "gloo-utils",
- "http",
+ "http 1.2.0",
  "js-sys",
  "pin-project",
  "serde",
@@ -645,6 +764,43 @@ dependencies = [
  "web-sys",
 ]
 
+[[package]]
+name = "gloo-render"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56008b6744713a8e8d98ac3dcb7d06543d5662358c9c805b4ce2167ad4649833"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-storage"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a"
+dependencies = [
+ "gloo-utils",
+ "js-sys",
+ "serde",
+ "serde_json",
+ "thiserror 1.0.69",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-timers"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "js-sys",
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "gloo-utils"
 version = "0.2.0"
@@ -658,6 +814,37 @@ dependencies = [
  "web-sys",
 ]
 
+[[package]]
+name = "gloo-worker"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "085f262d7604911c8150162529cefab3782e91adb20202e8658f7275d2aefe5d"
+dependencies = [
+ "bincode",
+ "futures",
+ "gloo-utils",
+ "gloo-worker-macros",
+ "js-sys",
+ "pinned",
+ "serde",
+ "thiserror 1.0.69",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-worker-macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "956caa58d4857bc9941749d55e4bd3000032d8212762586fa5705632967140e7"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "guardian"
 version = "1.3.0"
@@ -703,6 +890,17 @@ dependencies = [
  "utf8-width",
 ]
 
+[[package]]
+name = "http"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
 [[package]]
 name = "http"
 version = "1.2.0"
@@ -1287,6 +1485,17 @@ version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 
+[[package]]
+name = "pinned"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b"
+dependencies = [
+ "futures",
+ "rustversion",
+ "thiserror 1.0.69",
+]
+
 [[package]]
 name = "pkg-config"
 version = "0.3.32"
@@ -1309,6 +1518,16 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "proc-macro-crate"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
+dependencies = [
+ "once_cell",
+ "toml_edit 0.19.15",
+]
+
 [[package]]
 name = "proc-macro-error-attr2"
 version = "2.0.0"
@@ -1608,6 +1827,18 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
 [[package]]
 name = "server_fn"
 version = "0.7.7"
@@ -1618,8 +1849,8 @@ dependencies = [
  "const_format",
  "dashmap",
  "futures",
- "gloo-net",
- "http",
+ "gloo-net 0.6.0",
+ "http 1.2.0",
  "js-sys",
  "once_cell",
  "pin-project-lite",
@@ -1801,6 +2032,28 @@ dependencies = [
  "thiserror-impl 2.0.12",
 ]
 
+[[package]]
+name = "thiserror-ext"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef4323942237f7cc071061f2c5f0db919e6053c2cdf58c6bc974883073429737"
+dependencies = [
+ "thiserror 1.0.69",
+ "thiserror-ext-derive",
+]
+
+[[package]]
+name = "thiserror-ext-derive"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96541747c50e6c73e094737938f4f5dfaf50c48a31adff4197a3e2a481371674"
+dependencies = [
+ "either",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "thiserror-impl"
 version = "1.0.69"
@@ -1892,7 +2145,7 @@ dependencies = [
  "serde",
  "serde_spanned",
  "toml_datetime",
- "toml_edit",
+ "toml_edit 0.22.24",
 ]
 
 [[package]]
@@ -1904,6 +2157,17 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow 0.5.40",
+]
+
 [[package]]
 name = "toml_edit"
 version = "0.22.24"
@@ -1914,7 +2178,7 @@ dependencies = [
  "serde",
  "serde_spanned",
  "toml_datetime",
- "winnow",
+ "winnow 0.7.3",
 ]
 
 [[package]]
@@ -2063,6 +2327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
 dependencies = [
  "cfg-if",
+ "futures-core",
  "js-sys",
  "once_cell",
  "wasm-bindgen",
@@ -2101,6 +2366,17 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "wasm-logger"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718"
+dependencies = [
+ "log",
+ "wasm-bindgen",
+ "web-sys",
+]
+
 [[package]]
 name = "wasm-streams"
 version = "0.4.2"
@@ -2206,6 +2482,15 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
+[[package]]
+name = "winnow"
+version = "0.5.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "winnow"
 version = "0.7.3"
diff --git a/Cargo.toml b/Cargo.toml
index 333117c..7a33316 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,9 +15,14 @@ serde_json = "1.0.140"
 thiserror = "2.0.12"
 url = "2.5.4"
 wasm-bindgen = "0.2.100"
-wasm-bindgen-futures = "0.4.50"
+wasm-bindgen-futures = { version = "0.4.50", features = ["futures-core-03-stream"] }
 diesel-derive-enum = { version = "2.1.0", features = ["sqlite"] }
 diesel_migrations = { git = "https://github.com/diesel-rs/diesel.git", features = ["sqlite"] }
-
-[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
+time = { version = "0.3.39", features = ["parsing"] }
+wasm-logger = "0.2.0"
+log = "0.4.26"
 sqlite-wasm-rs = { version = ">=0.3.0, <0.4.0" , default-features = false, features = ["precompiled"]}
+thiserror-ext = "0.2.1"
+futures-util = "0.3.31"
+gloo = { version = "0.11.0", features = ["futures"] }
+gloo-net = { version = "0.6.0" }
diff --git a/index.html b/index.html
index bef68ca..1cf7e63 100644
--- a/index.html
+++ b/index.html
@@ -1,5 +1,8 @@
 <!DOCTYPE html>
 <html>
-  <head></head>
+  <head>
+    <link data-trunk rel="rust" data-type="worker" data-bin="database_worker" data-weak-refs data-loader-shim />
+    <link data-trunk rel="rust" data-bin="draft-manager" data-weak-refs />
+  </head>
   <body></boy>
 </html>
diff --git a/migrations/2025-03-13-161839_create_cards/down.sql b/migrations/2025-03-13-161839_create_cards/down.sql
deleted file mode 100644
index d9a93fe..0000000
--- a/migrations/2025-03-13-161839_create_cards/down.sql
+++ /dev/null
@@ -1 +0,0 @@
--- This file should undo anything in `up.sql`
diff --git a/migrations/2025-03-13-161839_create_cards/up.sql b/migrations/2025-03-13-161839_create_cards/up.sql
deleted file mode 100644
index 32fc4a6..0000000
--- a/migrations/2025-03-13-161839_create_cards/up.sql
+++ /dev/null
@@ -1,7 +0,0 @@
-CREATE TABLE cards (
-       password PRIMARY KEY,
-       name VARCHAR NOT NULL,
-       description TEXT NOT NULL,
-       frame TEXT CHECK(frame IN ('normal', 'effect', 'ritual', 'fusion', 'synchro', 'xyz', 'link', 'normal_pendulum', 'effect_pendulum', 'ritual_pendulum', 'fusion_pendulum', 'synchro_pendulum', 'xyz_pendulum', 'spell', 'trap', 'token')) NOT NULL,
-       image TEXT
-)
diff --git a/src/bin/database_worker.rs b/src/bin/database_worker.rs
new file mode 100644
index 0000000..4cecf83
--- /dev/null
+++ b/src/bin/database_worker.rs
@@ -0,0 +1,14 @@
+use draft_manager::database::BrowserDatabase;
+
+fn main() {
+    console_error_panic_hook::set_once();
+    wasm_logger::init(wasm_logger::Config::default());
+
+    wasm_bindgen_futures::spawn_local(async {
+        let a = BrowserDatabase::init_worker().await;
+
+        if let Err(e) = a {
+            log::error!("{:?}", e);
+        }
+    })
+}
diff --git a/src/components/card.rs b/src/components/card.rs
index 29fd136..27d02aa 100644
--- a/src/components/card.rs
+++ b/src/components/card.rs
@@ -1,13 +1,13 @@
 use leptos::prelude::*;
 
-use crate::database::models::CardDetails;
+use crate::database::models::CardData;
 
 #[component]
-pub fn Card(details: Option<CardDetails>) -> impl IntoView {
+pub fn Card(details: Option<CardData>) -> impl IntoView {
     match details {
         Some(details) => view! {
-            <p>{details.name}</p>
-            <span style="white-space: pre-line">{details.description}</span>
+            <p>{details.id}</p>
+            <span style="white-space: pre-line">{details.level}</span>
         }
         .into_any(),
         None => view! { <p>"Placeholder"</p> }.into_any(),
diff --git a/src/database.rs b/src/database.rs
index 1d7f06d..a4c652c 100644
--- a/src/database.rs
+++ b/src/database.rs
@@ -1,54 +1,60 @@
-use crate::database::{self, models::FrameType, schema::cards};
-use database::models::CardDetails;
-use diesel::{prelude::*, Connection};
-use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
-use thiserror::Error;
+use gloo::worker::{Registrable, Spawnable, WorkerBridge};
 
 pub mod models;
 pub mod schema;
+mod worker;
 
-const DB_URL: &str = "cards.db";
-const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
+use sqlite_wasm_rs::export::{install_opfs_sahpool, OpfsSAHError};
+use thiserror_ext::AsReport;
+use worker::{DatabaseWorker, Query};
 
-pub struct BrowserDatabase(SqliteConnection);
+use crate::utils::github_fetch_file;
+
+pub struct BrowserDatabase {
+    worker: WorkerBridge<DatabaseWorker>,
+}
 
 impl BrowserDatabase {
-    pub fn open() -> Result<Self> {
-        let mut connection = SqliteConnection::establish(DB_URL)?;
-        connection.run_pending_migrations(MIGRATIONS).unwrap();
-        Ok(Self(connection))
+    pub fn new() -> Self {
+        let bridge = DatabaseWorker::spawner()
+            .callback(move |o| {
+                log::info!("{:?}", o);
+            })
+            .spawn_with_loader("/database_worker_loader.js");
+
+        log::debug!("Shoulda spawned a worker");
+
+        Self { worker: bridge }
     }
 
-    pub async fn get_card(&mut self, password: i32) -> Result<Option<CardDetails>> {
-        match cards::dsl::cards.find(password).first(&mut self.0) {
-            Ok(details) => Ok(Some(details)),
-            Err(diesel::result::Error::NotFound) => Ok(None),
-            Err(other) => Err(other.into()),
-        }
+    pub fn send_message(&self) {
+        self.worker.send(Query::ByPassword(1861629));
     }
 
-    pub fn load_data(&mut self) -> Result<()> {
-        let decode_talker = CardDetails {
-            password: 1861629,
-            name: "Decode Talker".to_owned(),
-            description: "2+ Effect Monsters\r\nGains 500 ATK for each monster it points to. When your opponent activates a card or effect that targets a card(s) you control (Quick Effect): You can Tribute 1 monster this card points to; negate the activation, and if you do, destroy that card.".to_owned(),
-            frame: FrameType::Link,
-            image: "https://images.ygoprodeck.com/images/cards/1861629.jpg".to_owned()
+    /// Initialize the database worker.
+    ///
+    /// Must be executed in a worker script, not on the main thread.
+    pub async fn init_worker() -> Result<(), OpfsSAHError> {
+        log::debug!("Initializing database worker...");
+        let sah_pool_util = install_opfs_sahpool(None, false).await?;
+
+        let babel_cdb = match github_fetch_file("ProjectIgnis", "BabelCDB", "cards.cdb").await {
+            Ok(cdb) => cdb,
+            Err(err) => {
+                log::error!("{}", err.to_report_string());
+                panic!("{:?}", err);
+            }
         };
 
-        diesel::insert_into(cards::table)
-            .values(decode_talker)
-            .execute(&mut self.0)?;
-
+        sah_pool_util.import_db("/cards.db", &babel_cdb)?;
+        DatabaseWorker::registrar().register();
+        log::debug!("Database worker set up!");
         Ok(())
     }
 }
 
-#[derive(Debug, Error)]
-pub enum BrowserDatabaseError {
-    #[error("failed to connect to database")]
-    DieselConnectionError(#[from] diesel::ConnectionError),
-    #[error("failed to query database")]
-    DieselQueryError(#[from] diesel::result::Error),
+impl Default for BrowserDatabase {
+    fn default() -> Self {
+        Self::new()
+    }
 }
-type Result<T> = std::result::Result<T, BrowserDatabaseError>;
diff --git a/src/database/models.rs b/src/database/models.rs
index f98978a..456e4c1 100644
--- a/src/database/models.rs
+++ b/src/database/models.rs
@@ -1,38 +1,79 @@
 use diesel::prelude::*;
 use serde::{Deserialize, Serialize};
 
-use crate::database::schema::cards;
+use crate::database::schema::datas;
+use crate::database::schema::texts;
 
-#[derive(Clone, Debug, Serialize, Deserialize, Insertable, Selectable, Queryable)]
-#[diesel(table_name = cards)]
+#[derive(Debug, Selectable, Queryable)]
+#[diesel(table_name = datas)]
 #[diesel(check_for_backend(diesel::sqlite::Sqlite))]
+pub struct CardData {
+    pub id: i32,
+    pub ot: i32,
+    pub alias: i32,
+    pub setcode: i32,
+    pub card_type: i32,
+    pub atk: i32,
+    pub def: i32,
+    pub level: i32,
+    pub race: i32,
+    pub attribute: i32,
+    pub category: i32,
+}
+
+#[derive(Debug, Associations, Selectable, Queryable)]
+#[diesel(table_name = texts)]
+#[diesel(belongs_to(CardData, foreign_key = id))]
+#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
+pub struct CardTexts {
+    pub id: i32,
+    pub name: String,
+    pub desc: String,
+    pub str1: String,
+    pub str2: String,
+    pub str3: String,
+    pub str4: String,
+    pub str5: String,
+    pub str6: String,
+    pub str7: String,
+    pub str8: String,
+    pub str9: String,
+    pub str10: String,
+    pub str11: String,
+    pub str12: String,
+    pub str13: String,
+    pub str14: String,
+    pub str15: String,
+    pub str16: String,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct CardDetails {
     pub password: i32,
-
     pub name: String,
-    pub description: String,
-
-    pub frame: FrameType,
-    pub image: String,
+    pub desc: String,
+    pub card_type: i32,
+    pub atk: i32,
+    pub def: i32,
+    pub level: i32,
+    pub race: i32,
+    pub attribute: i32,
+    pub category: i32,
 }
 
-#[derive(Debug, Clone, Serialize, Deserialize, diesel_derive_enum::DbEnum)]
-#[serde(rename_all = "snake_case")]
-pub enum FrameType {
-    Normal,
-    Effect,
-    Ritual,
-    Fusion,
-    Synchro,
-    Xyz,
-    Link,
-    NormalPendulum,
-    EffectPendulum,
-    RitualPendulum,
-    FusionPendulum,
-    SynchroPendulum,
-    XyzPendulum,
-    Spell,
-    Trap,
-    Token,
+impl CardDetails {
+    pub fn from_data(data: CardData, texts: CardTexts) -> Self {
+        Self {
+            password: data.id,
+            name: texts.name,
+            desc: texts.desc,
+            card_type: data.card_type,
+            atk: data.atk,
+            def: data.def,
+            level: data.level,
+            race: data.race,
+            attribute: data.attribute,
+            category: data.category,
+        }
+    }
 }
diff --git a/src/database/schema.rs b/src/database/schema.rs
index 6d77694..6965958 100644
--- a/src/database/schema.rs
+++ b/src/database/schema.rs
@@ -1,9 +1,40 @@
 diesel::table! {
-    cards (password) {
-        password -> Integer,
-        name -> VarChar,
-        description -> Text,
-        frame -> crate::database::models::FrameTypeMapping,
-        image -> Text,
+    datas {
+        id -> Integer,
+        ot -> Integer,
+        alias -> Integer,
+        setcode -> Integer,
+        #[sql_name = "type"]
+        card_type -> Integer,
+        atk -> Integer,
+        def -> Integer,
+        level -> Integer,
+        race -> Integer,
+        attribute -> Integer,
+        category -> Integer,
+    }
+}
+
+diesel::table! {
+    texts {
+        id -> Integer,
+        name -> Text,
+        desc -> Text,
+        str1 -> Text,
+        str2 -> Text,
+        str3 -> Text,
+        str4 -> Text,
+        str5 -> Text,
+        str6 -> Text,
+        str7 -> Text,
+        str8 -> Text,
+        str9 -> Text,
+        str10 -> Text,
+        str11 -> Text,
+        str12 -> Text,
+        str13 -> Text,
+        str14 -> Text,
+        str15 -> Text,
+        str16 -> Text,
     }
 }
diff --git a/src/database/worker.rs b/src/database/worker.rs
new file mode 100644
index 0000000..9d6916c
--- /dev/null
+++ b/src/database/worker.rs
@@ -0,0 +1,140 @@
+use diesel::{prelude::*, Connection};
+use futures::TryFutureExt;
+use gloo::worker::{HandlerId, Worker, WorkerScope};
+use serde::{Deserialize, Serialize};
+use thiserror::Error;
+use thiserror_ext::AsReport;
+use wasm_bindgen_futures::spawn_local;
+
+use super::{
+    models::CardDetails,
+    schema::{datas, texts},
+};
+
+const DB_URL: &str = "file:/cards.db?vfs=opfs-sahpool&mode=ro";
+
+#[derive(Serialize, Deserialize)]
+pub enum Query {
+    ByPassword(i32),
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub enum Response {
+    ByPassword(Option<CardDetails>),
+    Error(String),
+}
+
+pub struct DatabaseWorker {}
+
+impl DatabaseWorker {
+    async fn run_query(query: &Query) -> Result<Response> {
+        let res = match query {
+            Query::ByPassword(pw) => Response::ByPassword(Self::get_card(*pw).await?),
+        };
+
+        Ok(res)
+    }
+
+    /// Open a connection to the database.
+    ///
+    /// We do not hold a connection in memory because that requires
+    /// lots of awkward multi-thread data management.
+    fn open_connection() -> Result<SqliteConnection> {
+        let connection = SqliteConnection::establish(DB_URL)?;
+        Ok(connection)
+    }
+
+    async fn get_card(password: i32) -> Result<Option<CardDetails>> {
+        let mut connection = Self::open_connection()?;
+
+        let res = {
+            let data = datas::dsl::datas.find(password).first(&mut connection)?;
+            let texts = texts::dsl::texts.find(password).first(&mut connection)?;
+
+            Ok((data, texts))
+        };
+
+        let Some((data, texts)) = match res {
+            Ok(details) => Ok(Some(details)),
+            Err(diesel::result::Error::NotFound) => Ok(None),
+            Err(other) => Err(other),
+        }?
+        else {
+            return Ok(None);
+        };
+
+        //  match datas::dsl::datas.find(password).first(&mut connection) {
+        //     Ok(details) => Ok(Some(details)),
+        //     Err(diesel::result::Error::NotFound) => Ok(None),
+        //     Err(other) => Err(other.into()),
+        // };
+
+        Ok(Some(CardDetails::from_data(data, texts)))
+    }
+
+    // pub async fn load_card_data(&mut self) -> Result<()> {
+    //     let last_updated =
+    //         utils::github_fetch_last_updated("ProjectIgnis", "BabelCDB", "cards.cdb").await?;
+    //     if last_updated > time::OffsetDateTime::UNIX_EPOCH {
+    //         let card_data =
+    //             utils::github_fetch_file("ProjectIgnis", "BabelCDB", "cards.cdb").await?;
+    //         self.opfs_util.import_db("cards.cdb", &card_data)?;
+    //     }
+
+    //     let decode_talker = CardDetails {
+    //         password: 1861629,
+    //         name: "Decode Talker".to_owned(),
+    //         description: "2+ Effect Monsters\r\nGains 500 ATK for each monster it points to. When your opponent activates a card or effect that targets a card(s) you control (Quick Effect): You can Tribute 1 monster this card points to; negate the activation, and if you do, destroy that card.".to_owned(),
+    //         frame: FrameType::Link,
+    //         image: "https://images.ygoprodeck.com/images/cards/1861629.jpg".to_owned()
+    //     };
+
+    //     diesel::insert_into(cards::table)
+    //         .values(decode_talker)
+    //         .execute(&mut self.connection)?;
+
+    //     Ok(())
+    // }
+}
+
+impl Worker for DatabaseWorker {
+    type Input = Query;
+    type Message = ();
+    type Output = std::result::Result<Response, String>;
+
+    fn create(_scope: &WorkerScope<Self>) -> Self {
+        Self {}
+    }
+
+    fn update(&mut self, _scope: &WorkerScope<Self>, _msg: Self::Message) {
+        log::debug!("Unimplemented!");
+    }
+
+    fn received(&mut self, scope: &WorkerScope<Self>, query: Self::Input, id: HandlerId) {
+        let scope = scope.clone();
+
+        spawn_local(async move {
+            let res = DatabaseWorker::run_query(&query)
+                .map_err(|err| {
+                    log::error!("{}", err.to_report_string_pretty());
+                    format!("{}", err.as_report())
+                })
+                .await;
+
+            scope.respond(id, res);
+        })
+    }
+}
+
+#[derive(Debug, Error)]
+pub enum BrowserDatabaseError {
+    // #[error("failed to set up browser database storage")]
+    // VFSError(#[from] sqlite_wasm_rs::export::OpfsSAHError),
+    // #[error("could not fetch updated card database")]
+    // CardDbFetchError(#[from] GithubError),
+    #[error("failed to connect to database")]
+    DieselConnectionError(#[from] diesel::ConnectionError),
+    #[error("failed to query database")]
+    DieselQueryError(#[from] diesel::result::Error),
+}
+type Result<T> = std::result::Result<T, BrowserDatabaseError>;
diff --git a/src/lib.rs b/src/lib.rs
index f9cf4eb..20e1984 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,4 @@
 pub mod components;
-pub mod draft_list;
 pub mod database;
+pub mod draft_list;
+pub mod utils;
diff --git a/src/main.rs b/src/main.rs
index 7ebb1f1..3021afa 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,32 +1,37 @@
-use draft_manager::{components::card::Card, database::BrowserDatabase};
+use std::time::Duration;
+
+use draft_manager::database::BrowserDatabase;
 use leptos::prelude::*;
+use wasm_bindgen_futures::spawn_local;
 
 fn main() {
     console_error_panic_hook::set_once();
+    wasm_logger::init(wasm_logger::Config::default());
+
+    log::debug!("Startin app database");
+    let db = BrowserDatabase::new();
+    log::debug!("Database initialized");
 
     leptos::mount::mount_to_body(|| view! { <App /> });
+
+    spawn_local(async move {
+        let db = db;
+
+        log::info!("Running loop!");
+        db.send_message();
+
+        // We create a loop so that listeners can be held for forever.
+        loop {
+            gloo::timers::future::sleep(Duration::from_secs(3600)).await;
+        }
+    });
 }
 
 #[component]
 fn App() -> impl IntoView {
-    let card = LocalResource::new(|| async move {
-        let mut database = BrowserDatabase::open()?;
-        database.load_data()?;
-
-        database.get_card(1861629).await
-    });
-
     view! {
         <Transition fallback=move || {
             view! { <p>"Loading database..."</p> }
-        }>
-            {move || {
-                card.read()
-                    .as_deref()
-                    .and_then(|card| {
-                        view! { <Card details=card.as_ref().unwrap().clone() /> }.into()
-                    })
-            }}
-        </Transition>
+        }>{move || {}}</Transition>
     }
 }
diff --git a/src/utils.rs b/src/utils.rs
new file mode 100644
index 0000000..31e459d
--- /dev/null
+++ b/src/utils.rs
@@ -0,0 +1,60 @@
+use gloo::net::http;
+use thiserror::Error;
+use time::{format_description::well_known::Rfc3339, OffsetDateTime};
+
+const GITHUB_API: &str = "https://api.github.com";
+
+pub async fn github_fetch_last_updated(
+    owner: &str,
+    repo: &str,
+    path: &str,
+) -> Result<OffsetDateTime, GithubError> {
+    let commit: Vec<serde_json::Value> = http::Request::get(&format!(
+        "{GITHUB_API}/repos/{owner}/{repo}/commits?path={path}&page=1&per_page=1"
+    ))
+    .send()
+    .await?
+    .json()
+    .await?;
+
+    commit[0]
+        .get("commit")
+        .and_then(|commit| commit.get("committer"))
+        .and_then(|committer| committer.get("date"))
+        .map(|date| {
+            OffsetDateTime::parse(
+                date.as_str().ok_or(GithubError::MissingCommitDateError)?,
+                &Rfc3339,
+            )
+            .map_err(|err| err.into())
+        })
+        .unwrap_or(Err(GithubError::MissingCommitDateError))
+}
+
+pub async fn github_fetch_file(
+    owner: &str,
+    repo: &str,
+    path: &str,
+) -> Result<Vec<u8>, GithubError> {
+    http::Request::get(&format!(
+        "{GITHUB_API}/repos/{owner}/{repo}/contents/{path}"
+    ))
+    .header("Accept", "application/vnd.github.raw+json")
+    .send()
+    .await?
+    .binary()
+    .await
+    .map_err(|err| err.into())
+}
+
+#[derive(Debug, Error)]
+pub enum GithubError {
+    #[error("no file returned from the given GitHub API")]
+    NoSuchFile,
+    #[error("failed to fetch from GitHub API")]
+    RequestError(#[from] gloo::net::Error),
+    #[error("file fetched from the GitHub API does not have a commit date")]
+    MissingCommitDateError,
+    #[error("date/time format reported for GitHub commit is invalid")]
+    InvalidDateTimeError(#[from] time::error::Parse),
+}