comment_app_backend/
handlers.rs

1use std::convert::Infallible;
2use std::collections::HashMap;
3use warp::http::StatusCode;
4use log::*;
5
6use crate::db::{self, DbConn};
7use crate::models::comment::{self, Comment};
8use crate::models::reply::{self, Reply};
9use crate::models::upvote::{self, Upvote};
10use crate::models::downvote::{self, Downvote};
11use crate::models::status::Status;
12
13pub async fn list_comments(
14    db_conn: DbConn,
15) -> Result<impl warp::Reply, Infallible> {
16    let mut results: Vec<Comment> = Vec::new();
17    match db::get_all_comments(db_conn).await {
18        Ok(data) => results = data,
19        Err(message) => error!("{:?} occurred in handlers::list_comments()", message),
20    };
21    let filtered: Vec<Comment> = results.into_iter().filter(|each| 
22        each.status == Status::Pending 
23        || each.status == Status::Approved 
24        || each.status == Status:: Disabled 
25    ).collect();
26    Ok( warp::reply::json( &filtered ) )
27}
28
29pub async fn more_comments(
30    count: usize,
31    db_conn: DbConn,
32) -> Result<impl warp::Reply, Infallible> {
33    let mut results: Vec<Comment> = Vec::new();
34    match db::get_more_comments(count, db_conn).await {
35        Ok(data) => results = data,
36        Err(message) => error!("{:?} occurred in handlers::more_comments()", message),
37    };
38    let filtered: Vec<Comment> = results.into_iter().filter(|each| 
39        each.status == Status::Pending 
40        || each.status == Status::Approved 
41        || each.status == Status:: Disabled 
42    ).collect();
43    Ok( warp::reply::json( &filtered ) )    
44}
45
46pub async fn add_comment(
47    new_comment: Comment,
48    db_conn: DbConn,
49) -> Result<impl warp::Reply, Infallible> {
50    debug!("add comment: {:?}", new_comment);
51    let mut comment_clone = new_comment.clone();
52    comment::assign_uid(&mut comment_clone);
53    comment::assign_created_date(&mut comment_clone);
54    let result = db::add_comment(&comment_clone, db_conn).await;
55    let code = process_db_result( result );
56    Ok(code)
57}
58
59fn process_db_result(result: Result<usize, rusqlite::Error>) -> warp::http::StatusCode {
60    match result {
61        Ok(count) => {
62            match count {
63                1 => StatusCode::NO_CONTENT,
64                _ => StatusCode::NOT_FOUND
65            }
66        },
67        Err(error) => {
68            error!("Error: {} occurred in process_db_result...........", &error);
69            StatusCode::NOT_FOUND
70        }
71    }
72}
73
74pub async fn update_comment(
75    uid: String,
76    mut comment: Comment,
77    db_conn: DbConn,
78) -> Result<impl warp::Reply, Infallible> {
79    debug!("update comment: uid = {}, comment = {:?}", uid, comment);
80    comment.unique_id = uid;
81    comment.remarks = String::from("several fields are updated");
82    comment::assign_updated_date(&mut comment);
83    let result = db::update_comment(&comment, db_conn).await;
84    let code = process_db_result( result );
85    Ok(code)
86}
87
88pub async fn update_comment_status(
89    stat: String,
90    uid: String,
91    db_conn: DbConn,
92) -> Result<impl warp::Reply, Infallible> {
93    debug!("update comment status: stat = {}, uid = {}", &stat, uid);
94    let now: String = comment::timestamp_now();
95    let status: Status = Status::from_string(stat);
96    let result = db::update_comment_status(status, uid, now, db_conn).await;
97    let code = process_db_result( result );
98    Ok(code)
99}
100
101pub async fn update_comment_message(
102    uid: String,
103    map: HashMap<String, String>,
104    db_conn: DbConn,
105) -> Result<impl warp::Reply, Infallible> {
106    debug!("update comment message: uid = {}, new-message: {:?}+++++++++++++++++++++++++++", &uid, &map);
107    let now: String = comment::timestamp_now();
108    let msg = map.get("message").unwrap();
109    let result = db::update_comment_message(msg.to_string(), uid, now, db_conn).await;
110    let code = process_db_result(result);
111    Ok(code)
112}
113
114pub async fn delete_comment(
115    uid: String, 
116    db_conn: DbConn
117) -> Result<impl warp::Reply, Infallible> {
118    debug!("delete comment: uid = {}", uid);
119    let now: String = comment::timestamp_now();
120    let result = db::delete_comment(&uid, now, db_conn).await;
121    let code = process_db_result( result );
122    Ok(code)
123}
124
125pub async fn get_comment(
126    uid: String, 
127    db_conn: DbConn
128) -> Result<impl warp::Reply, Infallible> {
129    // just return a json of comment for the given uid 
130    debug!("get comment: uid = {}", uid);
131    match db::get_comment(&uid, db_conn).await {
132        Ok(data) => Ok( warp::reply::json( &data ) ),
133        Err(message) => {
134            error!("{} occurred in handlers::get_comment()", message);
135            Ok( warp::reply::json( &"No Data Found" ) )
136        },
137    }
138
139}
140
141pub async fn comments_total(
142    db_conn: DbConn,
143) -> Result<impl warp::Reply, Infallible> {
144    let total: u32 = match db::get_comments_total(db_conn).await {
145        Ok(data) => data,
146        Err(message) => {
147            error!("{:?} occurred in handlers::comments_total()", message);
148            0u32
149        }
150    };
151    Ok( warp::reply::json( &total ) )
152}
153
154// Reply functions
155pub async fn add_reply(
156    new_reply: Reply,
157    db_conn: DbConn,
158) -> Result<impl warp::Reply, Infallible> {
159    debug!("add reply: {:?}", new_reply);
160    let mut reply_clone = new_reply.clone();
161    reply::assign_uid(&mut reply_clone);
162    reply::assign_created_date(&mut reply_clone);
163    let result = db::add_reply(&reply_clone, db_conn).await;
164    let code = process_db_result( result );
165    Ok(code)
166}
167
168pub async fn list_replies(
169    comment_id: String,
170    db_conn: DbConn,
171) -> Result<impl warp::Reply, Infallible> {
172    let mut results: Vec<Reply> = Vec::new();
173    match db::get_replies(comment_id, &Status::Pending.to_string(), db_conn).await {
174        Ok(data) => results = data,
175        Err(message) => error!("{:?} occurred in handlers::list_replies()", message),
176    };
177    Ok( warp::reply::json( &results ) )
178}
179
180pub async fn update_reply(
181    uid: String,
182    mut reply: Reply,
183    db_conn: DbConn,
184) -> Result<impl warp::Reply, Infallible> {
185    debug!("update reply: uid = {}, reply = {:?}", uid, reply);
186    reply.unique_id = uid;
187    reply.status = Status::Approved;
188    reply.remarks = String::from("updated");
189    reply::assign_updated_date(&mut reply);
190    let result = db::update_reply(&reply, db_conn).await;
191    let code = process_db_result( result );
192    Ok(code)
193}
194
195pub async fn update_reply_message(
196    uid: String,
197    map: HashMap<String, String>,
198    db_conn: DbConn,
199) -> Result<impl warp::Reply, Infallible> {
200    debug!("update reply message: uid = {}, new-message: {:?}+++++++++++++++++++++++++++", &uid, &map);
201    let now: String = comment::timestamp_now();
202    let msg = map.get("message").unwrap();
203    let result = db::update_reply_message(msg.to_string(), uid, now, db_conn).await;
204    let code = process_db_result( result );
205    Ok(code)
206}
207
208pub async fn delete_reply(
209    uid: String, 
210    db_conn: DbConn
211) -> Result<impl warp::Reply, Infallible> {
212    debug!("delete reply: uid = {}", uid);
213    let now: String = comment::timestamp_now();
214    let result = db::delete_reply(&uid, now, db_conn).await;
215    let code = process_db_result( result );
216    Ok(code)
217}
218
219pub async fn get_reply(
220    uid: String, 
221    db_conn: DbConn
222) -> Result<impl warp::Reply, Infallible> {
223    // just return a json of reply for the given uid 
224    debug!("get reply: uid = {}", uid);
225    match db::get_reply(&uid, db_conn).await {
226        Ok(data) => Ok( warp::reply::json( &data ) ),
227        Err(message) => {
228            error!("{} occurred in handlers::get_reply()", message);
229            Ok( warp::reply::json( &"No Data Found" ) )
230        },
231    }
232}
233
234// This function serves two purposes: Add upvote and Revoke upvote (toggling effect)
235// Add upvote happens when there is no row in the database for the combination of
236// comment_id and user_id; if a row already exist for this combination, remove this row.
237// To be precise, first time request on add_upvote will actually add an upvote, whereas
238// second time request on add_upvote will remove the upvote
239pub async fn add_upvote(
240    new_upvote: Upvote,
241    db_conn: DbConn,
242) -> Result<impl warp::Reply, Infallible> {
243    debug!("add upvote: {:?}", new_upvote);
244    let mut upvote_clone = new_upvote.clone();
245    upvote::assign_created_date(&mut upvote_clone);
246    match db::add_upvote(&upvote_clone, db_conn.clone()).await {
247        Ok(count) => {
248            match count { 
249                1 => Ok(StatusCode::CREATED),
250                _ => Ok(StatusCode::BAD_REQUEST)
251            }
252        },
253        Err(rusqlite::Error::SqliteFailure(error, _options)) => { // if unique constraint occurs, treat it as revoke request
254            let mut code = StatusCode::BAD_REQUEST;
255            let is_constraint_error =  error.code == rusqlite::ErrorCode::ConstraintViolation 
256                                            && error.extended_code == "2067".parse::<i32>().unwrap();
257            if is_constraint_error {                                            
258                code = revoke_upvote(&upvote_clone, db_conn.clone()).await;
259            }
260            Ok(code)
261        },
262        Err(error) => {
263            error!("{:?} occurred in handlers::add_upvote()", &error);
264            Ok(StatusCode::BAD_REQUEST)
265        },
266    }
267}
268
269async fn revoke_upvote(upvote: &Upvote, db_conn: DbConn) -> warp::http::StatusCode {
270    let now: String = upvote::timestamp_now();
271    let result = db::delete_upvote(&upvote.comment_id, &upvote.user_id, now, db_conn).await;
272    process_db_result(result)
273}
274
275pub async fn delete_upvote(
276    comment_id: String,
277    user_id: String, 
278    db_conn: DbConn
279) -> Result<impl warp::Reply, Infallible> {
280    debug!("delete upvote: comment_id = {}, user_id = {}", &comment_id, &user_id);
281    let now: String = upvote::timestamp_now();
282    let result = db::delete_upvote(&comment_id, &user_id, now, db_conn).await;
283    let code = process_db_result(result);
284    Ok(code)
285}
286
287pub async fn upvotes_total(
288    comment_id: String,
289    db_conn: DbConn,
290) -> Result<impl warp::Reply, Infallible> {
291    let total: u32 = match db::get_upvotes_total(&comment_id, db_conn).await {
292        Ok(data) => data,
293        Err(message) => {
294            error!("{:?} occurred in handlers::upvotes_total()", message);
295            0u32
296        }
297    };
298    Ok( warp::reply::json( &total ) )
299}
300
301///////////////////////// Downvote
302// This function serves two purposes: Add downvote and Revoke downvote (toggling effect)
303// Add downvote happens when there is no row in the database for the combination of
304// comment_id and user_id; if a row already exist for this combination, remove this row.
305// To be precise, first time request on add_downvote will actually add an downvote, whereas
306// second time request on add_downvote will remove the downvote
307pub async fn add_downvote(
308    new_downvote: Downvote,
309    db_conn: DbConn,
310) -> Result<impl warp::Reply, Infallible> {
311    debug!("add downvote: {:?}", new_downvote);
312    let mut downvote_clone = new_downvote.clone();
313    downvote::assign_created_date(&mut downvote_clone);
314    match db::add_downvote(&downvote_clone, db_conn.clone()).await {
315        Ok(count) => {
316            match count { 
317                1 => Ok(StatusCode::CREATED),
318                _ => Ok(StatusCode::BAD_REQUEST)
319            }
320        },
321        Err(rusqlite::Error::SqliteFailure(error, _options)) => { // if unique constraint occurs, treat it as revoke request
322            let mut code = StatusCode::BAD_REQUEST;
323            let is_constraint_error =  error.code == rusqlite::ErrorCode::ConstraintViolation 
324                                            && error.extended_code == "2067".parse::<i32>().unwrap();
325            if is_constraint_error {                                            
326                code = revoke_downvote(&downvote_clone, db_conn.clone()).await;
327            }
328            Ok(code)
329        },
330        Err(error) => {
331            error!("{:?} occurred in handlers::add_downvote()", &error);
332            Ok(StatusCode::BAD_REQUEST)
333        },
334    }
335}
336
337async fn revoke_downvote(downvote: &Downvote, db_conn: DbConn) -> warp::http::StatusCode {
338    let now: String = downvote::timestamp_now();
339    let result = db::delete_downvote(&downvote.comment_id, &downvote.user_id, now, db_conn).await;
340    process_db_result(result)
341}
342
343pub async fn delete_downvote(
344    comment_id: String,
345    user_id: String, 
346    db_conn: DbConn
347) -> Result<impl warp::Reply, Infallible> {
348    debug!("delete downvote: comment_id = {}, user_id = {}", &comment_id, &user_id);
349    let now: String = downvote::timestamp_now();
350    let result = db::delete_downvote(&comment_id, &user_id, now, db_conn).await;
351    let code = process_db_result(result);
352    Ok(code)
353}
354
355pub async fn downvotes_total(
356    comment_id: String,
357    db_conn: DbConn,
358) -> Result<impl warp::Reply, Infallible> {
359    let total: u32 = match db::get_downvotes_total(&comment_id, db_conn).await {
360        Ok(data) => data,
361        Err(message) => {
362            error!("{:?} occurred in handlers::downvotes_total()", message);
363            0u32
364        }
365    };
366    Ok( warp::reply::json( &total ) )
367}
368