summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKjetil Orbekk <kjetil.orbekk@gmail.com>2017-07-23 08:15:01 -0400
committerKjetil Orbekk <kjetil.orbekk@gmail.com>2017-07-23 08:15:01 -0400
commit87b51491bcffc1c8f1a4d4c197dec2d7cd4bc113 (patch)
tree577f001778472abc4794f0e9f5da9ed57decc18d
parentc515dd5fb598c1e3bdb17d6ef78109b6c99369c2 (diff)
add: IP and timestamp tracking
Track client IP address and creation timestamps for votes and quotes
-rw-r--r--src/data.rs20
-rw-r--r--src/server.rs50
2 files changed, 47 insertions, 23 deletions
diff --git a/src/data.rs b/src/data.rs
index 8178bb9..c9e5b92 100644
--- a/src/data.rs
+++ b/src/data.rs
@@ -69,6 +69,8 @@ pub fn init(c: &Connection) -> Result<()> {
quote_date DATE CHECK (DATE(quote_date, '+0 days') IS quote_date),
author TEXT NOT NULL,
content TEXT NOT NULL,
+ ip TEXT NOT NULL,
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT unique_content UNIQUE (quote_date, content)
);
@@ -77,6 +79,8 @@ pub fn init(c: &Connection) -> Result<()> {
user_id INTEGER NOT NULL,
quote_id INTEGER NOT NULL,
score INTEGER NOT NULL,
+ ip TEXT NOT NULL,
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(quote_id) REFERENCES quotes(id)
CONSTRAINT unique_vote UNIQUE (user_id, quote_id)
);
@@ -135,25 +139,25 @@ pub fn get_key(c: &Connection, id: &str) -> Result<Vec<u8>> {
Ok(key)
}
-pub fn new_quote(c: &Connection, date: &str, author: &str, content: &str) -> Result<()> {
+pub fn new_quote(c: &Connection, date: &str, author: &str, content: &str, ip: &str) -> Result<()> {
c.execute(
r#"
- INSERT INTO quotes (quote_date, author, content) VALUES
- (?1, ?2, ?3);
+ INSERT INTO quotes (quote_date, author, content, ip) VALUES
+ (?1, ?2, ?3, ?4);
"#,
- &[&date, &author, &content],
+ &[&date, &author, &content, &ip],
)?;
info!("New quote added by {}", author);
Ok(())
}
-pub fn new_vote(c: &Connection, user_id: i64, quote_id: i64, score: i32) -> Result<()> {
+pub fn new_vote(c: &Connection, user_id: i64, quote_id: i64, score: i32, ip: &str) -> Result<()> {
c.execute(
r#"
- INSERT OR REPLACE INTO votes (quote_id, user_id, score) VALUES
- (?1, ?2, ?3)
+ INSERT OR REPLACE INTO votes (quote_id, user_id, score, ip) VALUES
+ (?1, ?2, ?3, ?4)
"#,
- &[&quote_id, &user_id, &score],
+ &[&quote_id, &user_id, &score, &ip],
)?;
info!("New vote: quote_id({}) score({})", quote_id, score);
Ok(())
diff --git a/src/server.rs b/src/server.rs
index fc9ed68..55cdd01 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -19,6 +19,8 @@ use rand::{OsRng, Rng};
use router::Router;
use iron::mime;
+const ROFLCOPTER: &'static [u8] = include_bytes!("data/roflcopter.gif");
+
#[derive(Debug)]
pub struct State {
pub connection: Connection,
@@ -41,6 +43,16 @@ impl iron_sessionstorage::Value for UserId {
}
}
+fn user_ip(r: &Request) -> IronResult<String> {
+ if let Some(raw) = r.headers.get_raw("X-TAT-Client-IP").and_then(|h| h.first()) {
+ String::from_utf8(raw.clone()).map_err(|e| {
+ From::from(LinoError::BadRequest(format!("{}", e)))
+ })
+ } else {
+ Ok(format!("{}", r.remote_addr))
+ }
+}
+
fn user_id(r: &mut Request) -> IronResult<UserId> {
if let Some(user_id) = r.session().get::<UserId>()? {
return Ok(user_id);
@@ -113,9 +125,10 @@ fn quotes(r: &mut Request) -> IronResult<Response> {
|id| id.parse::<i64>().ok(),
);
let ordering = get_param(r, "order").unwrap_or("".to_string());
- let limit = get_param(r, "limit").ok().and_then(
- |limit| limit.parse::<usize>().ok(),
- ).unwrap_or(200);
+ let limit = get_param(r, "limit")
+ .ok()
+ .and_then(|limit| limit.parse::<usize>().ok())
+ .unwrap_or(200);
let quotes = {
let mu = r.get::<Write<State>>().unwrap();
@@ -128,7 +141,10 @@ fn quotes(r: &mut Request) -> IronResult<Response> {
let quotes = quotes.into_iter().take(limit).collect::<Vec<_>>();
result.insert("quotes".to_string(), to_json(&quotes));
result.insert("scores".to_string(), to_json(&(1..6).collect::<Vec<_>>()));
- result.insert("display_more".to_string(), to_json(&(quotes.len() == limit)));
+ result.insert(
+ "display_more".to_string(),
+ to_json(&(quotes.len() == limit)),
+ );
Ok(Response::with((
status::Ok,
Header(ContentType::html()),
@@ -152,6 +168,7 @@ fn add_post(r: &mut Request) -> IronResult<Response> {
let nick = get_param(r, "nick")?;
let date = get_param(r, "date")?;
let quote = get_param(r, "quote")?;
+ let ip = user_ip(r)?;
macro_rules! check {
($i:ident) => (
@@ -168,7 +185,7 @@ fn add_post(r: &mut Request) -> IronResult<Response> {
{
let mu = r.get::<Write<State>>().unwrap();
let state = mu.lock().unwrap();
- data::new_quote(&state.connection, &date, &nick, &quote)?;
+ data::new_quote(&state.connection, &date, &nick, &quote, &ip)?;
}
Ok(Response::with((
@@ -217,6 +234,7 @@ pub fn vote(r: &mut Request) -> IronResult<Response> {
From::from(LinoError::BadRequest(format!("id: {}", e)))
})
})?;
+ let ip = user_ip(r)?;
let vote = get_param(r, "vote").and_then(|id| {
id.parse::<i32>().map_err(|e| {
From::from(LinoError::BadRequest(format!("vote: {}", e)))
@@ -229,7 +247,7 @@ 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, user_id.0, quote_id, vote)?;
+ data::new_vote(&state.connection, user_id.0, quote_id, vote, &ip)?;
data::get_quote(&state.connection, quote_id, user_id.0)?
};
@@ -243,17 +261,19 @@ pub fn vote(r: &mut Request) -> IronResult<Response> {
)))
}
-const ROFLCOPTER: &'static [u8] = include_bytes!("data/roflcopter.gif");
-
fn static_file(r: &mut Request) -> IronResult<Response> {
- let file = iexpect!(r.extensions.get::<Router>().unwrap().find("file"),
- status::BadRequest);
- let gif = ContentType(mime::Mime(mime::TopLevel::Image,
- mime::SubLevel::Gif, vec![]));
+ let file = iexpect!(
+ r.extensions.get::<Router>().unwrap().find("file"),
+ status::BadRequest
+ );
+ let gif = ContentType(mime::Mime(
+ mime::TopLevel::Image,
+ mime::SubLevel::Gif,
+ vec![],
+ ));
Ok(match file {
- "roflcopter.gif" =>
- Response::with((status::Ok, Header(gif), ROFLCOPTER)),
- _ => Response::with(status::NotFound)
+ "roflcopter.gif" => Response::with((status::Ok, Header(gif), ROFLCOPTER)),
+ _ => Response::with(status::NotFound),
})
}