From 87b51491bcffc1c8f1a4d4c197dec2d7cd4bc113 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Sun, 23 Jul 2017 08:15:01 -0400 Subject: add: IP and timestamp tracking Track client IP address and creation timestamps for votes and quotes --- src/data.rs | 20 ++++++++++++-------- src/server.rs | 50 +++++++++++++++++++++++++++++++++++--------------- 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> { 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) "#, - &["e_id, &user_id, &score], + &["e_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 { + 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 { if let Some(user_id) = r.session().get::()? { return Ok(user_id); @@ -113,9 +125,10 @@ fn quotes(r: &mut Request) -> IronResult { |id| id.parse::().ok(), ); let ordering = get_param(r, "order").unwrap_or("".to_string()); - let limit = get_param(r, "limit").ok().and_then( - |limit| limit.parse::().ok(), - ).unwrap_or(200); + let limit = get_param(r, "limit") + .ok() + .and_then(|limit| limit.parse::().ok()) + .unwrap_or(200); let quotes = { let mu = r.get::>().unwrap(); @@ -128,7 +141,10 @@ fn quotes(r: &mut Request) -> IronResult { let quotes = quotes.into_iter().take(limit).collect::>(); result.insert("quotes".to_string(), to_json("es)); result.insert("scores".to_string(), to_json(&(1..6).collect::>())); - 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 { 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 { { let mu = r.get::>().unwrap(); let state = mu.lock().unwrap(); - data::new_quote(&state.connection, &date, &nick, "e)?; + data::new_quote(&state.connection, &date, &nick, "e, &ip)?; } Ok(Response::with(( @@ -217,6 +234,7 @@ pub fn vote(r: &mut Request) -> IronResult { From::from(LinoError::BadRequest(format!("id: {}", e))) }) })?; + let ip = user_ip(r)?; let vote = get_param(r, "vote").and_then(|id| { id.parse::().map_err(|e| { From::from(LinoError::BadRequest(format!("vote: {}", e))) @@ -229,7 +247,7 @@ pub fn vote(r: &mut Request) -> IronResult { let quote = { let mu = r.get::>().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 { ))) } -const ROFLCOPTER: &'static [u8] = include_bytes!("data/roflcopter.gif"); - fn static_file(r: &mut Request) -> IronResult { - let file = iexpect!(r.extensions.get::().unwrap().find("file"), - status::BadRequest); - let gif = ContentType(mime::Mime(mime::TopLevel::Image, - mime::SubLevel::Gif, vec![])); + let file = iexpect!( + r.extensions.get::().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), }) } -- cgit v1.2.3