kpcli: adds 'db' subcommand for interacting with the database
This commit is contained in:
@@ -10,6 +10,8 @@ anyhow = "1.0.93"
|
||||
clap = { version = "4.5.20", features = ["derive"] }
|
||||
dotenv = "0.15.0"
|
||||
kordophone = { path = "../kordophone" }
|
||||
kordophone-db = { path = "../kordophone-db" }
|
||||
log = "0.4.22"
|
||||
pretty = { version = "0.12.3", features = ["termcolor"] }
|
||||
time = "0.3.37"
|
||||
tokio = "1.41.1"
|
||||
|
||||
@@ -3,9 +3,28 @@ use kordophone::api::http_client::HTTPAPIClient;
|
||||
use kordophone::api::http_client::Credentials;
|
||||
|
||||
use dotenv;
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use crate::printers::ConversationPrinter;
|
||||
|
||||
pub fn make_api_client_from_env() -> HTTPAPIClient {
|
||||
dotenv::dotenv().ok();
|
||||
|
||||
// read from env
|
||||
let base_url = std::env::var("KORDOPHONE_API_URL")
|
||||
.expect("KORDOPHONE_API_URL must be set");
|
||||
|
||||
let credentials = Credentials {
|
||||
username: std::env::var("KORDOPHONE_USERNAME")
|
||||
.expect("KORDOPHONE_USERNAME must be set"),
|
||||
|
||||
password: std::env::var("KORDOPHONE_PASSWORD")
|
||||
.expect("KORDOPHONE_PASSWORD must be set"),
|
||||
};
|
||||
|
||||
HTTPAPIClient::new(base_url.parse().unwrap(), credentials.into())
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Commands {
|
||||
/// Prints all known conversations on the server.
|
||||
@@ -16,7 +35,7 @@ pub enum Commands {
|
||||
}
|
||||
|
||||
impl Commands {
|
||||
pub async fn run(cmd: Commands) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub async fn run(cmd: Commands) -> Result<()> {
|
||||
let mut client = ClientCli::new();
|
||||
match cmd {
|
||||
Commands::Version => client.print_version().await,
|
||||
@@ -31,34 +50,20 @@ struct ClientCli {
|
||||
|
||||
impl ClientCli {
|
||||
pub fn new() -> Self {
|
||||
dotenv::dotenv().ok();
|
||||
|
||||
// read from env
|
||||
let base_url = std::env::var("KORDOPHONE_API_URL")
|
||||
.expect("KORDOPHONE_API_URL must be set");
|
||||
|
||||
let credentials = Credentials {
|
||||
username: std::env::var("KORDOPHONE_USERNAME")
|
||||
.expect("KORDOPHONE_USERNAME must be set"),
|
||||
|
||||
password: std::env::var("KORDOPHONE_PASSWORD")
|
||||
.expect("KORDOPHONE_PASSWORD must be set"),
|
||||
};
|
||||
|
||||
let api = HTTPAPIClient::new(base_url.parse().unwrap(), credentials.into());
|
||||
let api = make_api_client_from_env();
|
||||
Self { api: api }
|
||||
}
|
||||
|
||||
pub async fn print_version(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub async fn print_version(&mut self) -> Result<()> {
|
||||
let version = self.api.get_version().await?;
|
||||
println!("Version: {}", version);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn print_conversations(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub async fn print_conversations(&mut self) -> Result<()> {
|
||||
let conversations = self.api.get_conversations().await?;
|
||||
for conversation in conversations {
|
||||
println!("{}", ConversationPrinter::new(&conversation));
|
||||
println!("{}", ConversationPrinter::new(&conversation.into()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
83
kpcli/src/db/mod.rs
Normal file
83
kpcli/src/db/mod.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use kordophone::APIInterface;
|
||||
use std::{env, path::{Path, PathBuf}};
|
||||
|
||||
use kordophone_db::ChatDatabase;
|
||||
use crate::{client, printers::ConversationPrinter};
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Commands {
|
||||
/// For dealing with the table of cached conversations.
|
||||
Conversations {
|
||||
#[clap(subcommand)]
|
||||
command: ConversationCommands
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum ConversationCommands {
|
||||
/// Lists all conversations currently in the database.
|
||||
List,
|
||||
|
||||
/// Syncs with an API client.
|
||||
Sync,
|
||||
}
|
||||
|
||||
impl Commands {
|
||||
pub async fn run(cmd: Commands) -> Result<()> {
|
||||
let mut db = DbClient::new()?;
|
||||
match cmd {
|
||||
Commands::Conversations { command: cmd } => match cmd {
|
||||
ConversationCommands::List => db.print_conversations(),
|
||||
ConversationCommands::Sync => db.sync_with_client().await,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DbClient {
|
||||
db: ChatDatabase
|
||||
}
|
||||
|
||||
impl DbClient {
|
||||
fn database_path() -> PathBuf {
|
||||
let temp_dir = env::temp_dir();
|
||||
temp_dir.join("kpcli_chat.db")
|
||||
}
|
||||
|
||||
pub fn new() -> Result<Self> {
|
||||
let path = Self::database_path();
|
||||
let path_str: &str = path.as_path().to_str().unwrap();
|
||||
|
||||
println!("kpcli: Using temporary db at {}", path_str);
|
||||
|
||||
let db = ChatDatabase::new(path_str)?;
|
||||
Ok( Self { db })
|
||||
}
|
||||
|
||||
pub fn print_conversations(&mut self) -> Result<()> {
|
||||
let all_conversations = self.db.all_conversations()?;
|
||||
|
||||
println!("{} Conversations: ", all_conversations.len());
|
||||
for conversation in all_conversations {
|
||||
println!("{}", ConversationPrinter::new(&conversation.into()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn sync_with_client(&mut self) -> Result<()> {
|
||||
let mut client = client::make_api_client_from_env();
|
||||
let fetched_conversations = client.get_conversations().await?;
|
||||
let db_conversations: Vec<kordophone_db::models::Conversation> = fetched_conversations.into_iter()
|
||||
.map(|c| kordophone_db::models::Conversation::from(c))
|
||||
.collect();
|
||||
|
||||
for conversation in db_conversations {
|
||||
self.db.insert_conversation(conversation)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
mod client;
|
||||
mod db;
|
||||
mod printers;
|
||||
mod client;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
/// A command line interface for the Kordophone library and daemon
|
||||
@@ -17,11 +20,18 @@ enum Commands {
|
||||
#[command(subcommand)]
|
||||
command: client::Commands,
|
||||
},
|
||||
|
||||
/// Commands for the cache database
|
||||
Db {
|
||||
#[command(subcommand)]
|
||||
command: db::Commands,
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_command(command: Commands) -> Result<(), Box<dyn std::error::Error>> {
|
||||
async fn run_command(command: Commands) -> Result<()> {
|
||||
match command {
|
||||
Commands::Client { command } => client::Commands::run(command).await,
|
||||
Commands::Db { command } => db::Commands::run(command).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,48 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use time::OffsetDateTime;
|
||||
use pretty::RcDoc;
|
||||
use kordophone::model::Conversation;
|
||||
|
||||
pub struct PrintableConversation {
|
||||
pub guid: String,
|
||||
pub date: OffsetDateTime,
|
||||
pub unread_count: i32,
|
||||
pub last_message_preview: Option<String>,
|
||||
pub participants: Vec<String>,
|
||||
pub display_name: Option<String>,
|
||||
}
|
||||
|
||||
impl From<kordophone::model::Conversation> for PrintableConversation {
|
||||
fn from(value: kordophone::model::Conversation) -> Self {
|
||||
Self {
|
||||
guid: value.guid,
|
||||
date: value.date,
|
||||
unread_count: value.unread_count,
|
||||
last_message_preview: value.last_message_preview,
|
||||
participants: value.participant_display_names,
|
||||
display_name: value.display_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<kordophone_db::models::Conversation> for PrintableConversation {
|
||||
fn from(value: kordophone_db::models::Conversation) -> Self {
|
||||
Self {
|
||||
guid: value.guid,
|
||||
date: OffsetDateTime::from_unix_timestamp(value.date.and_utc().timestamp()).unwrap(),
|
||||
unread_count: value.unread_count.into(),
|
||||
last_message_preview: value.last_message_preview,
|
||||
participants: value.participants.into_iter().map(|p| p.display_name).collect(),
|
||||
display_name: value.display_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConversationPrinter<'a> {
|
||||
doc: RcDoc<'a, Conversation>
|
||||
doc: RcDoc<'a, PrintableConversation>
|
||||
}
|
||||
|
||||
impl<'a> ConversationPrinter<'a> {
|
||||
pub fn new(conversation: &'a Conversation) -> Self {
|
||||
pub fn new(conversation: &'a PrintableConversation) -> Self {
|
||||
let preview = conversation.last_message_preview
|
||||
.as_deref()
|
||||
.unwrap_or("<null>")
|
||||
@@ -27,7 +61,7 @@ impl<'a> ConversationPrinter<'a> {
|
||||
.append("[")
|
||||
.append(RcDoc::line()
|
||||
.append(
|
||||
conversation.participant_display_names
|
||||
conversation.participants
|
||||
.iter()
|
||||
.map(|name|
|
||||
RcDoc::text(name)
|
||||
|
||||
Reference in New Issue
Block a user