summary refs log blame commit diff stats
path: root/src/posts.rs
blob: f625a9063b396c805353a2b5f1353ea50b0a5b0d (plain) (tree)
1
2
3
4
5
6
7
8
9


             
 
              
              
                 
                
 

                                                     
                                                                                               




                                  


          












                                               
                                                   

                     
                                                                                             


                                        
 
                                  
                                       
 







                                          
 


                                            
            
                 

      


                                              

               
          


                               











                                                           
          
        



                                 







                                          







                                                                                  

          

 
                                 
                                                                    



                                                    

                                               



            







                                                                              
 
                              

                                                                   
                      


                                      





                                                      
                                           
 











                                                       
          


                                                   
                                                                                                    













                                                                                         
                                      
                                                                                 




                                       
                                 
                                                                    



                                              

                                               






                                                                           

                                                   
 
                                                                                          
 
                                    


                                                     
                      

     
                                        
               
          

 



                 
           
                   
                                            
                                       






                                                                                               
                                                          
                                                          





                                                                
                                              



                                                                                      
                                      
     
 
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::<String>()
}

// 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!("Title: {}\n\nBody: {}", row[0], row[2]);
    println!();
    println!("Enter new title:");
    io::stdin().read_line(&mut new_title)?;

    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();

    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::<usize, String>(1))
            .unwrap();

        assert_eq!("NEW TITLE", &out);
    }
}