comment_app_backend 0.1.3

Serves comments through Restful APIs
Documentation
use std::convert::Infallible;
use std::collections::HashMap;
use warp::http::StatusCode;
use log::*;

use crate::db::{self, DbConn};
use crate::models::comment::{self, Comment};
use crate::models::reply::{self, Reply};
use crate::models::upvote::{self, Upvote};
use crate::models::downvote::{self, Downvote};
use crate::models::status::Status;

pub async fn list_comments(
    db_conn: DbConn,
) -> Result<impl warp::Reply, Infallible> {
    let mut results: Vec<Comment> = Vec::new();
    match db::get_all_comments(db_conn).await {
        Ok(data) => results = data,
        Err(message) => error!("{:?} occurred in handlers::list_comments()", message),
    };
    let filtered: Vec<Comment> = results.into_iter().filter(|each| 
        each.status == Status::Pending 
        || each.status == Status::Approved 
        || each.status == Status:: Disabled 
    ).collect();
    Ok( warp::reply::json( &filtered ) )
}

pub async fn more_comments(
    count: usize,
    db_conn: DbConn,
) -> Result<impl warp::Reply, Infallible> {
    let mut results: Vec<Comment> = Vec::new();
    match db::get_more_comments(count, db_conn).await {
        Ok(data) => results = data,
        Err(message) => error!("{:?} occurred in handlers::more_comments()", message),
    };
    let filtered: Vec<Comment> = results.into_iter().filter(|each| 
        each.status == Status::Pending 
        || each.status == Status::Approved 
        || each.status == Status:: Disabled 
    ).collect();
    Ok( warp::reply::json( &filtered ) )    
}

pub async fn add_comment(
    new_comment: Comment,
    db_conn: DbConn,
) -> Result<impl warp::Reply, Infallible> {
    debug!("add comment: {:?}", new_comment);
    let mut comment_clone = new_comment.clone();
    comment::assign_uid(&mut comment_clone);
    comment::assign_created_date(&mut comment_clone);
    let result = db::add_comment(&comment_clone, db_conn).await;
    let code = process_db_result( result );
    Ok(code)
}

fn process_db_result(result: Result<usize, rusqlite::Error>) -> warp::http::StatusCode {
    match result {
        Ok(count) => {
            match count {
                1 => StatusCode::NO_CONTENT,
                _ => StatusCode::NOT_FOUND
            }
        },
        Err(error) => {
            error!("Error: {} occurred in process_db_result...........", &error);
            StatusCode::NOT_FOUND
        }
    }
}

pub async fn update_comment(
    uid: String,
    mut comment: Comment,
    db_conn: DbConn,
) -> Result<impl warp::Reply, Infallible> {
    debug!("update comment: uid = {}, comment = {:?}", uid, comment);
    comment.unique_id = uid;
    comment.remarks = String::from("several fields are updated");
    comment::assign_updated_date(&mut comment);
    let result = db::update_comment(&comment, db_conn).await;
    let code = process_db_result( result );
    Ok(code)
}

pub async fn update_comment_status(
    stat: String,
    uid: String,
    db_conn: DbConn,
) -> Result<impl warp::Reply, Infallible> {
    debug!("update comment status: stat = {}, uid = {}", &stat, uid);
    let now: String = comment::timestamp_now();
    let status: Status = Status::from_string(stat);
    let result = db::update_comment_status(status, uid, now, db_conn).await;
    let code = process_db_result( result );
    Ok(code)
}

pub async fn update_comment_message(
    uid: String,
    map: HashMap<String, String>,
    db_conn: DbConn,
) -> Result<impl warp::Reply, Infallible> {
    debug!("update comment message: uid = {}, new-message: {:?}+++++++++++++++++++++++++++", &uid, &map);
    let now: String = comment::timestamp_now();
    let msg = map.get("message").unwrap();
    let result = db::update_comment_message(msg.to_string(), uid, now, db_conn).await;
    let code = process_db_result(result);
    Ok(code)
}

pub async fn delete_comment(
    uid: String, 
    db_conn: DbConn
) -> Result<impl warp::Reply, Infallible> {
    debug!("delete comment: uid = {}", uid);
    let now: String = comment::timestamp_now();
    let result = db::delete_comment(&uid, now, db_conn).await;
    let code = process_db_result( result );
    Ok(code)
}

