d3vlog 0.5.0

A tiny developer journal that lives in your terminal
use std::{env, io, path::PathBuf};

use chrono::{DateTime, Utc};
use rusqlite::Connection;

use crate::data::entry::DevLogEntry;

pub struct Store {
    connection: Connection,
}

impl Store {
    pub fn open() -> io::Result<Self> {
        let path = devlog_db_path()?;
        let opt_c = Connection::open(path);
        match opt_c {
            Ok(c) => {
                let s = Self { connection: c };
                s.prepare().map_err(io::Error::other)?;
                Ok(s)
            }
            Err(e) => Err(io::Error::other(e)),
        }
    }

    fn prepare(&self) -> rusqlite::Result<()> {
        self.connection.execute(
            "
                CREATE TABLE IF NOT EXISTS devlog_entries (
                    id          TEXT PRIMARY KEY NOT NULL,
                    created_at  TEXT NOT NULL CHECK (datetime(created_at) IS NOT NULL),
                    message     TEXT NOT NULL CHECK (length(trim(message)) > 0)
                )
            ",
            [],
        )?;

        Ok(())
    }

    pub fn insert_devlog_entry(&self, entry: DevLogEntry) -> rusqlite::Result<()> {
        self.connection.execute(
            "
                INSERT INTO devlog_entries VALUES (
                    ?1, ?2, ?3
                )
            ",
            (entry.id, entry.created_at.to_rfc3339(), entry.message),
        )?;

        Ok(())
    }

    pub fn get_entries(&self) -> rusqlite::Result<Vec<DevLogEntry>> {
        let mut stmt = self.connection.prepare(
            "
                SELECT * FROM devlog_entries
            ",
        )?;

        let entries = stmt.query_map([], |row| {
            let id: String = row.get("id")?;
            let created_at_text: String = row.get("created_at")?;
            let message: String = row.get("message")?;

            let created_at = DateTime::parse_from_rfc3339(&created_at_text)
                .map_err(|e| {
                    rusqlite::Error::FromSqlConversionFailure(
                        1,
                        rusqlite::types::Type::Text,
                        Box::new(e),
                    )
                })?
                .with_timezone(&Utc);

            Ok(DevLogEntry {
                id,
                created_at,
                message,
            })
        })?;

        entries.collect()
    }
}

fn devlog_db_path() -> io::Result<PathBuf> {
    let home = env::var_os("HOME")
        .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "HOME is not set"))?;

    let mut path = PathBuf::from(home);
    path.push(".devlog");

    std::fs::create_dir_all(&path)?;

    path.push("entries.sqlite");

    Ok(path)
}