From f7d094fcd67c85d6fb02c2a4bf3a5dee783c569a Mon Sep 17 00:00:00 2001 From: James Magahern Date: Fri, 25 Apr 2025 15:42:46 -0700 Subject: [PATCH] reorg: split repo / database so settings can use db connection as well --- Cargo.lock | 10 ++ kordophone-db/Cargo.toml | 1 + .../2025-04-25-223015_add_settings/down.sql | 7 ++ .../2025-04-25-223015_add_settings/up.sql | 11 ++ kordophone-db/src/database.rs | 34 ++++++ kordophone-db/src/lib.rs | 6 +- .../src/{chat_database.rs => repository.rs} | 70 +++++------- kordophone-db/src/schema.rs | 7 ++ kordophone-db/src/settings.rs | 71 ++++++++++++ kordophone-db/src/tests/mod.rs | 83 +++++++++----- kpcli/src/daemon/mod.rs | 2 +- kpcli/src/db/mod.rs | 106 ++++++++++++++++-- 12 files changed, 326 insertions(+), 82 deletions(-) create mode 100644 kordophone-db/migrations/2025-04-25-223015_add_settings/down.sql create mode 100644 kordophone-db/migrations/2025-04-25-223015_add_settings/up.sql create mode 100644 kordophone-db/src/database.rs rename kordophone-db/src/{chat_database.rs => repository.rs} (82%) create mode 100644 kordophone-db/src/settings.rs diff --git a/Cargo.lock b/Cargo.lock index 7a70174..ca1bd19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,6 +160,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 = "1.3.2" @@ -796,6 +805,7 @@ name = "kordophone-db" version = "0.1.0" dependencies = [ "anyhow", + "bincode", "chrono", "diesel", "diesel_migrations", diff --git a/kordophone-db/Cargo.toml b/kordophone-db/Cargo.toml index 626cc7e..0696e36 100644 --- a/kordophone-db/Cargo.toml +++ b/kordophone-db/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] anyhow = "1.0.94" +bincode = "1.3.3" chrono = "0.4.38" diesel = { version = "2.2.6", features = ["chrono", "sqlite", "time"] } diesel_migrations = { version = "2.2.0", features = ["sqlite"] } diff --git a/kordophone-db/migrations/2025-04-25-223015_add_settings/down.sql b/kordophone-db/migrations/2025-04-25-223015_add_settings/down.sql new file mode 100644 index 0000000..0445954 --- /dev/null +++ b/kordophone-db/migrations/2025-04-25-223015_add_settings/down.sql @@ -0,0 +1,7 @@ +-- This file should undo anything in `up.sql` + + + + + +DROP TABLE IF EXISTS `settings`; diff --git a/kordophone-db/migrations/2025-04-25-223015_add_settings/up.sql b/kordophone-db/migrations/2025-04-25-223015_add_settings/up.sql new file mode 100644 index 0000000..56e9cc6 --- /dev/null +++ b/kordophone-db/migrations/2025-04-25-223015_add_settings/up.sql @@ -0,0 +1,11 @@ +-- Your SQL goes here + + + + + +CREATE TABLE `settings`( + `key` TEXT NOT NULL PRIMARY KEY, + `value` BINARY NOT NULL +); + diff --git a/kordophone-db/src/database.rs b/kordophone-db/src/database.rs new file mode 100644 index 0000000..29bb6fd --- /dev/null +++ b/kordophone-db/src/database.rs @@ -0,0 +1,34 @@ +use anyhow::Result; +use diesel::prelude::*; + +use crate::repository::Repository; +use crate::settings::Settings; + +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; +pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); + +pub struct Database { + pub connection: SqliteConnection, +} + +impl Database { + pub fn new(path: &str) -> Result { + let mut connection = SqliteConnection::establish(path)?; + connection.run_pending_migrations(MIGRATIONS) + .map_err(|e| anyhow::anyhow!("Error running migrations: {}", e))?; + + Ok(Self { connection }) + } + + pub fn new_in_memory() -> Result { + Self::new(":memory:") + } + + pub fn get_repository(&mut self) -> Repository { + Repository::new(self) + } + + pub fn get_settings(&mut self) -> Settings { + Settings::new(self) + } +} \ No newline at end of file diff --git a/kordophone-db/src/lib.rs b/kordophone-db/src/lib.rs index 3ca1345..abdc8f7 100644 --- a/kordophone-db/src/lib.rs +++ b/kordophone-db/src/lib.rs @@ -1,8 +1,10 @@ +pub mod database; pub mod models; -pub mod chat_database; +pub mod repository; pub mod schema; +pub mod settings; #[cfg(test)] mod tests; -pub use chat_database::ChatDatabase; \ No newline at end of file +pub use repository::Repository; \ No newline at end of file diff --git a/kordophone-db/src/chat_database.rs b/kordophone-db/src/repository.rs similarity index 82% rename from kordophone-db/src/chat_database.rs rename to kordophone-db/src/repository.rs index e4d55c8..8eb535f 100644 --- a/kordophone-db/src/chat_database.rs +++ b/kordophone-db/src/repository.rs @@ -2,11 +2,14 @@ use anyhow::Result; use diesel::prelude::*; use diesel::query_dsl::BelongingToDsl; -use crate::models::Participant; +use std::sync::Arc; + use crate::{ + database::Database, models::{ Conversation, Message, + Participant, db::conversation::Record as ConversationRecord, db::participant::{ ConversationParticipant, @@ -18,32 +21,13 @@ use crate::{ schema, }; -use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; -pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); - -pub struct ChatDatabase { - db: SqliteConnection, +pub struct Repository<'a> { + db: &'a mut Database, } -impl ChatDatabase { - pub fn new_in_memory() -> Result { - Self::new(":memory:") - } - - // Helper function to get the last inserted row ID - // This is a workaround since the Sqlite backend doesn't support `RETURNING` - // Huge caveat with this is that it depends on whatever the last insert was, prevents concurrent inserts. - fn last_insert_id(&mut self) -> Result { - Ok(diesel::select(diesel::dsl::sql::("last_insert_rowid()")) - .get_result(&mut self.db)?) - } - - pub fn new(db_path: &str) -> Result { - let mut db = SqliteConnection::establish(db_path)?; - db.run_pending_migrations(MIGRATIONS) - .map_err(|e| anyhow::anyhow!("Error running migrations: {}", e))?; - - Ok(Self { db }) +impl<'a> Repository<'a> { + pub fn new(db: &'a mut Database) -> Self { + Self { db } } pub fn insert_conversation(&mut self, conversation: Conversation) -> Result<()> { @@ -55,25 +39,25 @@ impl ChatDatabase { diesel::replace_into(conversations) .values(&db_conversation) - .execute(&mut self.db)?; + .execute(&mut self.db.connection)?; diesel::replace_into(participants) .values(&db_participants) - .execute(&mut self.db)?; + .execute(&mut self.db.connection)?; // 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)?; + .first::(&mut self.db.connection)?; diesel::replace_into(conversation_participants) .values(( conversation_id.eq(&db_conversation.id), participant_id.eq(pid), )) - .execute(&mut self.db)?; + .execute(&mut self.db.connection)?; } Ok(()) @@ -85,14 +69,14 @@ impl ChatDatabase { let result = conversations .find(match_guid) - .first::(&mut self.db) + .first::(&mut self.db.connection) .optional()?; if let Some(conversation) = result { let db_participants = ConversationParticipant::belonging_to(&conversation) .inner_join(participants) .select(ParticipantRecord::as_select()) - .load::(&mut self.db)?; + .load::(&mut self.db.connection)?; let mut model_conversation: Conversation = conversation.into(); model_conversation.participants = db_participants.into_iter().map(|p| p.into()).collect(); @@ -108,14 +92,14 @@ impl ChatDatabase { use crate::schema::participants::dsl::*; let db_conversations = conversations - .load::(&mut self.db)?; + .load::(&mut self.db.connection)?; let mut result = Vec::new(); for db_conversation in db_conversations { let db_participants = ConversationParticipant::belonging_to(&db_conversation) .inner_join(participants) .select(ParticipantRecord::as_select()) - .load::(&mut self.db)?; + .load::(&mut self.db.connection)?; let mut model_conversation: Conversation = db_conversation.into(); model_conversation.participants = db_participants.into_iter().map(|p| p.into()).collect(); @@ -137,14 +121,14 @@ impl ChatDatabase { diesel::replace_into(messages) .values(&db_message) - .execute(&mut self.db)?; + .execute(&mut self.db.connection)?; diesel::replace_into(conversation_messages) .values(( conversation_id.eq(conversation_guid), message_id.eq(&db_message.id), )) - .execute(&mut self.db)?; + .execute(&mut self.db.connection)?; Ok(()) } @@ -159,7 +143,7 @@ impl ChatDatabase { .inner_join(messages) .select(MessageRecord::as_select()) .order_by(schema::messages::date.asc()) - .load::(&mut self.db)?; + .load::(&mut self.db.connection)?; let mut result = Vec::new(); for message_record in message_records { @@ -169,7 +153,7 @@ impl ChatDatabase { if let Some(pid) = message_record.sender_participant_id { let participant = participants .find(pid) - .first::(&mut self.db)?; + .first::(&mut self.db.connection)?; message.sender = participant.into(); } @@ -179,6 +163,14 @@ impl ChatDatabase { Ok(result) } + // Helper function to get the last inserted row ID + // This is a workaround since the Sqlite backend doesn't support `RETURNING` + // Huge caveat with this is that it depends on whatever the last insert was, prevents concurrent inserts. + fn last_insert_id(&mut self) -> Result { + Ok(diesel::select(diesel::dsl::sql::("last_insert_rowid()")) + .get_result(&mut self.db.connection)?) + } + fn get_or_create_participant(&mut self, participant: &Participant) -> Option { match participant { Participant::Me => None, @@ -187,7 +179,7 @@ impl ChatDatabase { let existing_participant = participants .filter(display_name.eq(p_name)) - .first::(&mut self.db) + .first::(&mut self.db.connection) .optional() .unwrap(); @@ -202,7 +194,7 @@ impl ChatDatabase { diesel::insert_into(participants) .values(&participant_record) - .execute(&mut self.db) + .execute(&mut self.db.connection) .unwrap(); self.last_insert_id().ok() diff --git a/kordophone-db/src/schema.rs b/kordophone-db/src/schema.rs index f1708b9..d77e401 100644 --- a/kordophone-db/src/schema.rs +++ b/kordophone-db/src/schema.rs @@ -42,6 +42,13 @@ diesel::table! { } } +diesel::table! { + settings (key) { + key -> Text, + value -> Binary, + } +} + 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); diff --git a/kordophone-db/src/settings.rs b/kordophone-db/src/settings.rs new file mode 100644 index 0000000..a234560 --- /dev/null +++ b/kordophone-db/src/settings.rs @@ -0,0 +1,71 @@ +use diesel::*; +use serde::{Serialize, de::DeserializeOwned}; +use anyhow::Result; +use crate::database::Database; +#[derive(Insertable, Queryable, AsChangeset)] +#[diesel(table_name = crate::schema::settings)] +struct SettingsRow<'a> { + key: &'a str, + value: &'a [u8], +} + +pub struct Settings<'a> { + db: &'a mut Database, +} + +impl<'a> Settings<'a> { + pub fn new(db: &'a mut Database) -> Self { + Self { db } + } + + pub fn put( + &mut self, + k: &str, + v: &T, + ) -> Result<()> { + use crate::schema::settings::dsl::*; + let bytes = bincode::serialize(v)?; + + diesel::insert_into(settings) + .values(SettingsRow { key: k, value: &bytes }) + .on_conflict(key) + .do_update() + .set((value.eq(&bytes))) + .execute(&mut self.db.connection)?; + + Ok(()) + } + + pub fn get( + &mut self, + k: &str, + ) -> Result> { + use crate::schema::settings::dsl::*; + let blob: Option> = settings + .select(value) + .filter(key.eq(k)) + .first(&mut self.db.connection) + .optional()?; + + Ok(match blob { + Some(b) => Some(bincode::deserialize(&b)?), + None => None, + }) + } + + pub fn del(&mut self, k: &str) -> Result { + use crate::schema::settings::dsl::*; + Ok(diesel::delete(settings.filter(key.eq(k))).execute(&mut self.db.connection)?) + } + + pub fn list_keys(&mut self) -> Result> { + use crate::schema::settings::dsl::*; + let keys: Vec = settings + .select(key) + .load(&mut self.db.connection)?; + + Ok(keys) + } +} + + diff --git a/kordophone-db/src/tests/mod.rs b/kordophone-db/src/tests/mod.rs index 8434447..961f486 100644 --- a/kordophone-db/src/tests/mod.rs +++ b/kordophone-db/src/tests/mod.rs @@ -1,10 +1,12 @@ use crate::{ - chat_database::ChatDatabase, + database::Database, + repository::Repository, models::{ conversation::{Conversation, ConversationBuilder}, participant::Participant, message::Message, - } + }, + settings::Settings, }; // Helper function to compare participants ignoring database IDs @@ -26,12 +28,14 @@ fn participants_vec_equal_ignoring_id(a: &[Participant], b: &[Participant]) -> b #[test] fn test_database_init() { - let _ = ChatDatabase::new_in_memory().unwrap(); + let mut db = Database::new_in_memory().unwrap(); + let _ = Repository::new(&mut db); } #[test] fn test_add_conversation() { - let mut db = ChatDatabase::new_in_memory().unwrap(); + let mut db = Database::new_in_memory().unwrap(); + let mut repository = db.get_repository(); let guid = "test"; let test_conversation = Conversation::builder() @@ -40,10 +44,10 @@ fn test_add_conversation() { .display_name("Test Conversation") .build(); - db.insert_conversation(test_conversation.clone()).unwrap(); + repository.insert_conversation(test_conversation.clone()).unwrap(); // Try to fetch with id now - let conversation = db.get_conversation_by_guid(guid).unwrap().unwrap(); + let conversation = repository.get_conversation_by_guid(guid).unwrap().unwrap(); assert_eq!(conversation.guid, "test"); // Modify the conversation and update it @@ -51,20 +55,21 @@ fn test_add_conversation() { .display_name("Modified Conversation") .build(); - db.insert_conversation(modified_conversation.clone()).unwrap(); + repository.insert_conversation(modified_conversation.clone()).unwrap(); // Make sure we still only have one conversation. - let all_conversations = db.all_conversations().unwrap(); + let all_conversations = repository.all_conversations().unwrap(); assert_eq!(all_conversations.len(), 1); // And make sure the display name was updated - let conversation = db.get_conversation_by_guid(guid).unwrap().unwrap(); + let conversation = repository.get_conversation_by_guid(guid).unwrap().unwrap(); assert_eq!(conversation.display_name.unwrap(), "Modified Conversation"); } #[test] fn test_conversation_participants() { - let mut db = ChatDatabase::new_in_memory().unwrap(); + let mut db = Database::new_in_memory().unwrap(); + let mut repository = db.get_repository(); let participants: Vec = vec!["one".into(), "two".into()]; @@ -75,9 +80,9 @@ fn test_conversation_participants() { .participants(participants.clone()) .build(); - db.insert_conversation(conversation).unwrap(); + repository.insert_conversation(conversation).unwrap(); - let read_conversation = db.get_conversation_by_guid(&guid).unwrap().unwrap(); + let read_conversation = repository.get_conversation_by_guid(&guid).unwrap().unwrap(); let read_participants = read_conversation.participants; assert!(participants_vec_equal_ignoring_id(&participants, &read_participants)); @@ -88,9 +93,9 @@ fn test_conversation_participants() { .participants(participants.clone()) .build(); - db.insert_conversation(conversation).unwrap(); + repository.insert_conversation(conversation).unwrap(); - let read_conversation = db.get_conversation_by_guid(&guid).unwrap().unwrap(); + let read_conversation = repository.get_conversation_by_guid(&guid).unwrap().unwrap(); let read_participants: Vec = read_conversation.participants; assert!(participants_vec_equal_ignoring_id(&participants, &read_participants)); @@ -98,7 +103,8 @@ fn test_conversation_participants() { #[test] fn test_all_conversations_with_participants() { - let mut db = ChatDatabase::new_in_memory().unwrap(); + let mut db = Database::new_in_memory().unwrap(); + let mut repository = db.get_repository(); // Create two conversations with different participants let participants1: Vec = vec!["one".into(), "two".into()]; @@ -119,11 +125,11 @@ fn test_all_conversations_with_participants() { .build(); // Insert both conversations - db.insert_conversation(conversation1).unwrap(); - db.insert_conversation(conversation2).unwrap(); + repository.insert_conversation(conversation1).unwrap(); + repository.insert_conversation(conversation2).unwrap(); // Get all conversations and verify the results - let all_conversations = db.all_conversations().unwrap(); + let all_conversations = repository.all_conversations().unwrap(); assert_eq!(all_conversations.len(), 2); // Find and verify each conversation's participants @@ -136,7 +142,8 @@ fn test_all_conversations_with_participants() { #[test] fn test_messages() { - let mut db = ChatDatabase::new_in_memory().unwrap(); + let mut db = Database::new_in_memory().unwrap(); + let mut repository = db.get_repository(); // First create a conversation with participants let participants = vec!["Alice".into(), "Bob".into()]; @@ -146,7 +153,7 @@ fn test_messages() { .build(); let conversation_id = conversation.guid.clone(); - db.insert_conversation(conversation).unwrap(); + repository.insert_conversation(conversation).unwrap(); // Create and insert a message from Me let message1 = Message::builder() @@ -160,11 +167,11 @@ fn test_messages() { .build(); // Insert both messages - db.insert_message(&conversation_id, message1.clone()).unwrap(); - db.insert_message(&conversation_id, message2.clone()).unwrap(); + repository.insert_message(&conversation_id, message1.clone()).unwrap(); + repository.insert_message(&conversation_id, message2.clone()).unwrap(); // Retrieve messages - let messages = db.get_messages_for_conversation(&conversation_id).unwrap(); + let messages = repository.get_messages_for_conversation(&conversation_id).unwrap(); assert_eq!(messages.len(), 2); // Verify first message (from Me) @@ -184,14 +191,15 @@ fn test_messages() { #[test] fn test_message_ordering() { - let mut db = ChatDatabase::new_in_memory().unwrap(); + let mut db = Database::new_in_memory().unwrap(); + let mut repository = db.get_repository(); // Create a conversation let conversation = ConversationBuilder::new() .display_name("Test Chat") .build(); let conversation_id = conversation.guid.clone(); - db.insert_conversation(conversation).unwrap(); + repository.insert_conversation(conversation).unwrap(); // Create messages with specific timestamps let now = chrono::Utc::now().naive_utc(); @@ -211,16 +219,31 @@ fn test_message_ordering() { .build(); // Insert messages - db.insert_message(&conversation_id, message1).unwrap(); - db.insert_message(&conversation_id, message2).unwrap(); - db.insert_message(&conversation_id, message3).unwrap(); + repository.insert_message(&conversation_id, message1).unwrap(); + repository.insert_message(&conversation_id, message2).unwrap(); + repository.insert_message(&conversation_id, message3).unwrap(); // Retrieve messages and verify order - let messages = db.get_messages_for_conversation(&conversation_id).unwrap(); + let messages = repository.get_messages_for_conversation(&conversation_id).unwrap(); assert_eq!(messages.len(), 3); // Messages should be ordered by date for i in 1..messages.len() { assert!(messages[i].date > messages[i-1].date); } -} \ No newline at end of file +} + +#[test] +fn test_settings() { + let mut db = Database::new_in_memory().unwrap(); + let mut settings = db.get_settings(); + + settings.put("test", &"test".to_string()).unwrap(); + assert_eq!(settings.get::("test").unwrap().unwrap(), "test"); + + settings.del("test").unwrap(); + assert!(settings.get::("test").unwrap().is_none()); + + let keys = settings.list_keys().unwrap(); + assert_eq!(keys.len(), 0); +} diff --git a/kpcli/src/daemon/mod.rs b/kpcli/src/daemon/mod.rs index 9e52757..8e5ae49 100644 --- a/kpcli/src/daemon/mod.rs +++ b/kpcli/src/daemon/mod.rs @@ -3,7 +3,7 @@ use clap::Subcommand; use dbus::blocking::{Connection, Proxy}; const DBUS_NAME: &str = "net.buzzert.kordophonecd"; -const DBUS_PATH: &str = "/net/buzzert/kordophone/Server"; +const DBUS_PATH: &str = "/net/buzzert/kordophonecd"; mod dbus_interface { #![allow(unused)] diff --git a/kpcli/src/db/mod.rs b/kpcli/src/db/mod.rs index eed8002..c95ae76 100644 --- a/kpcli/src/db/mod.rs +++ b/kpcli/src/db/mod.rs @@ -3,7 +3,7 @@ use clap::Subcommand; use kordophone::APIInterface; use std::{env, path::PathBuf}; -use kordophone_db::ChatDatabase; +use kordophone_db::{database::Database, repository::Repository, settings}; use crate::{client, printers::{ConversationPrinter, MessagePrinter}}; #[derive(Subcommand)] @@ -19,6 +19,12 @@ pub enum Commands { #[clap(subcommand)] command: MessageCommands }, + + /// For managing settings in the database. + Settings { + #[clap(subcommand)] + command: SettingsCommands + }, } #[derive(Subcommand)] @@ -38,6 +44,29 @@ pub enum MessageCommands { }, } +#[derive(Subcommand)] +pub enum SettingsCommands { + /// Lists all settings or gets a specific setting. + Get { + /// The key to get. If not provided, all settings will be listed. + key: Option + }, + + /// Sets a setting value. + Put { + /// The key to set. + key: String, + /// The value to set. + value: String, + }, + + /// Deletes a setting. + Delete { + /// The key to delete. + key: String, + }, +} + impl Commands { pub async fn run(cmd: Commands) -> Result<()> { let mut db = DbClient::new()?; @@ -49,12 +78,17 @@ impl Commands { Commands::Messages { command: cmd } => match cmd { MessageCommands::List { conversation_id } => db.print_messages(&conversation_id).await, }, + Commands::Settings { command: cmd } => match cmd { + SettingsCommands::Get { key } => db.get_setting(key), + SettingsCommands::Put { key, value } => db.put_setting(key, value), + SettingsCommands::Delete { key } => db.delete_setting(key), + }, } } } struct DbClient { - db: ChatDatabase + database: Database } impl DbClient { @@ -69,12 +103,12 @@ impl DbClient { println!("kpcli: Using temporary db at {}", path_str); - let db = ChatDatabase::new(path_str)?; - Ok( Self { db }) + let db = Database::new(path_str)?; + Ok( Self { database: db }) } pub fn print_conversations(&mut self) -> Result<()> { - let all_conversations = self.db.all_conversations()?; + let all_conversations = self.database.get_repository().all_conversations()?; println!("{} Conversations: ", all_conversations.len()); for conversation in all_conversations { @@ -85,7 +119,7 @@ impl DbClient { } pub async fn print_messages(&mut self, conversation_id: &str) -> Result<()> { - let messages = self.db.get_messages_for_conversation(conversation_id)?; + let messages = self.database.get_repository().get_messages_for_conversation(conversation_id)?; for message in messages { println!("{}", MessagePrinter::new(&message.into())); } @@ -99,18 +133,70 @@ impl DbClient { .map(|c| kordophone_db::models::Conversation::from(c)) .collect(); + let mut repository = self.database.get_repository(); for conversation in db_conversations { let conversation_id = conversation.guid.clone(); - self.db.insert_conversation(conversation)?; + repository.insert_conversation(conversation)?; // Fetch and sync messages for this conversation let messages = client.get_messages(&conversation_id).await?; - for message in messages { - let db_message = kordophone_db::models::Message::from(message); - self.db.insert_message(&conversation_id, db_message)?; + let db_messages: Vec = messages.into_iter() + .map(|m| kordophone_db::models::Message::from(m)) + .collect(); + + for message in db_messages { + repository.insert_message(&conversation_id, message)?; } } Ok(()) } + + pub fn get_setting(&mut self, key: Option) -> Result<()> { + let mut settings = self.database.get_settings(); + + match key { + Some(key) => { + // Get a specific setting + let value: Option = settings.get(&key)?; + match value { + Some(v) => println!("{} = {}", key, v), + None => println!("Setting '{}' not found", key), + } + }, + None => { + // List all settings + let keys = settings.list_keys()?; + if keys.is_empty() { + println!("No settings found"); + } else { + println!("Settings:"); + for key in keys { + let value: Option = settings.get(&key)?; + match value { + Some(v) => println!(" {} = {}", key, v), + None => println!(" {} = ", key), + } + } + } + } + } + + Ok(()) + } + + pub fn put_setting(&mut self, key: String, value: String) -> Result<()> { + let mut settings = self.database.get_settings(); + settings.put(&key, &value)?; + Ok(()) + } + + pub fn delete_setting(&mut self, key: String) -> Result<()> { + let mut settings = self.database.get_settings(); + let count = settings.del(&key)?; + if count == 0 { + println!("Setting '{}' not found", key); + } + Ok(()) + } }