summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKjetil Orbekk <kjetil.orbekk@gmail.com>2017-07-13 07:28:42 -0400
committerKjetil Orbekk <kjetil.orbekk@gmail.com>2017-07-13 07:28:42 -0400
commitfe6d45474a2024fb362ee59f7a38f827283ac0c4 (patch)
treecc09092fac8bca86372939cabc61220b1e0e303c /src
parentba1ab61901e657903a34b0b0acecb81c12b5e0d0 (diff)
add: Persistent user id.
This will be used to limit users to one vote per quote.
Diffstat (limited to 'src')
-rw-r--r--src/data.rs31
-rw-r--r--src/data/templates/base.hbs1
-rw-r--r--src/error.rs10
-rw-r--r--src/lib.rs2
-rw-r--r--src/main.rs23
-rw-r--r--src/server.rs58
6 files changed, 106 insertions, 19 deletions
diff --git a/src/data.rs b/src/data.rs
index 5674f92..a5d7afb 100644
--- a/src/data.rs
+++ b/src/data.rs
@@ -1,5 +1,6 @@
use rusqlite::Connection;
use error::{Result, LinoError};
+use rand::{OsRng, Rng};
#[derive(Serialize, Debug, Clone)]
pub struct Quote {
@@ -31,8 +32,27 @@ pub fn init(c: &Connection) -> Result<()> {
score INTEGER NOT NULL,
FOREIGN KEY(quote_id) REFERENCES quotes(id)
);
+
+ CREATE TABLE IF NOT EXISTS keys (
+ id TEXT PRIMARY KEY,
+ value BLOB NOT NULL
+ );
"#,
)?;
+
+ let key = {
+ let mut v = vec![0u8; 32];
+ (OsRng::new()?).fill_bytes(v.as_mut());
+ v
+ };
+ c.execute(
+ r#"
+ INSERT OR IGNORE INTO keys (id, value) VALUES
+ ('session', ?1);
+ "#,
+ &[&key],
+ )?;
+
Ok(())
}
@@ -56,6 +76,17 @@ pub fn populate_test_db(c: &Connection) -> Result<()> {
Ok(())
}
+pub fn get_key(c: &Connection, id: &str) -> Result<Vec<u8>> {
+ let key = c.query_row(
+ r#"
+ SELECT value FROM keys WHERE id = ?1
+ "#,
+ &[&id],
+ |row| row.get(0),
+ )?;
+ Ok(key)
+}
+
pub fn new_quote(c: &Connection, date: &str, author: &str, content: &str) -> Result<()> {
c.execute(
r#"
diff --git a/src/data/templates/base.hbs b/src/data/templates/base.hbs
index d548965..db7bb9e 100644
--- a/src/data/templates/base.hbs
+++ b/src/data/templates/base.hbs
@@ -67,6 +67,7 @@
<p>linoquotes v.3 © 2004-2017 Kjetil Ørbekk, Erlend Hamberg, Vidar Holen, John H. Anthony.
<p>Source code at <a href="https://git.orbekk.com/linoquotes-gamma.git">https://git.orbekk.com/linoquotes-gamma.git</a>.
<p>The quotes on this page are copyright their respective owners and submitters.</p>
+<p>Your user id: {{user_id}}.
</center>
</body>
diff --git a/src/error.rs b/src/error.rs
index 41b4e64..1005564 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -9,6 +9,7 @@ use iron::modifiers::Header;
#[derive(Debug)]
pub enum LinoError {
DbError(rusqlite::Error),
+ IoError(std::io::Error),
NotFound(String),
BadRequest(String),
}
@@ -19,6 +20,7 @@ impl fmt::Display for LinoError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
LinoError::DbError(ref err) => err.fmt(f),
+ LinoError::IoError(ref err) => err.fmt(f),
LinoError::NotFound(ref x) => write!(f, "Could not find {}", x),
LinoError::BadRequest(ref x) => write!(f, "Bad request: {}", x),
}
@@ -29,6 +31,7 @@ impl std::error::Error for LinoError {
fn description(&self) -> &str {
match *self {
LinoError::DbError(ref err) => err.description(),
+ LinoError::IoError(ref err) => err.description(),
LinoError::NotFound(_) => "not found",
LinoError::BadRequest(_) => "bad request",
}
@@ -37,6 +40,7 @@ impl std::error::Error for LinoError {
fn cause(&self) -> Option<&std::error::Error> {
match *self {
LinoError::DbError(ref err) => Some(err),
+ LinoError::IoError(ref err) => Some(err),
LinoError::NotFound(_) => None,
LinoError::BadRequest(_) => None,
}
@@ -49,6 +53,12 @@ impl From<rusqlite::Error> for LinoError {
}
}
+impl From<std::io::Error> for LinoError {
+ fn from(err: std::io::Error) -> LinoError {
+ LinoError::IoError(err)
+ }
+}
+
impl From<LinoError> for IronError {
fn from(err: LinoError) -> IronError {
let code = match err {
diff --git a/src/lib.rs b/src/lib.rs
index 6b448d9..fcceb48 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -13,6 +13,8 @@ extern crate persistent;
extern crate params;
extern crate chrono;
extern crate logger;
+extern crate rand;
+extern crate iron_sessionstorage;
pub mod server;
pub mod data;
diff --git a/src/main.rs b/src/main.rs
index 026f610..e687255 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -62,17 +62,18 @@ fn main() {
linoquotes_gamma::data::populate_test_db(&state.connection).unwrap();
}
- for i in 1..1000 {
- linoquotes_gamma::data::new_quote(
- &state.connection,
- "2017-07-10",
- "orbekk",
- &format!("another test {}", i),
- ).unwrap();
- let qid = state.connection.last_insert_rowid();
- info!("Last inserted quote: {}", qid);
- linoquotes_gamma::data::approve_quote(&state.connection, qid).unwrap();
- }
+ // for i in 1..1000 {
+ // linoquotes_gamma::data::new_quote(
+ // &state.connection,
+ // "2017-07-10",
+ // "orbekk",
+ // &format!("another test {}", i),
+ // ).unwrap();
+ // let qid = state.connection.last_insert_rowid();
+ // info!("Last inserted quote: {}", qid);
+ // linoquotes_gamma::data::approve_quote(&state.connection, qid).unwrap();
+ // }
+ info!("Starting...");
linoquotes_gamma::server::serve(state, port);
}
diff --git a/src/server.rs b/src/server.rs
index 4816c33..54beb34 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -8,11 +8,14 @@ use rusqlite::Connection;
use std::collections::BTreeMap;
use persistent::Write;
use handlebars_iron::handlebars::to_json;
-use serde_json::Map;
+use serde_json;
use params;
use error::LinoError;
use chrono;
use logger::Logger;
+use iron_sessionstorage::{self, SessionStorage, SessionRequestExt};
+use iron_sessionstorage::backends::SignedCookieBackend;
+use rand::{OsRng, Rng};
#[derive(Debug)]
pub struct State {
@@ -22,6 +25,41 @@ impl iron::typemap::Key for State {
type Value = State;
}
+#[derive(Debug, Clone, Serialize)]
+struct UserId(u64);
+impl iron_sessionstorage::Value for UserId {
+ fn get_key() -> &'static str {
+ "user_id"
+ }
+ fn into_raw(self) -> String {
+ format!("{}", self.0)
+ }
+ fn from_raw(value: String) -> Option<Self> {
+ value.parse().ok().map(|i| UserId(i))
+ }
+}
+
+fn user_id(r: &mut Request) -> IronResult<UserId> {
+ if let Some(user_id) = r.session().get::<UserId>()? {
+ return Ok(user_id);
+ }
+
+ let mut rng = OsRng::new().map_err(|e| {
+ // This is ugly, but I don't know how to annotate the expression.
+ let e: LinoError = From::from(e);
+ e
+ })?;
+ let user_id = UserId(rng.next_u64());
+ r.session().set(user_id.clone())?;
+ Ok(user_id)
+}
+
+fn make_result(r: &mut Request) -> IronResult<serde_json::Map<String, serde_json::value::Value>> {
+ let mut result = serde_json::Map::new();
+ result.insert("user_id".to_string(), to_json(&user_id(r)?));
+ Ok(result)
+}
+
fn get_param(r: &mut Request, param: &str) -> IronResult<String> {
let map = itry!(r.get_ref::<params::Params>());
match map.get(param) {
@@ -67,7 +105,7 @@ fn make_renderer() -> HandlebarsEngine {
}
fn quotes(r: &mut Request) -> IronResult<Response> {
- let mut result = Map::new();
+ let mut result = make_result(r)?;
let quote_id = get_param(r, "id").ok().and_then(
|id| id.parse::<i64>().ok(),
);
@@ -90,9 +128,9 @@ fn quotes(r: &mut Request) -> IronResult<Response> {
)))
}
-fn add_get(_r: &mut Request) -> IronResult<Response> {
+fn add_get(r: &mut Request) -> IronResult<Response> {
let date = chrono::offset::Local::now().format("%Y-%m-%d").to_string();
- let mut result = Map::new();
+ let mut result = make_result(r)?;
result.insert("date".to_string(), to_json(&date));
Ok(Response::with((
status::Ok,
@@ -102,6 +140,7 @@ fn add_get(_r: &mut Request) -> IronResult<Response> {
}
fn add_post(r: &mut Request) -> IronResult<Response> {
+ let result = make_result(r)?;
let nick = get_param(r, "nick")?;
let date = get_param(r, "date")?;
let quote = get_param(r, "quote")?;
@@ -127,12 +166,12 @@ fn add_post(r: &mut Request) -> IronResult<Response> {
Ok(Response::with((
status::Ok,
Header(ContentType::html()),
- Template::new("add_post", Map::new()),
+ Template::new("add_post", result)
)))
}
fn approve(r: &mut Request) -> IronResult<Response> {
- let mut result = Map::new();
+ let mut result = make_result(r)?;
let quote_id = get_param(r, "id").ok().and_then(
|id| id.parse::<i64>().ok(),
);
@@ -185,7 +224,7 @@ pub fn vote(r: &mut Request) -> IronResult<Response> {
data::get_quote(&state.connection, quote_id)?
};
- let mut result = Map::new();
+ let mut result = make_result(r)?;
result.insert("quote".to_string(), to_json(&quote));
Ok(Response::with((
@@ -206,13 +245,16 @@ pub fn serve(state: State, port: u16) {
approve: get "/approve.jsp" => approve,
vote: get "/vote" => vote,
);
- let (logger_before, logger_after) = Logger::new(None);
let mut chain = Chain::new(router);
+ let (logger_before, logger_after) = Logger::new(None);
chain.link_before(logger_before);
+ let key = data::get_key(&state.connection, "session").expect("session key");
+ chain.link_around(SessionStorage::new(SignedCookieBackend::new(key)));
chain.link_after(make_renderer());
chain.link(Write::<State>::both(state));
chain.link_after(logger_after);
+
let bind_address = format!("{}:{}", "::", port);
let _server = Iron::new(chain).http(bind_address.as_str());
info!("Serving on {}", bind_address);