zero4rs 2.0.0

zero4rs is a powerful, pragmatic, and extremely fast web framework for Rust
Documentation
pub mod models;
pub mod schemas;

use sqlx::MySql;
use sqlx::Pool;
use sqlx::QueryBuilder;
use sqlx::Row;
use sqlx_mysql::MySqlRow;

pub use models::*;
pub use schemas::*;

use crate::core::error2::Error;
use crate::core::error2::Result;

pub fn filter_db_record(note: &NoteModel) -> NoteModelResponse {
    NoteModelResponse {
        id: note.id.to_owned(),
        title: note.title.to_owned(),
        content: note.content.to_owned(),
        category: note.category.to_owned(),
        published: note.published != 0,
        createdAt: note.created_at.unwrap(),
        updatedAt: note.updated_at.unwrap(),
    }
}

pub fn filter_db_record2(r: &MySqlRow) -> NoteModelResponse {
    let id: String = r.get("id");
    let title: String = r.get("title");
    let content: String = r.get("content");
    let category: Option<String> = r.get("category");
    let published: i8 = r.get("published");
    let created_at: chrono::DateTime<chrono::Utc> = r.get("createdAt");
    let updated_at: chrono::DateTime<chrono::Utc> = r.get("updatedAt");

    NoteModelResponse {
        id,
        title,
        content,
        category,
        published: published != 0,
        createdAt: created_at,
        updatedAt: updated_at,
    }
}

pub async fn select_nodes(
    limit: usize,
    offset: usize,
    pool: &Pool<MySql>,
) -> Result<Vec<NoteModelResponse>> {
    let mut query_builder: QueryBuilder<MySql> =
        QueryBuilder::new("SELECT * FROM t_notes ORDER by id");

    query_builder.push(" LIMIT ");
    query_builder.push_bind(limit as i32);

    query_builder.push(" OFFSET ");
    query_builder.push_bind(offset as i32);

    let rows = query_builder.build().fetch_all(pool).await.map_err(|e| {
        Error::throw(
            "Failed to acquire a mysql connection from the pool",
            Some(e),
        )
    })?;

    Ok(rows
        .into_iter()
        .map(|note| filter_db_record2(&note))
        .collect::<Vec<NoteModelResponse>>())
}

pub async fn select_by_id(note_id: &str, pool: &Pool<MySql>) -> Result<Option<NoteModelResponse>> {
    let mut query_builder: QueryBuilder<MySql> =
        QueryBuilder::new("SELECT * FROM t_notes WHERE 1=1");

    query_builder.push(" id = ");
    query_builder.push_bind(note_id);

    let query_result = query_builder.build().fetch_one(pool).await;

    match query_result {
        Ok(note) => Ok(Some(filter_db_record2(&note))),
        Err(sqlx::Error::RowNotFound) => Ok(None),
        Err(e) => Err(Error::UnexpectedError(anyhow::anyhow!(e))),
    }
}

pub async fn save_note(record: &CreateNoteSchema, pool: &Pool<MySql>) -> Result<String> {
    let user_id = uuid::Uuid::now_v7().to_string();

    let insert_result =
        sqlx::query(r#"INSERT INTO t_notes (id,title,content,category) VALUES (?, ?, ?, ?)"#)
            .bind(user_id.clone())
            .bind(record.title.to_string())
            .bind(record.content.to_string())
            .bind(record.category.to_owned())
            .execute(pool)
            .await
            .map_err(|err: sqlx::Error| err.to_string());

    if let Err(err) = insert_result {
        if err.contains("Duplicate entry") {
            return Err(Error::UnexpectedError(anyhow::anyhow!(
                "Note with that title already exists"
            )));
        }

        return Err(Error::UnexpectedError(anyhow::anyhow!(err)));
    }

    Ok(user_id)
}

pub async fn note_update(
    note_id: &str,
    data: &UpdateNoteSchema,
    pool: &Pool<MySql>,
) -> Result<bool> {
    let note = select_by_id(note_id, pool).await?;

    let note = if let Some(s) = note {
        s
    } else {
        return Ok(false);
    };

    let published = data.published.unwrap_or(note.published);
    let i8_publised = published as i8;

    let update_result = sqlx::query(
        r#"UPDATE t_notes SET title = ?, content = ?, category = ?, published = ? WHERE id = ?"#,
    )
    .bind(data.title.to_owned().unwrap_or_else(|| note.title.clone()))
    .bind(
        data.content
            .to_owned()
            .unwrap_or_else(|| note.content.clone()),
    )
    .bind(if let Some(s) = data.category.to_owned() {
        Some(s)
    } else {
        note.category
    })
    .bind(i8_publised)
    .bind(note_id.to_owned())
    .execute(pool)
    .await;

    match update_result {
        Ok(result) => {
            if result.rows_affected() == 0 {
                Ok(false)
            } else {
                Ok(true)
            }
        }
        Err(e) => {
            log::error!("update-note-failed: error={:?}", e);
            Ok(false)
        }
    }
}

pub async fn delete_note_by_id(note_id: &str, pool: &Pool<MySql>) -> Result<bool> {
    let mut query_builder: QueryBuilder<MySql> =
        QueryBuilder::new(r#""DELETE FROM t_notes WHERE 1=1"#);

    query_builder.push(" and id = ");
    query_builder.push_bind(note_id);

    let query_result = query_builder.build().execute(pool).await;

    match query_result {
        Ok(result) => {
            if result.rows_affected() == 0 {
                Ok(false)
            } else {
                Ok(true)
            }
        }
        Err(e) => {
            log::error!("delete-note-failed: error={:?}", e);
            Err(Error::throw("delete-note-failed", Some(e)))
        }
    }
}