adds kordophone-db
This commit is contained in:
78
kordophone-db/src/chat_database.rs
Normal file
78
kordophone-db/src/chat_database.rs
Normal 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
69
kordophone-db/src/lib.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
113
kordophone-db/src/models/conversation.rs
Normal file
113
kordophone-db/src/models/conversation.rs
Normal 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
|
||||
}
|
||||
}
|
||||
18
kordophone-db/src/models/date.rs
Normal file
18
kordophone-db/src/models/date.rs
Normal 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() }
|
||||
}
|
||||
}
|
||||
2
kordophone-db/src/models/mod.rs
Normal file
2
kordophone-db/src/models/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod conversation;
|
||||
pub mod date;
|
||||
Reference in New Issue
Block a user