elmo 0.0.1

Library for easy creation of persistent notifications
Documentation
use async_trait::async_trait;
use chrono::NaiveDateTime;
use eyre::Result;
use serde::{Deserialize, Serialize};
use sqlx::sqlite::{SqlitePool, SqliteRow};
use sqlx::{FromRow, Row};

#[derive(Serialize, Deserialize)]
pub struct EventRequest {
    pub name: String,
    pub description: String,
    pub date: String,
    pub icon_name: Option<String>,
    pub sound_file_path: Option<String>,
}

#[derive(Serialize, FromRow)]
pub struct Event {
    pub id: i64,
    pub name: String,
    pub description: Option<String>,
    pub date: String,
    pub icon_name: Option<String>,
    pub sound_file_path: Option<String>,
}

#[derive(Serialize, Deserialize)]
pub struct CronEventRequest {
    pub name: String,
    pub description: String,
    pub cron_string: String,
    pub icon_name: Option<String>,
    pub sound_file_path: Option<String>,
}

#[derive(Serialize, FromRow)]
pub struct CronEvent {
    pub id: i64,
    pub name: String,
    pub description: Option<String>,
    pub cron_string: String,
    pub icon_name: Option<String>,
    pub sound_file_path: Option<String>,
    pub last_notification: Option<String>,
}

async fn get_max_date(pool: &SqlitePool) -> Result<String> {
    let max_date = sqlx::query("SELECT datetime(MAX(event_date), '+1 hour') AS date FROM events")
        .fetch_one(&*pool)
        .await?;
    Ok(max_date.get(0))
}

#[async_trait]
pub trait EventRepo<T, U> {
    async fn get_event(pool: &SqlitePool, id: i64) -> Result<U>;
    async fn create_event(pool: &SqlitePool, request: T) -> Result<U>;
    async fn delete_event(pool: &SqlitePool, id: i64) -> Result<()>;
    async fn create_table(pool: &SqlitePool) -> Result<()>;
}

#[async_trait]
impl EventRepo<EventRequest, Event> for Event {
    async fn get_event(pool: &SqlitePool, id: i64) -> Result<Event> {
        let response = sqlx::query("SELECT * FROM events WHERE id = ?")
            .bind(id)
            .map(|row: SqliteRow| Event {
                id: row.get(0),
                name: row.get(1),
                description: row.get(2),
                date: row.get(3),
                icon_name: row.get(4),
                sound_file_path: row.get(5),
            })
            .fetch_one(&*pool)
            .await?;

        Ok(response)
    }

    async fn create_event(pool: &SqlitePool, request: EventRequest) -> Result<Event> {
        let mut transmitter = pool.begin().await?;
        let event = sqlx::query(
            "
            INSERT INTO events
            (name, description, event_date, icon, sound_file_path)
            VALUES ($1, $2, $3, $4, $5)
            RETURNING id, name, description, event_date, icon, sound_file_path
            ",
        )
        .bind(&request.name)
        .bind(&request.description)
        .bind(&request.date)
        .bind(&request.icon_name)
        .bind(&request.sound_file_path)
        .map(|row: SqliteRow| Event {
            id: row.get(0),
            name: row.get(1),
            description: row.get(2),
            date: row.get(3),
            icon_name: row.get(4),
            sound_file_path: row.get(5),
        })
        .fetch_one(&mut transmitter)
        .await?;

        transmitter.commit().await?;
        Ok(event)
    }

    async fn delete_event(pool: &SqlitePool, id: i64) -> Result<()> {
        sqlx::query("DELETE FROM events WHERE id = $1")
            .bind(id)
            .execute(pool)
            .await?;
        Ok(())
    }

    async fn create_table(pool: &SqlitePool) -> Result<()> {
        sqlx::query(
            "CREATE TABLE IF NOT EXISTS events(
                 id INTEGER PRIMARY KEY NOT NULL,
                 name TEXT NOT NULL,
                 description TEXT NULL,
                 event_date TEXT NOT NULL,
                 icon TEXT NULL,
                 sound_file_path TEXT NULL
                 )
             ",
        )
        .execute(pool)
        .await?;
        Ok(())
    }
}

