From 93cb735370c48c3102fdb10e744f7c95d6c0f7c9 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Fri, 14 Jul 2017 06:18:13 -0400 Subject: add: Fancy voting. Current vote is displayed in bold text. Let user change vote. --- src/data.rs | 114 +++++++++++++++++++++++++++++------------- src/data/templates/quotes.hbs | 10 +--- src/data/templates/vote.hbs | 12 +++-- src/server.rs | 19 ++++--- 4 files changed, 100 insertions(+), 55 deletions(-) diff --git a/src/data.rs b/src/data.rs index a5d7afb..8178bb9 100644 --- a/src/data.rs +++ b/src/data.rs @@ -2,6 +2,12 @@ use rusqlite::Connection; use error::{Result, LinoError}; use rand::{OsRng, Rng}; +#[derive(Serialize, Debug, Clone)] +pub struct Vote { + value: i32, + is_user_vote: bool, +} + #[derive(Serialize, Debug, Clone)] pub struct Quote { id: i64, @@ -11,6 +17,46 @@ pub struct Quote { points: i32, votes: i32, content: String, + vote_options: Vec, +} + +fn make_quote( + id: i64, + date: String, + author: String, + content: String, + user_vote: Option, + points: i32, + votes: i32, +) -> Quote { + fn make_vote_options(user_vote: Option) -> Vec { + (1..6) + .map({ + |v| { + Vote { + value: v, + is_user_vote: Some(v) == user_vote, + } + } + }) + .collect() + } + + let score = if votes > 0 { + format!("{:.1}", points as f64 / votes as f64) + } else { + "—".to_string() + }; + Quote { + id: id, + date: date, + author: author, + points: points, + votes: votes, + content: content, + score: score, + vote_options: make_vote_options(user_vote), + } } pub fn init(c: &Connection) -> Result<()> { @@ -28,9 +74,11 @@ pub fn init(c: &Connection) -> Result<()> { CREATE TABLE IF NOT EXISTS votes ( id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, quote_id INTEGER NOT NULL, score INTEGER NOT NULL, FOREIGN KEY(quote_id) REFERENCES quotes(id) + CONSTRAINT unique_vote UNIQUE (user_id, quote_id) ); CREATE TABLE IF NOT EXISTS keys ( @@ -65,12 +113,12 @@ pub fn populate_test_db(c: &Connection) -> Result<()> { (2, 1, '2017-07-09', 'orbekk', 'test quote2'), (3, 0, '2017-07-09', 'orbekk', 'test quote3'); - INSERT INTO votes (quote_id, score) VALUES - (1, 2), - (1, 3), - (1, 1), - (2, 1), - (2, 2); + INSERT INTO votes (quote_id, user_id, score) VALUES + (1, 1, 2), + (1, 2, 3), + (1, 3, 1), + (2, 1, 1), + (2, 4, 2); "#, )); Ok(()) @@ -99,13 +147,13 @@ pub fn new_quote(c: &Connection, date: &str, author: &str, content: &str) -> Res Ok(()) } -pub fn new_vote(c: &Connection, quote_id: i64, score: i32) -> Result<()> { +pub fn new_vote(c: &Connection, user_id: i64, quote_id: i64, score: i32) -> Result<()> { c.execute( r#" - INSERT INTO votes (quote_id, score) VALUES - (?1, ?2) + INSERT OR REPLACE INTO votes (quote_id, user_id, score) VALUES + (?1, ?2, ?3) "#, - &["e_id, &score], + &["e_id, &user_id, &score], )?; info!("New vote: quote_id({}) score({})", quote_id, score); Ok(()) @@ -137,6 +185,7 @@ pub fn delete_quote(c: &Connection, quote_id: i64) -> Result<()> { fn internal_get_quotes( c: &Connection, id: Option, + user_id: i64, ordering: &str, approved: bool, ) -> Result> { @@ -144,37 +193,32 @@ fn internal_get_quotes( // ?1: bool, whether to query for a specific quote // ?2: i64, quote id to use if ?1 is true // ?3: bool, whether to fetch approved or not approved quotes + // ?4: i64, user_id let mut stmt = c.prepare( r#" - SELECT q.id, q.quote_date, q.author, q.content, + SELECT q.id, q.quote_date, q.author, q.content, uv.score, COALESCE(sum(v.score), 0), count(v.score) FROM quotes q LEFT JOIN votes v ON (q.id = v.quote_id) - WHERE (?1 AND q.id = ?2) OR (NOT ?1 AND q.approved = ?3) - GROUP BY 1, 2, 3, 4; + LEFT JOIN votes uv ON (q.id = uv.quote_id AND uv.user_id = ?4) + WHERE ((?1 AND q.id = ?2) OR (NOT ?1 AND q.approved = ?3)) + GROUP BY 1, 2, 3, 4, 5; ORDER BY q.id DESC; "#, )?; let rows = stmt.query_map( - &[&id.is_some(), &id.unwrap_or(-1), &approved], + &[&id.is_some(), &id.unwrap_or(-1), &approved, &user_id], |row| { - let points = row.get(4); - let votes = row.get(5); - let score = if votes > 0 { - format!("{:.1}", points as f64 / votes as f64) - } else { - "—".to_string() - }; - Quote { - id: row.get(0), - date: row.get(1), - author: row.get(2), - content: row.get(3), - score: score, - points: points, - votes: votes, - } + make_quote( + row.get(0), + row.get(1), + row.get(2), + row.get(3), + row.get(4), + row.get(5), + row.get(6), + ) }, )?; @@ -195,16 +239,16 @@ fn internal_get_quotes( }) } -pub fn get_quotes(c: &Connection, ordering: &str) -> Result> { - internal_get_quotes(c, None, ordering, true) +pub fn get_quotes(c: &Connection, user_id: i64, ordering: &str) -> Result> { + internal_get_quotes(c, None, user_id, ordering, true) } pub fn get_pending_quotes(c: &Connection) -> Result> { - internal_get_quotes(c, None, "", false) + internal_get_quotes(c, None, 0, "", false) } -pub fn get_quote(c: &Connection, id: i64) -> Result { - let quotes = internal_get_quotes(c, Some(id), "", true)?; +pub fn get_quote(c: &Connection, id: i64, user_id: i64) -> Result { + let quotes = internal_get_quotes(c, Some(id), user_id, "", true)?; quotes.into_iter().next().ok_or(LinoError::NotFound( format!("quote with id {}", id), )) diff --git a/src/data/templates/quotes.hbs b/src/data/templates/quotes.hbs index 71732ab..92cf114 100644 --- a/src/data/templates/quotes.hbs +++ b/src/data/templates/quotes.hbs @@ -1,21 +1,13 @@ {{#*inline "heading"}} Sortér etter dato | score {{/inline}} - {{#*inline "page"}} {{#each quotes}} -
#{{id}}, lagt til av {{author}}
Dato: {{date}}, Score: -{{score}} (fra {{votes}}), Vote: - 1 - 2 - 3 - 4 - 5, - +{{~> vote~}}



