use std::io; use rusqlite; use crate::db; use crate::ed; use crate::error; use crate::user; // Executes the sql statement that inserts a new post // Broken off for unit testing. pub fn exec_new(stmt: &mut rusqlite::Statement, title: &str, body: &str) -> error::Result<()> { stmt.execute_named(&[ (":title", &title), (":author", &*user::NAME), (":body", &body), ])?; Ok(()) } // Make sure nobody encodes narsty characters // into a message to negatively affect other // users fn str_to_utf8(str: &str) -> String { str.chars() .map(|c| { let mut buf = [0; 4]; c.encode_utf8(&mut buf).to_string() }) .collect::() } // First handler for creating a new post. pub fn create(db: &db::Conn) -> error::Result<()> { let mut stmt = db .conn .prepare("INSERT INTO posts (title, author, body) VALUES (:title, :author, :body)")?; println!(); println!("Title of the new post: "); let mut title = String::new(); io::stdin().read_line(&mut title)?; let title = str_to_utf8(title.trim()); let title = if title.len() > 30 { &title[..30] } else { &title }; println!(); let body_raw = str_to_utf8(&ed::call("")); let body = if body_raw.len() > 500 { &body_raw[..500] } else { &body_raw }; let trimmed_body = body.trim(); exec_new(&mut stmt, title, trimmed_body)?; println!(); Ok(()) } // Shows the most recent posts. pub fn display(db: &db::Conn) -> error::Result<()> { let mut stmt = db.conn.prepare("SELECT * FROM posts")?; let out = stmt.query_map(rusqlite::NO_PARAMS, |row| { let id: u32 = row.get(0)?; let title: String = row.get(1)?; let author: String = row.get(2)?; let body: String = row.get(3)?; Ok(db::Post { id, title, author, body, }) })?; let mut postvec = Vec::new(); out.for_each(|row| { if let Ok(post) = row { let newpost = format!( "{}. {} -> by {}\n{}\n\n", post.id, post.title.trim(), post.author, post.body.trim() ); postvec.push(newpost); } }); for (i, e) in postvec.iter().enumerate() { if (postvec.len() > 14 && i >= postvec.len() - 15) || postvec.len() < 15 { print!("{}", e); } } Ok(()) } // First handler to update posts. pub fn update_handler(db: &db::Conn, id: u32) -> error::Result<()> { let id_num_in = if id == 0 { println!(); println!("ID number of your post to edit?"); let mut id_num_in = String::new(); io::stdin().read_line(&mut id_num_in)?; id_num_in.trim().parse()? } else { id }; let mut get_stmt = db.conn.prepare("SELECT * FROM posts WHERE id = :id")?; let row = get_stmt.query_row_named(&[(":id", &id_num_in)], |row| { let title: String = row.get(1)?; let author = row.get(2)?; let body = row.get(3)?; Ok(vec![title, author, body]) })?; if *user::NAME != row[1] { println!(); println!("Username mismatch - can't update_handler post!"); return Ok(()); } let mut new_title = String::new(); println!("Updating post {}", id_num_in); println!(); println!("Current Title: {}", &row[0]); println!(); println!("Enter new title:"); io::stdin().read_line(&mut new_title)?; let body_raw = str_to_utf8(&ed::call(&row[2])); let body = if body_raw.len() > 500 { &body_raw[..500] } else { &body_raw }; let trimmed_body = body.trim(); update(&new_title, &trimmed_body, id_num_in, &db)?; println!(); Ok(()) } // Allows editing of posts - called by main::update pub fn update(new_title: &str, new_body: &str, id_num_in: u32, db: &db::Conn) -> error::Result<()> { let new_title = new_title.trim(); let new_body = new_body.trim(); let title_stmt = format!("UPDATE posts SET title = :title WHERE id = {}", id_num_in); let mut stmt = db.conn.prepare(&title_stmt)?; stmt.execute_named(&[(":title", &new_title)])?; let body_stmt = format!("UPDATE posts SET body = :body WHERE id = {}", id_num_in); let mut stmt = db.conn.prepare(&body_stmt)?; stmt.execute_named(&[(":body", &new_body)])?; Ok(()) } // Helper to just run a sql statement. pub fn exec_stmt_no_params(stmt: &mut rusqlite::Statement) -> error::Result<()> { stmt.execute(rusqlite::NO_PARAMS)?; Ok(()) } // First handler to remove a post pub fn delete_handler(db: &db::Conn, id: u32) -> error::Result<()> { let id_num_in: u32 = if id == 0 { println!(); println!("ID of the post to delete?"); let mut id_num_in = String::new(); io::stdin().read_line(&mut id_num_in)?; id_num_in.trim().parse()? } else { id }; let del_stmt = format!("DELETE FROM posts WHERE id = {}", id_num_in); let get_stmt = format!("SELECT * FROM posts WHERE id = {}", id_num_in); let mut get_stmt = db.conn.prepare(&get_stmt)?; let mut del_stmt = db.conn.prepare(&del_stmt)?; let user_in_post: String = get_stmt.query_row(rusqlite::NO_PARAMS, |row| row.get(2))?; if *user::NAME != user_in_post { println!(); println!("Users don't match. Can't delete!"); println!(); return Ok(()); } exec_stmt_no_params(&mut del_stmt)?; println!(); Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn post_new() { let db = db::Conn::init(":memory:"); let db = db::Conn { conn: db }; let mut stmt = db .conn .prepare("INSERT INTO posts (title, author, body) VALUES (:title, :author, :body)") .unwrap(); let title = String::from("TEST TITLE"); exec_new(&mut stmt, &title, "TEST BODY").unwrap(); update("NEW TITLE", "TEST BODY", 1, &db).unwrap(); let mut stmt = db .conn .prepare("SELECT * FROM posts WHERE title = :title") .unwrap(); let title = String::from("NEW TITLE"); let out: String = stmt .query_row_named(&[(":title", &title)], |row| row.get::(1)) .unwrap(); assert_eq!("NEW TITLE", &out); } }