pub async fn get_comment(
    uid: String, 
    db_conn: DbConn
) -> Result<impl warp::Reply, Infallible> {
    // just return a json of comment for the given uid 
    debug!("get comment: uid = {}", uid);
    match db::get_comment(&uid, db_conn).await {
        Ok(data) => Ok( warp::reply::json( &data ) ),
        Err(message) => {
            error!("{} occurred in handlers::get_comment()", message);
            Ok( warp::reply::json( &"No Data Found" ) )
        },
    }

}

pub async fn comments_total(
    db_conn: DbConn,
) -> Result<impl warp::Reply, Infallible> {
    let total: u32 = match db::get_comments_total(db_conn).await {
        Ok(data) => data,
        Err(message) => {
            error!("{:?} occurred in handlers::comments_total()", message);
            0u32
        }
    };
    Ok( warp::reply::json( &total ) )
}

// Reply functions
pub async fn add_reply(
    new_reply: Reply,
    db_conn: DbConn,
) -> Result<impl warp::Reply, Infallible> {
    debug!("add reply: {:?}", new_reply);
    let mut reply_clone = new_reply.clone();
    reply::assign_uid(&mut reply_clone);
    reply::assign_created_date(&mut reply_clone);
    let result = db::add_reply(&reply_clone, db_conn).await;
    let code = process_db_result( result );
    Ok(code)
}

pub async fn list_replies(
    comment_id: String,
    db_conn: DbConn,
) -> Result<impl warp::Reply, Infallible> {
    let mut results: Vec<Reply> = Vec::new();
    match db::get_replies(comment_id, &Status::Pending.to_string(), db_conn).await {
        Ok(data) => results = data,
        Err(message) => error!("{:?} occurred in handlers::list_replies()", message),
    };
    Ok( warp::reply::json( &results ) )
}

pub async fn update_reply(
    uid: String,
    mut reply: Reply,
    db_conn: DbConn,
) -> Result<impl warp::Reply, Infallible> {
    debug!("update reply: uid = {}, reply = {:?}", uid, reply);
    reply.unique_id = uid;
    reply.status = Status::Approved;
    reply.remarks = String::from("updated");
    reply::assign_updated_date(&mut reply);
    let result = db::update_reply(&reply, db_conn).await;
    let code = process_db_result( result );
    Ok(code)
}

pub async fn update_reply_message(
    uid: String,
    map: HashMap<String, String>,
    db_conn: DbConn,
) -> Result<impl warp::Reply, Infallible> {
    debug!("update reply message: uid = {}, new-message: {:?}+++++++++++++++++++++++++++", &uid, &map);
    let now: String = comment::timestamp_now();
    let msg = map.get("message").unwrap();
    let result = db::update_reply_message(msg.to_string(), uid, now, db_conn).await;
    let code = process_db_result( result );
    Ok(code)
}

pub async fn delete_reply(
    uid: String, 
    db_conn: DbConn
) -> Result<impl warp::Reply, Infallible> {
    debug!("delete reply: uid = {}", uid);
    let now: String = comment::timestamp_now();
    let result = db::delete_reply(&uid, now, db_conn).await;
    let code = process_db_result( result );
    Ok(code)
}

pub async fn get_reply(
    uid: String, 
    db_conn: DbConn
) -> Result<impl warp::Reply, Infallible> {
    // just return a json of reply for the given uid 
    debug!("get reply: uid = {}", uid);
    match db::get_reply(&uid, db_conn).await {
        Ok(data) => Ok( warp::reply::json( &data ) ),
        Err(message) => {
            error!("{} occurred in handlers::get_reply()", message);
            Ok( warp::reply::json( &"No Data Found" ) )
        },
    }
}

// This function serves two purposes: Add upvote and Revoke upvote (toggling effect)
// Add upvote happens when there is no row in the database for the combination of
// comment_id and user_id; if a row already exist for this combination, remove this row.
// To be precise, first time request on add_upvote will actually add an upvote, whereas
// second time request on add_upvote will remove the upvote
pub async fn add_upvote(
    new_upvote: Upvote,
    db_conn: DbConn,
) -> Result<impl warp::Reply, Infallible> {
    debug!("add upvote: {:?}", new_upvote);
    let mut upvote_clone = new_upvote.clone();
    upvote::assign_created_date(&mut upvote_clone);
    match db::add_upvote(&upvote_clone, db_conn.clone()).await {
        Ok(count) => {
            match count { 
                1 => Ok(StatusCode::CREATED),
                _ => Ok(StatusCode::BAD_REQUEST)
            }
        },
        Err(rusqlite::Error::SqliteFailure(error, _options)) => { // if unique constraint occurs, treat it as revoke request
            let mut code = StatusCode::BAD_REQUEST;
            let is_constraint_error =  error.code == rusqlite::ErrorCode::ConstraintViolation 
                                            && error.extended_code == "2067".parse::<i32>().unwrap();
            if is_constraint_error {                                            
                code = revoke_upvote(&upvote_clone, db_conn.clone()).await;
            }
            Ok(code)
        },
        Err(error) => {
            error!("{:?} occurred in handlers::add_upvote()", &error);
            Ok(StatusCode::BAD_REQUEST)
        },
    }
}

