~gbmor/clinte

ref: 1f4d385823d4a7209798eed0f3c7b6426815e3be clinte/src/posts.rs -rw-r--r-- 6.3 KiB
1f4d3858Ben Morrison version bump 1 year, 6 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
use std::io;

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!("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)?;
        println!();
        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)?;
    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);
    }
}