diff --git a/src/data/templates/vote.hbs b/src/data/templates/vote.hbs index 135fa2f..9aff608 100644 --- a/src/data/templates/vote.hbs +++ b/src/data/templates/vote.hbs @@ -1,3 +1,9 @@ -{{#with quote}} -{{score}} (fra {{votes}}) -{{/with}} +{{score}} (fra {{votes}}), Vote: +{{#each vote_options}} +{{#if is_user_vote}} +{{value}} +{{else}} +{{value}} +{{/if}} +{{/each}} + diff --git a/src/server.rs b/src/server.rs index 54beb34..8a45e37 100644 --- a/src/server.rs +++ b/src/server.rs @@ -26,7 +26,7 @@ impl iron::typemap::Key for State { } #[derive(Debug, Clone, Serialize)] -struct UserId(u64); +struct UserId(i64); impl iron_sessionstorage::Value for UserId { fn get_key() -> &'static str { "user_id" @@ -49,7 +49,7 @@ fn user_id(r: &mut Request) -> IronResult { let e: LinoError = From::from(e); e })?; - let user_id = UserId(rng.next_u64()); + let user_id = UserId((rng.next_u64() >> 1) as i64); r.session().set(user_id.clone())?; Ok(user_id) } @@ -106,6 +106,7 @@ fn make_renderer() -> HandlebarsEngine { fn quotes(r: &mut Request) -> IronResult { let mut result = make_result(r)?; + let user_id = user_id(r)?; let quote_id = get_param(r, "id").ok().and_then( |id| id.parse::().ok(), ); @@ -115,12 +116,13 @@ fn quotes(r: &mut Request) -> IronResult { let mu = r.get::>().unwrap(); let state = mu.lock().unwrap(); match quote_id { - Some(id) => vec![data::get_quote(&state.connection, id)?], - None => data::get_quotes(&state.connection, &ordering)?, + Some(id) => vec![data::get_quote(&state.connection, id, user_id.0)?], + None => data::get_quotes(&state.connection, user_id.0, &ordering)?, } }; let quotes = quotes.into_iter().collect::>(); result.insert("quotes".to_string(), to_json("es)); + result.insert("scores".to_string(), to_json(&(1..6).collect::>())); Ok(Response::with(( status::Ok, Header(ContentType::html()), @@ -166,7 +168,7 @@ fn add_post(r: &mut Request) -> IronResult { Ok(Response::with(( status::Ok, Header(ContentType::html()), - Template::new("add_post", result) + Template::new("add_post", result), ))) } @@ -203,6 +205,7 @@ fn approve(r: &mut Request) -> IronResult { } pub fn vote(r: &mut Request) -> IronResult { + let user_id = user_id(r)?; let quote_id = get_param(r, "id").and_then(|id| { id.parse::().map_err(|e| { From::from(LinoError::BadRequest(format!("id: {}", e))) @@ -220,8 +223,8 @@ pub fn vote(r: &mut Request) -> IronResult { let quote = { let mu = r.get::>().unwrap(); let state = mu.lock().unwrap(); - data::new_vote(&state.connection, quote_id, vote)?; - data::get_quote(&state.connection, quote_id)? + data::new_vote(&state.connection, user_id.0, quote_id, vote)?; + data::get_quote(&state.connection, quote_id, user_id.0)? }; let mut result = make_result(r)?; @@ -230,7 +233,7 @@ pub fn vote(r: &mut Request) -> IronResult { Ok(Response::with(( status::Ok, Header(ContentType::html()), - Template::new("vote", result), + Template::new("vote", to_json("e)), ))) } -- cgit v1.2.3