From f79cbbbc85d4cc1d5cdce39f1cfc4c27e9e19b77 Mon Sep 17 00:00:00 2001 From: James Magahern Date: Sat, 14 Dec 2024 19:03:27 -0800 Subject: [PATCH] kordophone-db: switch to diesel for more features --- Cargo.lock | 241 +++++++++++++----- kordophone-db/Cargo.toml | 4 +- kordophone-db/diesel.toml | 9 + kordophone-db/migrations/.keep | 0 .../down.sql | 4 + .../up.sql | 20 ++ kordophone-db/src/chat_database.rs | 163 ++++++------ kordophone-db/src/lib.rs | 32 ++- kordophone-db/src/models/conversation.rs | 150 +++++------ kordophone-db/src/models/date.rs | 1 - kordophone-db/src/models/participant.rs | 30 ++- kordophone-db/src/schema.rs | 27 ++ 12 files changed, 432 insertions(+), 249 deletions(-) create mode 100644 kordophone-db/diesel.toml create mode 100644 kordophone-db/migrations/.keep create mode 100644 kordophone-db/migrations/2024-12-15-030301_create_conversations/down.sql create mode 100644 kordophone-db/migrations/2024-12-15-030301_create_conversations/up.sql create mode 100644 kordophone-db/src/schema.rs diff --git a/Cargo.lock b/Cargo.lock index ff6a59e..07d270e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,9 +92,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arrayvec" @@ -110,7 +110,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn", ] [[package]] @@ -221,7 +221,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.90", + "syn", ] [[package]] @@ -236,12 +236,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" -[[package]] -name = "convert_case" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" - [[package]] name = "core-foundation" version = "0.9.4" @@ -265,7 +259,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" dependencies = [ "quote", - "syn 2.0.90", + "syn", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", ] [[package]] @@ -278,12 +307,71 @@ dependencies = [ "serde", ] +[[package]] +name = "diesel" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf1bedf64cdb9643204a36dd15b19a6ce8e7aa7f7b105868e9f1fad5ffa7d12" +dependencies = [ + "chrono", + "diesel_derives", + "libsqlite3-sys", + "time", +] + +[[package]] +name = "diesel_derives" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f2c3de51e2ba6bf2a648285696137aaf0f5f487bcbea93972fe8a364e131a4" +dependencies = [ + "diesel_table_macro_syntax", + "dsl_auto_type", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diesel_migrations" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a73ce704bad4231f001bff3314d91dce4aba0770cee8b233991859abc15c1f6" +dependencies = [ + "diesel", + "migrations_internals", + "migrations_macros", +] + +[[package]] +name = "diesel_table_macro_syntax" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" +dependencies = [ + "syn", +] + [[package]] name = "dotenv" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "dsl_auto_type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9abe6314103864cc2d8901b7ae224e0ab1a103a0a416661b4097b0779b607" +dependencies = [ + "darling", + "either", + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.13.0" @@ -433,9 +521,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -550,10 +638,16 @@ dependencies = [ ] [[package]] -name = "indexmap" -version = "2.2.6" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", @@ -565,15 +659,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.11" @@ -613,8 +698,10 @@ dependencies = [ name = "kordophone-db" version = "0.1.0" dependencies = [ + "anyhow", "chrono", - "microrm", + "diesel", + "diesel_migrations", "serde", "time", "uuid", @@ -647,9 +734,9 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libsqlite3-sys" -version = "0.28.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "pkg-config", "vcpkg", @@ -684,30 +771,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] -name = "microrm" -version = "0.4.4" +name = "migrations_internals" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7119c145e9ee33d8a79a2c7e15c4f429d681079cea976692a55c78aaae34f5ae" +checksum = "fd01039851e82f8799046eabbb354056283fb265c8ec0996af940f4e85a380ff" dependencies = [ - "itertools", - "libsqlite3-sys", - "log", - "microrm-macros", "serde", - "serde_json", - "time", + "toml", ] [[package]] -name = "microrm-macros" -version = "0.4.1" +name = "migrations_macros" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3e29bc1b3c1dc742de1b3a23f11e472a2adc936a967dc5d769c26a809326b8" +checksum = "ffb161cc72176cb37aa47f1fc520d3ef02263d67d661f44f05d05a079e1237fd" dependencies = [ - "convert_case", + "migrations_internals", "proc-macro2", "quote", - "syn 1.0.109", ] [[package]] @@ -802,7 +883,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn", ] [[package]] @@ -1054,7 +1135,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn", ] [[package]] @@ -1077,6 +1158,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1117,17 +1207,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.90" @@ -1167,6 +1246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde", @@ -1216,7 +1296,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn", ] [[package]] @@ -1243,6 +1323,40 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -1317,7 +1431,7 @@ checksum = "6b91f57fe13a38d0ce9e28a03463d8d3c2468ed03d75375110ec71d93b449a08" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn", ] [[package]] @@ -1363,7 +1477,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.90", + "syn", "wasm-bindgen-shared", ] @@ -1385,7 +1499,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1536,3 +1650,12 @@ name = "windows_x86_64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] diff --git a/kordophone-db/Cargo.toml b/kordophone-db/Cargo.toml index ac8e6db..1fd691e 100644 --- a/kordophone-db/Cargo.toml +++ b/kordophone-db/Cargo.toml @@ -4,8 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] +anyhow = "1.0.94" chrono = "0.4.38" -microrm = "0.4.4" +diesel = { version = "2.2.6", features = ["chrono", "sqlite", "time"] } +diesel_migrations = { version = "2.2.0", features = ["sqlite"] } serde = { version = "1.0.215", features = ["derive"] } time = "0.3.37" uuid = { version = "1.11.0", features = ["v4"] } diff --git a/kordophone-db/diesel.toml b/kordophone-db/diesel.toml new file mode 100644 index 0000000..c028f4a --- /dev/null +++ b/kordophone-db/diesel.toml @@ -0,0 +1,9 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" +custom_type_derives = ["diesel::query_builder::QueryId"] + +[migrations_directory] +dir = "migrations" diff --git a/kordophone-db/migrations/.keep b/kordophone-db/migrations/.keep new file mode 100644 index 0000000..e69de29 diff --git a/kordophone-db/migrations/2024-12-15-030301_create_conversations/down.sql b/kordophone-db/migrations/2024-12-15-030301_create_conversations/down.sql new file mode 100644 index 0000000..40e3b94 --- /dev/null +++ b/kordophone-db/migrations/2024-12-15-030301_create_conversations/down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` +DROP TABLE IF EXISTS `participants`; +DROP TABLE IF EXISTS `conversation_participants`; +DROP TABLE IF EXISTS `conversations`; diff --git a/kordophone-db/migrations/2024-12-15-030301_create_conversations/up.sql b/kordophone-db/migrations/2024-12-15-030301_create_conversations/up.sql new file mode 100644 index 0000000..9ac80cd --- /dev/null +++ b/kordophone-db/migrations/2024-12-15-030301_create_conversations/up.sql @@ -0,0 +1,20 @@ +-- Your SQL goes here +CREATE TABLE `participants`( + `id` INTEGER NOT NULL PRIMARY KEY, + `display_name` TEXT NOT NULL +); + +CREATE TABLE `conversation_participants`( + `conversation_id` TEXT NOT NULL, + `participant_id` INTEGER NOT NULL, + PRIMARY KEY(`conversation_id`, `participant_id`) +); + +CREATE TABLE `conversations`( + `id` TEXT NOT NULL PRIMARY KEY, + `unread_count` BIGINT NOT NULL, + `display_name` TEXT, + `last_message_preview` TEXT, + `date` TIMESTAMP NOT NULL +); + diff --git a/kordophone-db/src/chat_database.rs b/kordophone-db/src/chat_database.rs index 65e2286..8d25f83 100644 --- a/kordophone-db/src/chat_database.rs +++ b/kordophone-db/src/chat_database.rs @@ -1,113 +1,100 @@ use std::error::Error; -use microrm::prelude::*; -use microrm::Stored; +use anyhow::Result; +use diesel::prelude::*; +use diesel::query_dsl::BelongingToDsl; -use crate::models::participant::ParticipantID; -use crate::models::{ - participant::Participant, +use crate::{models::{ conversation::{ - self, Conversation, ConversationID, PendingConversation - } -}; + self, Conversation, DbConversation + }, participant::{ConversationParticipant, DbParticipant, Participant} +}, schema}; + +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; +pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); pub struct ChatDatabase { - db: DB, -} - -#[derive(Database)] -struct DB { - conversations: microrm::IDMap, - participants: microrm::IDMap, + db: SqliteConnection, } impl ChatDatabase { - pub fn new_in_memory() -> Result> { - let db = DB::open_path(":memory:")?; + pub fn new_in_memory() -> Result { + let mut db = SqliteConnection::establish(":memory:")?; + db.run_pending_migrations(MIGRATIONS) + .map_err(|e| anyhow::anyhow!("Error running migrations: {}", e))?; + return Ok(Self { db: db, }) } - pub fn insert_conversation(&self, conversation: PendingConversation) -> Result { - // First see if conversation guid already exists, update it if so - let guid = conversation.guid(); - let mut existing = self.stored_conversation_by_guid(guid)?; + pub fn insert_conversation(&mut self, conversation: Conversation) -> Result<()> { + use crate::schema::conversations::dsl::*; + use crate::schema::participants::dsl::*; + use crate::schema::conversation_participants::dsl::*; - if let Some(existing) = existing.as_mut() { - conversation.update(existing); - existing.sync(); - return Ok(existing.id()); - } else { - // Otherwise, insert. - let inserted = self.db.conversations.insert_and_return(conversation.get_conversation())?; + let (db_conversation, db_participants) = conversation.into(); - // Insert participants - let participants = conversation.get_participants(); - let inserted_participants = participants.iter() - .map(|p| self.db.participants.insert(p.clone()).unwrap()) - .collect::>(); - inserted.connect_participants(inserted_participants); - - return Ok(inserted.id()); + diesel::replace_into(conversations) + .values(&db_conversation) + .execute(&mut self.db)?; + + diesel::replace_into(participants) + .values(&db_participants) + .execute(&mut self.db)?; + + // Sqlite backend doesn't support batch insert, so we have to do this manually + for participant in db_participants { + let pid = participants + .select(schema::participants::id) + .filter(schema::participants::display_name.eq(&participant.display_name)) + .first::(&mut self.db)?; + + diesel::replace_into(conversation_participants) + .values(( + conversation_id.eq(&db_conversation.id), + participant_id.eq(pid), + )) + .execute(&mut self.db)?; } + + Ok(()) } - pub fn get_conversation_by_id(&self, id: ConversationID) -> Result, microrm::Error> { - self.db.conversations - .by_id(id) - .map(|stored_conversation| stored_conversation - .map(|stored| stored.wrapped()) - ) + pub fn get_conversation_by_guid(&mut self, match_guid: &str) -> Result> { + use crate::schema::conversations::dsl::*; + use crate::schema::participants::dsl::*; + + let result = conversations + .find(match_guid) + .first::(&mut self.db) + .optional()?; + + if let Some(conversation) = result { + let dbParticipants = ConversationParticipant::belonging_to(&conversation) + .inner_join(participants) + .select(DbParticipant::as_select()) + .load::(&mut self.db)?; + + let mut modelConversation: Conversation = conversation.into(); + modelConversation.participants = dbParticipants.into_iter().map(|p| p.into()).collect(); + + return Ok(Some(modelConversation)); + } + + Ok(None) } - pub fn get_conversation_by_guid(&self, guid: &str) -> Result, microrm::Error> { - self.db.conversations - .with(Conversation::Guid, guid) - .get() - .and_then(|v| Ok(v - .into_iter() - .map(|c| c.wrapped()) - .last() - )) - } + pub fn all_conversations(&mut self) -> Result> { + use crate::schema::conversations::dsl::*; - pub fn all_conversations(&self) -> Result, microrm::Error> { - self.db.conversations - .get() - .map(|v| v - .into_iter() - .map(|c| c.wrapped()) - .collect() - ) - } + let result = conversations + .load::(&mut self.db)? + .into_iter() + .map(|c| c.into()) + .collect(); - fn upsert_participants(&self, participants: Vec) -> Vec { - // Filter existing participants and add to result - let existing_participants = participants.iter() - .filter_map(|p| self.db.participants - .with(Participant::DisplayName, &p.display_name) - .get() - .ok() - .and_then(|v| v - .into_iter() - .last() - .map(|p| p.id()) - ) - ) - .collect::>(); + // TODO: Need to resolve participants here also somehow... - participants.iter() - .map(|p| self.db.participants.insert(p.clone()).unwrap()) - .collect() - } - - fn stored_conversation_by_guid(&self, guid: &str) -> Result>, microrm::Error> { - self.db.conversations - .with(Conversation::Guid, guid) - .get() - .map(|v| v - .into_iter() - .last() - ) + Ok(result) } } \ No newline at end of file diff --git a/kordophone-db/src/lib.rs b/kordophone-db/src/lib.rs index 6eb6f99..f7623f7 100644 --- a/kordophone-db/src/lib.rs +++ b/kordophone-db/src/lib.rs @@ -1,5 +1,6 @@ pub mod models; pub mod chat_database; +pub mod schema; #[cfg(test)] mod tests { @@ -18,18 +19,19 @@ mod tests { #[test] fn test_add_conversation() { - let db = ChatDatabase::new_in_memory().unwrap(); + let mut db = ChatDatabase::new_in_memory().unwrap(); + let guid = "test"; let test_conversation = Conversation::builder() - .guid("test") + .guid(guid) .unread_count(2) .display_name("Test Conversation") .build(); - let id = db.insert_conversation(test_conversation.clone()).unwrap(); + db.insert_conversation(test_conversation.clone()).unwrap(); // Try to fetch with id now - let conversation = db.get_conversation_by_id(id).unwrap().unwrap(); + let conversation = db.get_conversation_by_guid(guid).unwrap().unwrap(); assert_eq!(conversation.guid, "test"); // Modify the conversation and update it @@ -44,38 +46,40 @@ mod tests { assert_eq!(all_conversations.len(), 1); // And make sure the display name was updated - let conversation = db.get_conversation_by_id(id).unwrap().unwrap(); + let conversation = db.get_conversation_by_guid(guid).unwrap().unwrap(); assert_eq!(conversation.display_name.unwrap(), "Modified Conversation"); } #[test] fn test_conversation_participants() { - let db = ChatDatabase::new_in_memory().unwrap(); + let mut db = ChatDatabase::new_in_memory().unwrap(); let participants: Vec = vec!["one".into(), "two".into()]; + let guid = uuid::Uuid::new_v4().to_string(); let conversation = ConversationBuilder::new() + .guid(&guid) .display_name("Test") - .participant_display_names(participants.clone()) + .participants(participants.clone()) .build(); - let id = db.insert_conversation(conversation).unwrap(); + db.insert_conversation(conversation).unwrap(); - let read_conversation = db.get_conversation_by_id(id).unwrap().unwrap(); - let read_participants: Vec = read_conversation.get_participant_display_names(); + let read_conversation = db.get_conversation_by_guid(&guid).unwrap().unwrap(); + let read_participants = read_conversation.participants; assert_eq!(participants, read_participants); // Try making another conversation with the same participants let conversation = ConversationBuilder::new() .display_name("A Different Test") - .participant_display_names(participants.clone()) + .participants(participants.clone()) .build(); - let id = db.insert_conversation(conversation).unwrap(); + db.insert_conversation(conversation).unwrap(); - let read_conversation = db.get_conversation_by_id(id).unwrap().unwrap(); - let read_participants: Vec = read_conversation.get_participant_display_names(); + let read_conversation = db.get_conversation_by_guid(&guid).unwrap().unwrap(); + let read_participants: Vec = read_conversation.participants; assert_eq!(participants, read_participants); } diff --git a/kordophone-db/src/models/conversation.rs b/kordophone-db/src/models/conversation.rs index 4fd2ff5..e227d00 100644 --- a/kordophone-db/src/models/conversation.rs +++ b/kordophone-db/src/models/conversation.rs @@ -1,26 +1,58 @@ -use microrm::prelude::*; -use microrm::Stored; -use time::OffsetDateTime; +use diesel::prelude::*; +use chrono::NaiveDateTime; use uuid::Uuid; use crate::models::{ - date::Date, participant::Participant, }; -use super::participant::ParticipantID; - -#[derive(Entity, Clone)] -pub struct Conversation { - #[unique] - pub guid: String, - +#[derive(Queryable, Selectable, Insertable, AsChangeset, Clone, Identifiable)] +#[diesel(table_name = crate::schema::conversations)] +#[diesel(check_for_backend(diesel::sqlite::Sqlite))] +pub struct DbConversation { + pub id: String, pub unread_count: i64, pub display_name: Option, pub last_message_preview: Option, - pub date: OffsetDateTime, - - participant_display_names: microrm::RelationMap, + pub date: NaiveDateTime, +} + +impl From for DbConversation { + fn from(conversation: Conversation) -> Self { + Self { + id: conversation.guid, + unread_count: conversation.unread_count as i64, + display_name: conversation.display_name, + last_message_preview: conversation.last_message_preview, + date: conversation.date, + } + } +} + +impl From for (DbConversation, Vec) { + fn from(conversation: Conversation) -> Self { + ( + DbConversation { + id: conversation.guid, + unread_count: conversation.unread_count as i64, + display_name: conversation.display_name, + last_message_preview: conversation.last_message_preview, + date: conversation.date, + }, + + conversation.participants + ) + } +} + +#[derive(Clone, Debug)] +pub struct Conversation { + pub guid: String, + pub unread_count: u16, + pub display_name: Option, + pub last_message_preview: Option, + pub date: NaiveDateTime, + pub participants: Vec, } impl Conversation { @@ -31,76 +63,35 @@ impl Conversation { pub fn into_builder(&self) -> ConversationBuilder { ConversationBuilder { guid: Some(self.guid.clone()), - date: Date::new(self.date), - participant_display_names: None, + date: self.date, + participants: None, unread_count: Some(self.unread_count), last_message_preview: self.last_message_preview.clone(), display_name: self.display_name.clone(), } } - - pub fn get_participant_display_names(&self) -> Vec { - self.participant_display_names - .get() - .unwrap() - .into_iter() - .map(|p| p.wrapped()) - .collect() - } - - pub fn update(&self, stored_conversation: &mut Stored) { - *stored_conversation.as_mut() = self.clone(); - } - - pub fn connect_participants(&self, participant_ids: Vec) { - participant_ids.iter().for_each(|id| { - self.participant_display_names.connect_to(*id).unwrap(); - }); - } } -#[derive(Clone)] -pub struct PendingConversation { - conversation: Conversation, - participants: Vec, -} - -impl PendingConversation { - pub fn guid(&self) -> &String { - &self.conversation.guid - } - - pub fn into_builder(self) -> ConversationBuilder { - ConversationBuilder { - guid: Some(self.conversation.guid), - date: Date::new(self.conversation.date), - participant_display_names: Some(self.participants), - unread_count: Some(self.conversation.unread_count), - last_message_preview: self.conversation.last_message_preview, - display_name: self.conversation.display_name, +impl From for Conversation { + fn from(db_conversation: DbConversation) -> Self { + Self { + guid: db_conversation.id, + unread_count: db_conversation.unread_count as u16, + display_name: db_conversation.display_name, + last_message_preview: db_conversation.last_message_preview, + date: db_conversation.date, + participants: vec![], } } - - pub fn update(&self, stored_conversation: &mut microrm::Stored) { - self.conversation.update(stored_conversation); - } - - pub fn get_participants(&self) -> &Vec { - &self.participants - } - - pub fn get_conversation(&self) -> Conversation { - self.conversation.clone() - } } #[derive(Default)] pub struct ConversationBuilder { guid: Option, - date: Date, - unread_count: Option, + date: NaiveDateTime, + unread_count: Option, last_message_preview: Option, - participant_display_names: Option>, + participants: Option>, display_name: Option, } @@ -114,12 +105,12 @@ impl ConversationBuilder { self } - pub fn date(mut self, date: Date) -> Self { + pub fn date(mut self, date: NaiveDateTime) -> Self { self.date = date; self } - pub fn unread_count(mut self, unread_count: i64) -> Self { + pub fn unread_count(mut self, unread_count: u16) -> Self { self.unread_count = Some(unread_count); self } @@ -129,8 +120,8 @@ impl ConversationBuilder { self } - pub fn participant_display_names(mut self, participant_display_names: Vec) -> Self { - self.participant_display_names = Some(participant_display_names); + pub fn participants(mut self, participants: Vec) -> Self { + self.participants = Some(participants); self } @@ -139,21 +130,14 @@ impl ConversationBuilder { self } - fn build_conversation(&self) -> Conversation { + pub fn build(&self) -> Conversation { Conversation { guid: self.guid.clone().unwrap_or(Uuid::new_v4().to_string()), unread_count: self.unread_count.unwrap_or(0), last_message_preview: self.last_message_preview.clone(), display_name: self.display_name.clone(), - date: self.date.dt, - participant_display_names: Default::default(), - } - } - - pub fn build(self) -> PendingConversation { - PendingConversation { - conversation: self.build_conversation(), - participants: self.participant_display_names.unwrap_or_default(), + date: self.date, + participants: self.participants.clone().unwrap_or_default(), } } } diff --git a/kordophone-db/src/models/date.rs b/kordophone-db/src/models/date.rs index b4ffea7..084769e 100644 --- a/kordophone-db/src/models/date.rs +++ b/kordophone-db/src/models/date.rs @@ -1,4 +1,3 @@ -use chrono::{DateTime, Local, Utc}; use time::OffsetDateTime; pub struct Date { diff --git a/kordophone-db/src/models/participant.rs b/kordophone-db/src/models/participant.rs index e5cabb4..89733d8 100644 --- a/kordophone-db/src/models/participant.rs +++ b/kordophone-db/src/models/participant.rs @@ -1,11 +1,35 @@ -use microrm::prelude::*; +use diesel::prelude::*; +use crate::{models::conversation::DbConversation, schema::conversation_participants}; -#[derive(Entity, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Insertable)] +#[diesel(table_name = crate::schema::participants)] pub struct Participant { - #[unique] + pub display_name: String, +} + +impl From for Participant { + fn from(participant: DbParticipant) -> Self { + Participant { display_name: participant.display_name } + } +} + +#[derive(Queryable, Selectable, Insertable, AsChangeset, Clone, PartialEq, Debug, Identifiable)] +#[diesel(table_name = crate::schema::participants)] +pub struct DbParticipant { + pub id: i32, pub display_name: String } +#[derive(Identifiable, Selectable, Queryable, Associations, Debug)] +#[diesel(belongs_to(DbConversation, foreign_key = conversation_id))] +#[diesel(belongs_to(DbParticipant, foreign_key = participant_id))] +#[diesel(table_name = conversation_participants)] +#[diesel(primary_key(conversation_id, participant_id))] +pub struct ConversationParticipant { + pub conversation_id: String, + pub participant_id: i32, +} + impl Into for String { fn into(self) -> Participant { Participant { display_name: self } diff --git a/kordophone-db/src/schema.rs b/kordophone-db/src/schema.rs new file mode 100644 index 0000000..19556cb --- /dev/null +++ b/kordophone-db/src/schema.rs @@ -0,0 +1,27 @@ +diesel::table! { + conversations (id) { + id -> Text, + unread_count -> BigInt, + display_name -> Nullable, + last_message_preview -> Nullable, + date -> Timestamp, + } +} + +diesel::table! { + participants (id) { + id -> Integer, + display_name -> Text, + } +} + +diesel::table! { + conversation_participants (conversation_id, participant_id) { + conversation_id -> Text, + participant_id -> Integer, + } +} + +diesel::joinable!(conversation_participants -> conversations (conversation_id)); +diesel::joinable!(conversation_participants -> participants (participant_id)); +diesel::allow_tables_to_appear_in_same_query!(conversations, participants, conversation_participants);