summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKjetil Orbekk <kjetil.orbekk@gmail.com>2017-07-14 06:18:13 -0400
committerKjetil Orbekk <kjetil.orbekk@gmail.com>2017-07-14 06:18:13 -0400
commit93cb735370c48c3102fdb10e744f7c95d6c0f7c9 (patch)
tree6b9fff4d4cc9e7593b090b864fa9afffa78e7abc
parentfe6d45474a2024fb362ee59f7a38f827283ac0c4 (diff)
add: Fancy voting.
Current vote is displayed in bold text. Let user change vote.
-rw-r--r--src/data.rs114
-rw-r--r--src/data/templates/quotes.hbs10
-rw-r--r--src/data/templates/vote.hbs12
-rw-r--r--src/server.rs19
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
@@ -3,6 +3,12 @@ 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,
date: String,
@@ -11,6 +17,46 @@ pub struct Quote {
points: i32,
votes: i32,
content: String,
+ vote_options: Vec<Vote>,
+}
+
+fn make_quote(
+ id: i64,
+ date: String,
+ author: String,
+ content: String,
+ user_vote: Option<i32>,
+ points: i32,
+ votes: i32,
+) -> Quote {
+ fn make_vote_options(user_vote: Option<i32>) -> Vec<Vote> {
+ (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)
"#,
- &[&quote_id, &score],
+ &[&quote_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<i64>,
+ user_id: i64,
ordering: &str,
approved: bool,
) -> Result<Vec<Quote>> {
@@ -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<Vec<Quote>> {
- internal_get_quotes(c, None, ordering, true)
+pub fn get_quotes(c: &Connection, user_id: i64, ordering: &str) -> Result<Vec<Quote>> {
+ internal_get_quotes(c, None, user_id, ordering, true)
}
pub fn get_pending_quotes(c: &Connection) -> Result<Vec<Quote>> {
- internal_get_quotes(c, None, "", false)
+ internal_get_quotes(c, None, 0, "", false)
}
-pub fn get_quote(c: &Connection, id: i64) -> Result<Quote> {
- let quotes = internal_get_quotes(c, Some(id), "", true)?;
+pub fn get_quote(c: &Connection, id: i64, user_id: i64) -> Result<Quote> {
+ 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 <a href="quotes.jsp?order=date">dato</a> | <a href="quotes.jsp?order=score">score</a>
{{/inline}}
-
{{#*inline "page"}}
{{#each quotes}}
-
<br>
<a href="/view_quote?id={{id}}">#{{id}}</a>, lagt til av {{author}}<br>
Dato: {{date}}, Score:
<span id="v{{id}}">
-{{score}} (fra {{votes}}), Vote: <font size="-1">
- <a href="javascript:vote({{id}},1)">1</a>
- <a href="javascript:vote({{id}},2)">2</a>
- <a href="javascript:vote({{id}},3)">3</a>
- <a href="javascript:vote({{id}},4)">4</a>
- <a href="javascript:vote({{id}},5)">5</a></font></span>,
-
+{{~> vote~}}
<form method="post" style="display: inline;"action="http://www.vidarholen.net/contents/rage/index.php"><input type="hidden" name="irc" value="{{content}}"/><input type="submit" class="ragebutton" value="Rage it"/></form><br>
<br> <br>
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: <font size="-1">
+{{#each vote_options}}
+{{#if is_user_vote}}
+<b>{{value}}</b>
+{{else}}
+<a href="javascript:vote({{../id}},{{value}})">{{value}}</a>
+{{/if}}
+{{/each}}
+</font></span>
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<UserId> {
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<Response> {
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::<i64>().ok(),
);
@@ -115,12 +116,13 @@ fn quotes(r: &mut Request) -> IronResult<Response> {
let mu = r.get::<Write<State>>().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::<Vec<_>>();
result.insert("quotes".to_string(), to_json(&quotes));
+ result.insert("scores".to_string(), to_json(&(1..6).collect::<Vec<_>>()));
Ok(Response::with((
status::Ok,
Header(ContentType::html()),
@@ -166,7 +168,7 @@ fn add_post(r: &mut Request) -> IronResult<Response> {
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<Response> {
}
pub fn vote(r: &mut Request) -> IronResult<Response> {
+ let user_id = user_id(r)?;
let quote_id = get_param(r, "id").and_then(|id| {
id.parse::<i64>().map_err(|e| {
From::from(LinoError::BadRequest(format!("id: {}", e)))
@@ -220,8 +223,8 @@ pub fn vote(r: &mut Request) -> IronResult<Response> {
let quote = {
let mu = r.get::<Write<State>>().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<Response> {
Ok(Response::with((
status::Ok,
Header(ContentType::html()),
- Template::new("vote", result),
+ Template::new("vote", to_json(&quote)),
)))
}