impl Event {
    pub async fn find_current_events(pool: &SqlitePool, date: String) -> Result<Vec<Event>> {
        let response =
            sqlx::query("SELECT * FROM events WHERE event_date <= ? ORDER BY event_date")
                .bind(date)
                .map(|row: SqliteRow| Event {
                    id: row.get(0),
                    name: row.get(1),
                    description: row.get(2),
                    date: row.get(3),
                    icon_name: row.get(4),
                    sound_file_path: row.get(5),
                })
                .fetch_all(&*pool)
                .await?;

        Ok(response)
    }

    pub async fn find_future_events(
        pool: &SqlitePool,
        date: String,
        end_date: Option<String>,
    ) -> Result<Vec<Event>> {
        let range_end: String;
        if let Some(option_date) = end_date {
            range_end = option_date;
        } else {
            range_end = get_max_date(pool).await?;
        }
        let response = sqlx::query(
            "SELECT * FROM events WHERE event_date BETWEEN $1 AND $2 ORDER BY event_date",
        )
        .bind(date)
        .bind(range_end)
        .map(|row: SqliteRow| Event {
            id: row.get(0),
            name: row.get(1),
            description: row.get(2),
            date: row.get(3),
            icon_name: row.get(4),
            sound_file_path: row.get(5),
        })
        .fetch_all(&*pool)
        .await?;

        Ok(response)
    }
}

#[async_trait]
impl EventRepo<CronEventRequest, CronEvent> for CronEvent {
    async fn get_event(pool: &SqlitePool, id: i64) -> Result<CronEvent> {
        let response = sqlx::query("SELECT * FROM recurring_events WHERE id = ?")
            .bind(id)
            .map(|row: SqliteRow| CronEvent {
                id: row.get(0),
                name: row.get(1),
                description: row.get(2),
                cron_string: row.get(3),
                icon_name: row.get(4),
                sound_file_path: row.get(5),
                last_notification: row.get(6),
            })
            .fetch_one(&*pool)
            .await?;

        Ok(response)
    }

    async fn create_event(pool: &SqlitePool, request: CronEventRequest) -> Result<CronEvent> {
        let mut transmitter = pool.begin().await?;
        let event = sqlx::query(
            "
            INSERT INTO recurring_events
            (name, description, cron_string, icon, sound_file_path)
            VALUES ($1, $2, $3, $4, $5)
            RETURNING id, name, description, cron_string, icon, sound_file_path
            ",
        )
        .bind(&request.name)
        .bind(&request.description)
        .bind(&request.cron_string)
        .bind(&request.icon_name)
        .bind(&request.sound_file_path)
        .map(|row: SqliteRow| CronEvent {
            id: row.get(0),
            name: row.get(1),
            description: row.get(2),
            cron_string: row.get(3),
            icon_name: row.get(4),
            sound_file_path: row.get(5),
            last_notification: None,
        })
        .fetch_one(&mut transmitter)
        .await?;

        transmitter.commit().await?;
        Ok(event)
    }

    async fn delete_event(pool: &SqlitePool, id: i64) -> Result<()> {
        sqlx::query("DELETE FROM recurring_events WHERE id = $1")
            .bind(id)
            .execute(pool)
            .await?;
        Ok(())
    }

    async fn create_table(pool: &SqlitePool) -> Result<()> {
        sqlx::query(
            "CREATE TABLE IF NOT EXISTS recurring_events(
                 id INTEGER PRIMARY KEY NOT NULL,
                 name TEXT NOT NULL,
                 description TEXT NULL,
                 cron_string TEXT NOT NULL,
                 icon TEXT NULL,
                 sound_file_path TEXT NULL,
                 last_notification TEXT NULL
                 )
             ",
        )
        .execute(pool)
        .await?;
        Ok(())
    }
}

impl CronEvent {
    pub async fn find_events(pool: &SqlitePool) -> Result<Vec<CronEvent>> {
        let response = sqlx::query("SELECT * FROM recurring_events")
            .map(|row: SqliteRow| CronEvent {
                id: row.get(0),
                name: row.get(1),
                description: row.get(2),
                cron_string: row.get(3),
                icon_name: row.get(4),
                sound_file_path: row.get(5),
                last_notification: row.get(6),
            })
            .fetch_all(&*pool)
            .await?;

        Ok(response)
    }

    pub async fn update_last_notification_date(
        pool: &SqlitePool,
        id: i64,
        new_date: NaiveDateTime,
    ) -> Result<()> {
        sqlx::query("UPDATE recurring_events SET last_notification = $1 WHERE id = $2")
            .bind(new_date)
            .bind(id)
            .execute(pool)
            .await?;
        Ok(())
    }
}