async fn revoke_upvote(upvote: &Upvote, db_conn: DbConn) -> warp::http::StatusCode {
    let now: String = upvote::timestamp_now();
    let result = db::delete_upvote(&upvote.comment_id, &upvote.user_id, now, db_conn).await;
    process_db_result(result)
}

pub async fn delete_upvote(
    comment_id: String,
    user_id: String, 
    db_conn: DbConn
) -> Result<impl warp::Reply, Infallible> {
    debug!("delete upvote: comment_id = {}, user_id = {}", &comment_id, &user_id);
    let now: String = upvote::timestamp_now();
    let result = db::delete_upvote(&comment_id, &user_id, now, db_conn).await;
    let code = process_db_result(result);
    Ok(code)
}

pub async fn upvotes_total(
    comment_id: String,
    db_conn: DbConn,
) -> Result<impl warp::Reply, Infallible> {
    let total: u32 = match db::get_upvotes_total(&comment_id, db_conn).await {
        Ok(data) => data,
        Err(message) => {
            error!("{:?} occurred in handlers::upvotes_total()", message);
            0u32
        }
    };
    Ok( warp::reply::json( &total ) )
}

///////////////////////// Downvote
// This function serves two purposes: Add downvote and Revoke downvote (toggling effect)
// Add downvote happens when there is no row in the database for the combination of
// comment_id and user_id; if a row already exist for this combination, remove this row.
// To be precise, first time request on add_downvote will actually add an downvote, whereas
// second time request on add_downvote will remove the downvote
pub async fn add_downvote(
    new_downvote: Downvote,
    db_conn: DbConn,
) -> Result<impl warp::Reply, Infallible> {
    debug!("add downvote: {:?}", new_downvote);
    let mut downvote_clone = new_downvote.clone();
    downvote::assign_created_date(&mut downvote_clone);
    match db::add_downvote(&downvote_clone, db_conn.clone()).await {
        Ok(count) => {
            match count { 
                1 => Ok(StatusCode::CREATED),
                _ => Ok(StatusCode::BAD_REQUEST)
            }
        },
        Err(rusqlite::Error::SqliteFailure(error, _options)) => { // if unique constraint occurs, treat it as revoke request
            let mut code = StatusCode::BAD_REQUEST;
            let is_constraint_error =  error.code == rusqlite::ErrorCode::ConstraintViolation 
                                            && error.extended_code == "2067".parse::<i32>().unwrap();
            if is_constraint_error {                                            
                code = revoke_downvote(&downvote_clone, db_conn.clone()).await;
            }
            Ok(code)
        },
        Err(error) => {
            error!("{:?} occurred in handlers::add_downvote()", &error);
            Ok(StatusCode::BAD_REQUEST)
        },
    }
}

async fn revoke_downvote(downvote: &Downvote, db_conn: DbConn) -> warp::http::StatusCode {
    let now: String = downvote::timestamp_now();
    let result = db::delete_downvote(&downvote.comment_id, &downvote.user_id, now, db_conn).await;
    process_db_result(result)
}

pub async fn delete_downvote(
    comment_id: String,
    user_id: String, 
    db_conn: DbConn
) -> Result<impl warp::Reply, Infallible> {
    debug!("delete downvote: comment_id = {}, user_id = {}", &comment_id, &user_id);
    let now: String = downvote::timestamp_now();
    let result = db::delete_downvote(&comment_id, &user_id, now, db_conn).await;
    let code = process_db_result(result);
    Ok(code)
}

pub async fn downvotes_total(
    comment_id: String,
    db_conn: DbConn,
) -> Result<impl warp::Reply, Infallible> {
    let total: u32 = match db::get_downvotes_total(&comment_id, db_conn).await {
        Ok(data) => data,
        Err(message) => {
            error!("{:?} occurred in handlers::downvotes_total()", message);
            0u32
        }
    };
    Ok( warp::reply::json( &total ) )
}