use data; use handlebars_iron::{HandlebarsEngine, MemorySource, Template}; use iron::headers::ContentType; use iron::modifiers::Header; use iron::{self, Iron, Plugin, Chain, Request, Response, IronResult, status}; use rusqlite::Connection; use std::collections::BTreeMap; use persistent::Write; use handlebars_iron::handlebars::to_json; 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 { pub connection: Connection, } 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 { value.parse().ok().map(|i| UserId(i)) } } fn user_id(r: &mut Request) -> IronResult { if let Some(user_id) = r.session().get::()? { 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> { 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 { let map = itry!(r.get_ref::()); match map.get(param) { Some(¶ms::Value::String(ref v)) => Ok(v.to_string()), _ => Err(From::from(LinoError::NotFound(param.to_string()))), } } fn make_renderer() -> HandlebarsEngine { let mut e = HandlebarsEngine::new(); let mut templates = BTreeMap::new(); templates.insert( "base".to_string(), include_str!("data/templates/base.hbs").to_string(), ); templates.insert( "quotes".to_string(), include_str!("data/templates/quotes.hbs").to_string(), ); templates.insert( "add".to_string(), include_str!("data/templates/add.hbs").to_string(), ); templates.insert( "add_post".to_string(), include_str!("data/templates/add_post.hbs").to_string(), ); templates.insert( "approve".to_string(), include_str!("data/templates/approve.hbs").to_string(), ); templates.insert( "vote".to_string(), include_str!("data/templates/vote.hbs").to_string(), ); e.add(Box::new(MemorySource(templates))); if let Err(r) = e.reload() { panic!("Error loading templates: {}", r) } e } fn quotes(r: &mut Request) -> IronResult { let mut result = make_result(r)?; let quote_id = get_param(r, "id").ok().and_then( |id| id.parse::().ok(), ); let ordering = get_param(r, "order").unwrap_or("".to_string()); let quotes = { 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)?, } }; let quotes = quotes.into_iter().collect::>(); result.insert("quotes".to_string(), to_json("es)); Ok(Response::with(( status::Ok, Header(ContentType::html()), Template::new("quotes", result), ))) } fn add_get(r: &mut Request) -> IronResult { let date = chrono::offset::Local::now().format("%Y-%m-%d").to_string(); let mut result = make_result(r)?; result.insert("date".to_string(), to_json(&date)); Ok(Response::with(( status::Ok, Header(ContentType::html()), Template::new("add", result), ))) } fn add_post(r: &mut Request) -> IronResult { let result = make_result(r)?; let nick = get_param(r, "nick")?; let date = get_param(r, "date")?; let quote = get_param(r, "quote")?; macro_rules! check { ($i:ident) => ( if $i.is_empty() { return Err(From::from(LinoError::BadRequest( format!("missing parameter: {}", stringify!($i))))); } ) } check!(nick); check!(date); check!(quote); { let mu = r.get::>().unwrap(); let state = mu.lock().unwrap(); data::new_quote(&state.connection, &date, &nick, "e)?; } Ok(Response::with(( status::Ok, Header(ContentType::html()), Template::new("add_post", result) ))) } fn approve(r: &mut Request) -> IronResult { let mut result = make_result(r)?; let quote_id = get_param(r, "id").ok().and_then( |id| id.parse::().ok(), ); let action = get_param(r, "action").unwrap_or("".to_string()); let quotes = { let mu = r.get::>().unwrap(); let state = mu.lock().unwrap(); if let Some(quote_id) = quote_id { info!("Approval for quote({}): {}", quote_id, action); if action == "approve" { data::approve_quote(&state.connection, quote_id)?; } else if action == "reject" { data::delete_quote(&state.connection, quote_id)?; } else { return Err(From::from( LinoError::BadRequest(format!("invalid action: {}", action)), )); } } data::get_pending_quotes(&state.connection)? }; result.insert("quotes".to_string(), to_json("es)); Ok(Response::with(( status::Ok, Header(ContentType::html()), Template::new("approve", result), ))) } pub fn vote(r: &mut Request) -> IronResult { let quote_id = get_param(r, "id").and_then(|id| { id.parse::().map_err(|e| { From::from(LinoError::BadRequest(format!("id: {}", e))) }) })?; let vote = get_param(r, "vote").and_then(|id| { id.parse::().map_err(|e| { From::from(LinoError::BadRequest(format!("vote: {}", e))) }) })?; if vote < 1 || vote > 5 { return Err(From::from(LinoError::BadRequest("bad vote".to_string()))); } 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)? }; let mut result = make_result(r)?; result.insert("quote".to_string(), to_json("e)); Ok(Response::with(( status::Ok, Header(ContentType::html()), Template::new("vote", result), ))) } pub fn serve(state: State, port: u16) { let router = router!( index: get "/" => quotes, add_get: get "/add.jsp" => add_get, add_post: post "/add.jsp" => add_post, quotes_jsp: get "/quotes.jsp" => quotes, view_quote: get "/view_quote" => quotes, approve: get "/approve.jsp" => approve, vote: get "/vote" => vote, ); 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::::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); }