about summary refs log tree commit diff stats
path: root/src/posts.rs
diff options
context:
space:
mode:
authorBen Morrison <ben@gbmor.dev>2020-05-28 02:58:57 -0400
committerBen Morrison <ben@gbmor.dev>2020-05-28 02:58:57 -0400
commitbb327d381e5626d96942a805e6a5f4d4a5a771d5 (patch)
treec7e86c2a6ea8dc67be2221e8517806f365aac088 /src/posts.rs
parente4af0011c04e2861e4f890bcb7383f9c87e35126 (diff)
downloadclinte-bb327d381e5626d96942a805e6a5f4d4a5a771d5.tar.gz
extensive rewrite to use json for storage:
sqlite3 requires the directory where the database resides to be
writeable by the user. This presents a problem on multiuser UNIX systems
where they may want to limit areas where users have write access.

This rewrite totally scraps the sqlite3 database in favor of a pretty
simple json file consisting of an array of posts. flock(2) locking is
used to synchronize access to the file and make sure two clients aren't
trying to write to it at once. The locking is fairly granular right now,
but later I may change it to using a single lock for the duration of
execution since race conditions are *possible*, if unlikely for the
purposes of clinte's intended use.
Diffstat (limited to 'src/posts.rs')
-rw-r--r--src/posts.rs181
1 files changed, 56 insertions, 125 deletions
diff --git a/src/posts.rs b/src/posts.rs
index c9f9307..b286469 100644
--- a/src/posts.rs
+++ b/src/posts.rs
@@ -5,17 +5,6 @@ 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
@@ -29,11 +18,7 @@ fn str_to_utf8(str: &str) -> 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)")?;
-
+pub fn create() -> error::Result<()> {
     println!();
     println!("Title of the new post: ");
 
@@ -55,43 +40,38 @@ pub fn create(db: &db::Conn) -> error::Result<()> {
     } else {
         &body_raw
     };
-
     let trimmed_body = body.trim();
 
-    exec_new(&mut stmt, title, trimmed_body)?;
+    let user = &*user::NAME;
+
+    let mut all = db::Posts::get_all(db::PATH);
+    let new = db::Post {
+        author: user.into(),
+        title: title.to_string(),
+        body: trimmed_body.to_string(),
+    };
+
+    all.append(new);
+    all.write();
 
     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,
-        })
-    })?;
+pub fn display() -> error::Result<()> {
+    let all = db::Posts::get_all(db::PATH);
 
     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);
-        }
+    all.posts.iter().enumerate().for_each(|(id, post)| {
+        let newpost = format!(
+            "{}. {} -> by {}\n{}\n\n",
+            id + 1,
+            post.title.trim(),
+            post.author,
+            post.body.trim()
+        );
+        postvec.push(newpost);
     });
 
     for (i, e) in postvec.iter().enumerate() {
@@ -104,8 +84,8 @@ pub fn display(db: &db::Conn) -> error::Result<()> {
 }
 
 // First handler to update posts.
-pub fn update_handler(db: &db::Conn, id: u32) -> error::Result<()> {
-    let id_num_in = if id == 0 {
+pub fn update_handler(id: usize) -> error::Result<()> {
+    let mut id_num_in = if id == 0 {
         println!();
         println!("ID number of your post to edit?");
         let mut id_num_in = String::new();
@@ -115,31 +95,29 @@ pub fn update_handler(db: &db::Conn, id: u32) -> error::Result<()> {
         id
     };
 
-    let mut get_stmt = db.conn.prepare("SELECT * FROM posts WHERE id = :id")?;
+    id_num_in -= 1;
 
-    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])
-    })?;
+    let user = &*user::NAME;
+    let mut all = db::Posts::get_all(db::PATH);
+    let post = all.get(id_num_in);
 
-    if *user::NAME != row[1] {
+    if *user != post.author {
+        println!();
+        println!("Users don't match. Can't update post!");
         println!();
-        println!("Username mismatch - can't update_handler post!");
-        return Ok(());
+        std::process::exit(1);
     }
 
     let mut new_title = String::new();
 
     println!("Updating post {}", id_num_in);
     println!();
-    println!("Current Title: {}", &row[0]);
+    println!("Current Title: {}", post.title);
     println!();
     println!("Enter new title:");
     io::stdin().read_line(&mut new_title)?;
 
-    let body_raw = str_to_utf8(&ed::call(&row[2]));
+    let body_raw = str_to_utf8(&ed::call(&post.body));
     let body = if body_raw.len() > 500 {
         &body_raw[..500]
     } else {
@@ -148,38 +126,24 @@ pub fn update_handler(db: &db::Conn, id: u32) -> error::Result<()> {
 
     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)?;
+    all.replace(
+        id_num_in,
+        db::Post {
+            author: user.into(),
+            title: new_title,
+            body: trimmed_body.to_string(),
+        },
+    );
 
-    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)?;
+    all.write();
 
+    println!();
     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 {
+pub fn delete_handler(id: usize) -> error::Result<()> {
+    let mut id_num_in = if id == 0 {
         println!();
         println!("ID of the post to delete?");
         let mut id_num_in = String::new();
@@ -190,53 +154,20 @@ pub fn delete_handler(db: &db::Conn, id: u32) -> error::Result<()> {
         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)?;
+    id_num_in -= 1;
 
-    let user_in_post: String = get_stmt.query_row(rusqlite::NO_PARAMS, |row| row.get(2))?;
+    let mut all = db::Posts::get_all(db::PATH);
+    let post = all.get(id_num_in);
 
-    if *user::NAME != user_in_post {
+    if *user::NAME != post.author {
         println!();
-        println!("Users don't match. Can't delete!");
+        println!("Users don't match. Can't delete post!");
         println!();
-        return Ok(());
+        std::process::exit(1);
     }
 
-    exec_stmt_no_params(&mut del_stmt)?;
-    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();
+    all.delete(id_num_in);
+    all.write();
 
-        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);
-    }
+    Ok(())
 }