feat(card_db): Switch to fully-fledged diesel-based sqlite db
This commit is contained in:
parent
7e9f0ff128
commit
2bb09cbe54
14 changed files with 361 additions and 148 deletions
src
|
@ -1,6 +1,6 @@
|
|||
use leptos::prelude::*;
|
||||
|
||||
use crate::utils::card_database::CardDetails;
|
||||
use crate::database::models::CardDetails;
|
||||
|
||||
#[component]
|
||||
pub fn Card(details: Option<CardDetails>) -> impl IntoView {
|
||||
|
|
2
src/database.rs
Normal file
2
src/database.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod models;
|
||||
pub mod schema;
|
38
src/database/models.rs
Normal file
38
src/database/models.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
use diesel::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::database::schema::cards;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Insertable, Selectable, Queryable)]
|
||||
#[diesel(table_name = cards)]
|
||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
pub struct CardDetails {
|
||||
pub password: i32,
|
||||
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
|
||||
pub frame: FrameType,
|
||||
pub image: String,
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
9
src/database/schema.rs
Normal file
9
src/database/schema.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
diesel::table! {
|
||||
cards (password) {
|
||||
password -> Integer,
|
||||
name -> VarChar,
|
||||
description -> Text,
|
||||
frame -> crate::database::models::FrameTypeMapping,
|
||||
image -> Text,
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod components;
|
||||
pub mod draft_list;
|
||||
pub mod utils;
|
||||
pub mod database;
|
||||
|
|
|
@ -10,8 +10,8 @@ fn main() {
|
|||
#[component]
|
||||
fn App() -> impl IntoView {
|
||||
let card = LocalResource::new(|| async move {
|
||||
let database = CardDatabase::open().await?;
|
||||
database.load_data().await?;
|
||||
let mut database = CardDatabase::open()?;
|
||||
database.load_data()?;
|
||||
|
||||
database.get_card(1861629).await
|
||||
});
|
||||
|
|
|
@ -1,145 +1,51 @@
|
|||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use idb::{
|
||||
event::VersionChangeEvent, Database, DatabaseEvent, Factory, KeyPath, ObjectStoreParams, Query,
|
||||
TransactionMode,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_wasm_bindgen::Serializer;
|
||||
use crate::database::{self, models::FrameType, schema::cards};
|
||||
use database::models::CardDetails;
|
||||
use diesel::prelude::*;
|
||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum FrameType {
|
||||
Normal,
|
||||
Effect,
|
||||
Ritual,
|
||||
Fusion,
|
||||
Synchro,
|
||||
Xyz,
|
||||
Link,
|
||||
NormalPendulum,
|
||||
EffectPendulum,
|
||||
RitualPendulum,
|
||||
FusionPendulum,
|
||||
SynchroPendulum,
|
||||
XyzPendulum,
|
||||
Spell,
|
||||
Trap,
|
||||
Token,
|
||||
}
|
||||
const DB_URL: &str = "cards.db";
|
||||
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct CardDetails {
|
||||
pub password: u32,
|
||||
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
|
||||
pub frame: FrameType,
|
||||
pub image: String,
|
||||
}
|
||||
|
||||
pub struct CardDatabase(Database);
|
||||
|
||||
const DB_VERSION: u32 = 1;
|
||||
pub struct CardDatabase(SqliteConnection);
|
||||
|
||||
impl CardDatabase {
|
||||
pub async fn open() -> Result<Self> {
|
||||
let factory = Factory::new()?;
|
||||
let mut open_request = factory.open("cards", Some(DB_VERSION))?;
|
||||
|
||||
let err = Rc::new(RefCell::new(Ok(())));
|
||||
let moved_err = err.clone(); // Will be moved inside closure
|
||||
open_request.on_upgrade_needed(move |event| {
|
||||
let res = Self::upgrade_database(event);
|
||||
|
||||
if let Err(e) = res {
|
||||
*std::cell::RefCell::<_>::borrow_mut(&moved_err) = Err(e);
|
||||
drop(moved_err);
|
||||
}
|
||||
});
|
||||
|
||||
let database = open_request.await?;
|
||||
// Get the error if there is one - note that if the Rc has a
|
||||
// strong count > 1 and therefore Symbol’s value as variable is void: into_inner returns Symbol’s value as variable is void: None,
|
||||
// this means that the callback was not called, and as such
|
||||
// there cannot have been an error.
|
||||
let res = Rc::into_inner(err).unwrap_or(Ok(()).into()).into_inner();
|
||||
|
||||
res.map(|_| Self(database))
|
||||
pub fn open() -> Result<Self> {
|
||||
let mut connection = SqliteConnection::establish(DB_URL)?;
|
||||
connection.run_pending_migrations(MIGRATIONS).unwrap();
|
||||
Ok(Self(connection))
|
||||
}
|
||||
|
||||
pub async fn get_card(&self, key: u32) -> Result<Option<CardDetails>> {
|
||||
let transaction = self.0.transaction(&["cards"], TransactionMode::ReadOnly)?;
|
||||
let store = transaction.object_store("cards")?;
|
||||
let card = store.get(Query::Key(key.into()))?.await?;
|
||||
Ok(card.map(serde_wasm_bindgen::from_value).transpose()?)
|
||||
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 async fn load_data(&self) -> Result<()> {
|
||||
let transaction = self.0.transaction(&["cards"], TransactionMode::ReadWrite)?;
|
||||
let store = transaction.object_store("cards")?;
|
||||
|
||||
// Add some test data for now
|
||||
let decode_talker = serde_json::json!({
|
||||
"password": 1861629,
|
||||
"name": "Decode Talker",
|
||||
"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.",
|
||||
"frame": "link",
|
||||
"image": "https://images.ygoprodeck.com/images/cards/1861629.jpg"
|
||||
});
|
||||
|
||||
store
|
||||
.put(
|
||||
&decode_talker.serialize(&Serializer::json_compatible())?,
|
||||
None,
|
||||
)?
|
||||
.await?;
|
||||
|
||||
transaction.commit()?.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Upgrade the database; this is called by the browser whenever
|
||||
/// we try to open a newer database version.
|
||||
fn upgrade_database(event: VersionChangeEvent) -> Result<()> {
|
||||
let database = event.database()?;
|
||||
|
||||
let upgrade_from = event.old_version()?;
|
||||
match upgrade_from {
|
||||
// This version number signals that the database
|
||||
// hasn't been created before.
|
||||
0 => {
|
||||
let mut store_params = ObjectStoreParams::new();
|
||||
store_params.key_path(Some(KeyPath::new_single("password")));
|
||||
|
||||
let store = database.create_object_store("cards", store_params)?;
|
||||
|
||||
store.create_index(
|
||||
"fulltext",
|
||||
KeyPath::new_array(vec!["password", "name", "description"]),
|
||||
None,
|
||||
)?;
|
||||
}
|
||||
other => return Err(CardDatabaseError::UnknownDatabaseVersion(other)),
|
||||
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()
|
||||
};
|
||||
|
||||
diesel::insert_into(cards::table)
|
||||
.values(decode_talker)
|
||||
.execute(&mut self.0)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CardDatabaseError {
|
||||
#[error(
|
||||
"unknown card database version `{0}`, please close the tab and re-open the application"
|
||||
)]
|
||||
UnknownDatabaseVersion(u32),
|
||||
#[error("card database operation failed; does the browser support IndexedDB?")]
|
||||
BrowserError(#[from] idb::Error),
|
||||
#[error("invalid test data")]
|
||||
SerdeError(#[from] serde_wasm_bindgen::Error),
|
||||
#[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, CardDatabaseError>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue