Private
Public Access
1
0

adds kordophone-db

This commit is contained in:
2024-12-08 21:12:17 -08:00
parent 75d4767009
commit fac9b1ffe6
8 changed files with 398 additions and 26 deletions

130
Cargo.lock generated
View File

@@ -110,7 +110,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -221,7 +221,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -236,6 +236,12 @@ 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"
@@ -259,7 +265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
dependencies = [
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -278,6 +284,12 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "env_filter"
version = "0.1.2"
@@ -553,6 +565,15 @@ 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"
@@ -588,6 +609,17 @@ dependencies = [
"uuid",
]
[[package]]
name = "kordophone-db"
version = "0.1.0"
dependencies = [
"chrono",
"microrm",
"serde",
"time",
"uuid",
]
[[package]]
name = "kpcli"
version = "0.1.0"
@@ -613,6 +645,16 @@ version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "libsqlite3-sys"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
@@ -641,6 +683,33 @@ version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "microrm"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7119c145e9ee33d8a79a2c7e15c4f429d681079cea976692a55c78aaae34f5ae"
dependencies = [
"itertools",
"libsqlite3-sys",
"log",
"microrm-macros",
"serde",
"serde_json",
"time",
]
[[package]]
name = "microrm-macros"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c3e29bc1b3c1dc742de1b3a23f11e472a2adc936a967dc5d769c26a809326b8"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "miniz_oxide"
version = "0.7.2"
@@ -733,7 +802,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -821,9 +890,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.81"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
@@ -970,22 +1039,22 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.198"
version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.198"
version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -1050,9 +1119,20 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.60"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
@@ -1082,9 +1162,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.36"
version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
"num-conv",
@@ -1102,9 +1182,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.18"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
@@ -1136,7 +1216,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -1220,9 +1300,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.8.0"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
dependencies = [
"getrandom",
"rand",
@@ -1231,13 +1311,13 @@ dependencies = [
[[package]]
name = "uuid-macro-internal"
version = "1.8.0"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9881bea7cbe687e36c9ab3b778c36cd0487402e270304e8b1296d5085303c1a2"
checksum = "6b91f57fe13a38d0ce9e28a03463d8d3c2468ed03d75375110ec71d93b449a08"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -1283,7 +1363,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
"wasm-bindgen-shared",
]
@@ -1305,7 +1385,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@@ -1,6 +1,7 @@
[workspace]
members = [
"kordophone",
"kordophone-db",
"kpcli"
]
resolver = "2"

11
kordophone-db/Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[package]
name = "kordophone-db"
version = "0.1.0"
edition = "2021"
[dependencies]
chrono = "0.4.38"
microrm = "0.4.4"
serde = { version = "1.0.215", features = ["derive"] }
time = "0.3.37"
uuid = { version = "1.11.0", features = ["v4"] }

View File

@@ -0,0 +1,78 @@
use microrm::prelude::*;
use microrm::Stored;
use crate::models::conversation::{self, Conversation, ConversationID};
use std::error::Error;
pub struct ChatDatabase {
db: DB,
}
#[derive(Database)]
struct DB {
conversations: microrm::IDMap<Conversation>,
}
impl ChatDatabase {
pub fn new_in_memory() -> Result<Self, Box<dyn Error + Send + Sync>> {
let db = DB::open_path(":memory:")?;
return Ok(Self {
db: db,
})
}
pub fn insert_conversation(&self, conversation: Conversation) -> Result<ConversationID, microrm::Error> {
// First see if conversation guid already exists, update it if so
let guid = &conversation.guid;
let mut existing = self.db.conversations
.with(Conversation::Guid, guid)
.get()?;
if let Some(existing) = existing.first_mut() {
existing.display_name = conversation.display_name;
existing.sync();
return Ok(existing.id());
} else {
// Otherwise, insert.
return self.db.conversations.insert(conversation);
}
}
pub fn get_conversation_by_id(&self, id: ConversationID) -> Result<Option<Conversation>, microrm::Error> {
self.db.conversations
.by_id(id)
.map(|stored_conversation| stored_conversation
.map(|stored| stored.wrapped())
)
}
pub fn get_conversation_by_guid(&self, guid: &str) -> Result<Option<Conversation>, 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(&self) -> Result<Vec<Conversation>, microrm::Error> {
self.db.conversations
.get()
.map(|v| v
.into_iter()
.map(|c| c.wrapped())
.collect()
)
}
fn stored_conversation_by_guid(&self, guid: &str) -> Result<Option<Stored<Conversation>>, microrm::Error> {
self.db.conversations
.with(Conversation::Guid, guid)
.get()
.map(|v| v
.into_iter()
.last()
)
}
}

69
kordophone-db/src/lib.rs Normal file
View File

@@ -0,0 +1,69 @@
pub mod models;
pub mod chat_database;
#[cfg(test)]
mod tests {
use microrm::prelude::Queryable;
use crate::{chat_database::{self, ChatDatabase}, models::conversation::{Conversation, ConversationBuilder, Participant}};
#[test]
fn test_database_init() {
let _ = ChatDatabase::new_in_memory().unwrap();
}
#[test]
fn test_add_conversation() {
let db = ChatDatabase::new_in_memory().unwrap();
let test_conversation = Conversation::builder()
.guid("test")
.unread_count(2)
.display_name("Test Conversation")
.build();
let id = db.insert_conversation(test_conversation.clone()).unwrap();
// Try to fetch with id now
let conversation = db.get_conversation_by_id(id).unwrap().unwrap();
assert_eq!(conversation.guid, "test");
// Modify the conversation and update it
let modified_conversation = test_conversation.into_builder()
.display_name("Modified Conversation")
.build();
db.insert_conversation(modified_conversation.clone()).unwrap();
// Make sure we still only have one conversation.
let all_conversations = db.all_conversations().unwrap();
assert_eq!(all_conversations.len(), 1);
// And make sure the display name was updated
let conversation = db.get_conversation_by_id(id).unwrap().unwrap();
assert_eq!(conversation.display_name.unwrap(), "Modified Conversation");
}
#[test]
fn test_conversation_participants() {
let db = ChatDatabase::new_in_memory().unwrap();
let participants: Vec<String> = vec!["one".into(), "two".into()];
let conversation = ConversationBuilder::new()
.display_name("Test")
.participant_display_names(participants.clone())
.build();
let id = db.insert_conversation(conversation).unwrap();
let read_conversation = db.get_conversation_by_id(id).unwrap().unwrap();
let read_participants: Vec<String> = read_conversation.participant_display_names
.get()
.unwrap()
.into_iter()
.map(|p| p.wrapped().display_name)
.collect();
assert_eq!(participants, read_participants);
}
}

View File

@@ -0,0 +1,113 @@
use microrm::prelude::*;
use chrono::{DateTime, Local, Utc};
use time::OffsetDateTime;
use uuid::Uuid;
use crate::models::date::Date;
#[derive(Entity, Clone)]
pub struct Conversation {
#[unique]
pub guid: String,
pub unread_count: i64,
pub display_name: Option<String>,
pub last_message_preview: Option<String>,
pub date: OffsetDateTime,
pub participant_display_names: microrm::RelationMap<Participant>,
}
#[derive(Entity, Clone)]
pub struct Participant {
#[unique]
pub display_name: String
}
impl Into<Participant> for String {
fn into(self) -> Participant {
Participant { display_name: self }
}
}
impl Conversation {
pub fn builder() -> ConversationBuilder {
ConversationBuilder::new()
}
pub fn into_builder(&self) -> ConversationBuilder {
ConversationBuilder {
guid: Some(self.guid.clone()),
date: Date::new(self.date),
participant_display_names: None,
unread_count: Some(self.unread_count),
last_message_preview: self.last_message_preview.clone(),
display_name: self.display_name.clone(),
}
}
}
#[derive(Default)]
pub struct ConversationBuilder {
guid: Option<String>,
date: Date,
unread_count: Option<i64>,
last_message_preview: Option<String>,
participant_display_names: Option<Vec<String>>,
display_name: Option<String>,
}
impl ConversationBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn guid(mut self, guid: &str) -> Self {
self.guid = Some(guid.into());
self
}
pub fn date(mut self, date: Date) -> Self {
self.date = date;
self
}
pub fn unread_count(mut self, unread_count: i64) -> Self {
self.unread_count = Some(unread_count);
self
}
pub fn last_message_preview(mut self, last_message_preview: &str) -> Self {
self.last_message_preview = Some(last_message_preview.into());
self
}
pub fn participant_display_names(mut self, participant_display_names: Vec<String>) -> Self {
self.participant_display_names = Some(participant_display_names);
self
}
pub fn display_name(mut self, display_name: &str) -> Self {
self.display_name = Some(display_name.into());
self
}
pub fn build(self) -> Conversation {
let result = Conversation {
guid: self.guid.unwrap_or(Uuid::new_v4().to_string()),
unread_count: self.unread_count.unwrap_or(0),
last_message_preview: self.last_message_preview,
display_name: self.display_name,
date: self.date.dt,
participant_display_names: Default::default(),
};
// TODO: this isn't right... this is crashing the test.
if let Some(participants) = self.participant_display_names {
for participant in participants {
result.participant_display_names.insert(participant.into()).unwrap();
}
}
result
}
}

View File

@@ -0,0 +1,18 @@
use chrono::{DateTime, Local, Utc};
use time::OffsetDateTime;
pub struct Date {
pub dt: OffsetDateTime,
}
impl Date {
pub fn new(dt: OffsetDateTime) -> Self {
Self { dt }
}
}
impl Default for Date {
fn default() -> Self {
Self { dt: OffsetDateTime::now_utc() }
}
}

View File

@@ -0,0 +1,2 @@
pub mod conversation;
pub mod date;