caracal 0.2.0

Nostr client for Gemini
use crate::Url;
use crate::storage::Storage;
use heed::types::{SerdeRmp, Str};
use heed::{Database, RoTxn};
use serde::{Deserialize, Serialize};
use std::error::Error;

use chrono::{DateTime, Utc};

#[derive(Debug, Serialize, Deserialize)]
pub struct TextNoteDraft<'a> {
    #[serde(borrow)]
    pub content: &'a str,
    pub datetime_created: DateTime<Utc>,
    pub datetime_expires: Option<DateTime<Utc>>,
    pub datetime_autopublish: Option<DateTime<Utc>>,
    pub attachments: Vec<&'a str>,
}

type DraftsDatabase<'a> = Database<Str, SerdeRmp<TextNoteDraft<'a>>>;

impl<'a> TextNoteDraft<'a> {
    pub fn render_text(&self) -> String {
        let mut body = self.content.to_string();

        body.push('\n');

        for url in self.attachments.iter() {
            body.push_str(url);
            body.push('\n');
        }

        body
    }
}

const DB_NAME: &str = "note_drafts";

impl Storage {
    fn drafts_db(
        &self,
    ) -> Result<DraftsDatabase<'_>, Box<dyn Error + Send + Sync>> {
        let mut rtxn = self.get_read_txn()?;
        let dbo: Option<DraftsDatabase> =
            self.env.open_database(&mut rtxn, Some(DB_NAME))?;

        if dbo.is_some() {
            Ok(dbo.unwrap())
        } else {
            Err(Box::from("Error opening nostr keys db"))
        }
    }

    pub fn create_drafts_db(
        &self,
    ) -> Result<DraftsDatabase<'_>, Box<dyn Error + Send + Sync>> {
        let mut wtxn = self.env.write_txn()?;
        let db: DraftsDatabase =
            self.env.create_database(&mut wtxn, Some(DB_NAME))?;
        wtxn.commit()?;
        Ok(db)
    }

    pub fn draft_store(
        &self,
        name: &String,
        content: &str,
    ) -> Result<(), Box<dyn Error>> {
        if let Ok(db) = self.drafts_db() {
            let mut wtxn = self.env.write_txn()?;

            let draft = TextNoteDraft {
                content,
                datetime_created: Utc::now(),
                datetime_expires: None,
                datetime_autopublish: None,
                attachments: Vec::new(),
            };

            db.put(&mut wtxn, name.as_str(), &draft)?;
            wtxn.commit()?;
        }
        Ok(())
    }

    pub fn draft_attach<'c>(
        &'c self,
        rtxn: &'c mut RoTxn,
        name: &String,
        url: Url,
    ) -> Result<bool, Box<dyn Error>> {
        if let Ok(db) = self.drafts_db() {
            return match db.get(rtxn, name)? {
                Some(mut draft) => {
                    let mut wtxn = self.env.write_txn()?;

                    draft.attachments.push(url.as_str());
                    db.put(&mut wtxn, name.as_str(), &draft)?;

                    wtxn.commit()?;
                    Ok(true)
                }
                None => Err(Box::from("Cannot get draft")),
            };
        }
        Ok(false)
    }

    pub fn draft_get_content<'c>(
        &'c self,
        name: &String,
        rtxn: &'c mut RoTxn,
    ) -> Result<&'c str, Box<dyn Error + Send + Sync>> {
        match self.drafts_db()?.get(rtxn, name)? {
            Some(draft) => Ok(draft.content),
            None => Err(Box::from("Cannot get draft")),
        }
    }

    pub fn draft_get<'c>(
        &'c self,
        name: &String,
        rtxn: &'c mut RoTxn,
    ) -> Result<TextNoteDraft<'c>, Box<dyn Error + Send + Sync>> {
        match self.drafts_db()?.get(rtxn, name)? {
            Some(draft) => Ok(draft),
            None => Err(Box::from("Cannot get draft")),
        }
    }
}