diff options
author | Kjetil Orbekk <kjetil.orbekk@gmail.com> | 2017-07-09 05:23:03 -0400 |
---|---|---|
committer | Kjetil Orbekk <kjetil.orbekk@gmail.com> | 2017-07-09 05:23:03 -0400 |
commit | 432d3fce601e9ddc2060519053791f832cb98492 (patch) | |
tree | 7045140909aadf388517960b249ea2631974ee91 | |
parent | da3cfff817b0eaead9be11d945e8f7914c8e801c (diff) |
add: Display quotes from sqlite.
-rw-r--r-- | Cargo.lock | 11 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/data.rs | 86 | ||||
-rw-r--r-- | src/lib.rs | 3 | ||||
-rw-r--r-- | src/main.rs | 56 | ||||
-rw-r--r-- | src/server.rs | 79 |
6 files changed, 169 insertions, 67 deletions
@@ -8,6 +8,7 @@ dependencies = [ "handlebars-iron 0.24.1 (registry+https://github.com/rust-lang/crates.io-index)", "iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "persistent 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "router 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rusqlite 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -298,6 +299,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "persistent" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "pest" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -684,6 +694,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "1708c0628602a98b52fad936cf3edb9a107af06e52e49fdf0707e884456a6af6" "checksum num_cpus 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aec53c34f2d0247c5ca5d32cca1478762f301740468ee9ee6dcb7a0dd7a0c584" "checksum percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de154f638187706bde41d9b4738748933d64e6b37bdbffc0b47a97d16a6ae356" +"checksum persistent 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c9c94f2ef72dc272c6bcc8157ccf2bc7da14f4c58c69059ac2fc48492d6916" "checksum pest 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0a6dda33d67c26f0aac90d324ab2eb7239c819fc7b2552fe9faa4fe88441edc8" "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" "checksum plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a6a0dc3910bc8db877ffed8e457763b317cf880df4ae19109b9f77d277cf6e0" @@ -15,3 +15,4 @@ clap = "^2.25.0" serde = "^1.0.0" serde_json = "^1.0.0" serde_derive = "^1.0.0" +persistent = "^0.3.0" diff --git a/src/data.rs b/src/data.rs index 83f5360..2149a06 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,37 +1,40 @@ use serde_json::{Value, Map}; use handlebars_iron::handlebars::to_json; use rusqlite::Connection; -use std; +use error::Result; -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, Clone)] pub struct Quote { - id: u64, - author: String, + id: i64, date: String, + author: String, score: String, votes: u32, - content: String + content: String, } pub fn make_data() -> Map<String, Value> { let mut data = Map::new(); - data.insert("quotes".to_string(), to_json(&vec!( - &Quote { - id: 1, - author: "panda_man".to_owned(), - date: "2017-07-01".to_owned(), - score: format!("{:.2}", 450.0 / 96.0), - votes: 99, - content: "<orbekk> hvor er jantho?".to_owned(), - } - ))); + data.insert( + "quotes".to_string(), + to_json(&vec![ + &Quote { + id: 1, + author: "panda_man".to_owned(), + date: "2017-07-01".to_owned(), + score: format!("{:.2}", 450.0 / 96.0), + votes: 99, + content: "<orbekk> hvor er jantho?".to_owned(), + }, + ]), + ); data } -type Result<T> = std::result::Result<T, Box<std::error::Error>>; pub fn init(c: &Connection) -> Result<()> { info!("Initializing db"); - try!(c.execute_batch(r#" + try!(c.execute_batch( + r#" CREATE TABLE quotes ( id INTEGER PRIMARY KEY AUTOINCREMENT, approved BOOL DEFAULT false, @@ -47,16 +50,53 @@ pub fn init(c: &Connection) -> Result<()> { score INTEGER NOT NULL, FOREIGN KEY(quote_id) REFERENCES quotes(id) ); - "#)); + "#, + )); Ok(()) } pub fn populate_test_db(c: &Connection) -> Result<()> { info!("Populating test db"); - try!(c.execute_batch(r#" - INSERT INTO quotes (timestamp, author, content) VALUES - ('2017-07-09', 'orbekk', 'test quote'), - ('2017-07-09', 'orbekk', 'test quote2'); - "#)); + try!(c.execute_batch( + r#" + INSERT INTO quotes (id, approved, timestamp, author, content) VALUES + (1, 1, '2017-07-09', 'orbekk', 'test quote'), + (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, 3), + (1, 4); + "#, + )); Ok(()) } + +pub fn get_quotes(c: &Connection) -> Result<Vec<Quote>> { + let mut stmt = c.prepare( + r#" + SELECT q.id, q.timestamp, q.author, q.content, + sum(v.score), count(v.score) + FROM quotes q + JOIN votes v + WHERE q.approved + GROUP BY 1, 2, 3, 4; + "#, + )?; + + let mut rows = stmt.query_map(&[], |row| { + Quote { + id: row.get(0), + date: row.get(1), + author: row.get(2), + content: row.get(3), + score: format!("{:.2}", row.get::<i32, f64>(4) / row.get::<i32, f64>(5)), + votes: row.get(5), + } + })?; + let result = rows.map(|r| r.map_err(|e| From::from(e))).collect(); + result +} @@ -1,5 +1,6 @@ #[macro_use] extern crate log; +#[macro_use] extern crate iron; #[macro_use] extern crate router; @@ -8,6 +9,8 @@ extern crate serde_json; #[macro_use] extern crate serde_derive; extern crate rusqlite; +extern crate persistent; pub mod server; pub mod data; +mod error; diff --git a/src/main.rs b/src/main.rs index 68d83f7..49f110e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,18 +14,29 @@ fn main() { let matches = App::new("linoquotes") .version("3.0.0") .author("Kjetil Ørbekk") - .about("Quote db for #linux.no. Run with \ - RUST_LOG=linoquotes_gamma=info to enable logging.") - .arg(Arg::with_name("port") - .short("p").long("port").takes_value(true) - .help("Port to serve on")) - .arg(Arg::with_name("db_file") - .long("db_file").takes_value(true) - .help("Path to sqlite database (use in-memory test \ - database if unset)")) - .arg(Arg::with_name("use_test_db") - .long("use_test_db") - .help("Use in-memory test database")) + .about( + "Quote db for #linux.no. Run with \ + RUST_LOG=linoquotes_gamma=info to enable logging.", + ) + .arg( + Arg::with_name("port") + .short("p") + .long("port") + .takes_value(true) + .help("Port to serve on"), + ) + .arg( + Arg::with_name("db_file") + .long("db_file") + .takes_value(true) + .help( + "Path to sqlite database (use in-memory test \ + database if unset)", + ), + ) + .arg(Arg::with_name("use_test_db").long("use_test_db").help( + "Use in-memory test database", + )) .get_matches(); let port = matches @@ -36,20 +47,21 @@ fn main() { let use_test_db = matches.is_present("use_test_db"); let db_file = matches.value_of("db_file"); - let connection = match db_file { - None => { - assert!(use_test_db, "--db_file or --use_test_db must be set"); - rusqlite::Connection::open_in_memory().unwrap() - }, - Some(ref path) => rusqlite::Connection::open(path).unwrap() + let state = linoquotes_gamma::server::State { + connection: match db_file { + None => { + assert!(use_test_db, "--db_file or --use_test_db must be set"); + rusqlite::Connection::open_in_memory().unwrap() + } + Some(ref path) => rusqlite::Connection::open(path).unwrap(), + }, }; - linoquotes_gamma::data::init(&connection).unwrap(); + linoquotes_gamma::data::init(&state.connection).unwrap(); if use_test_db { - linoquotes_gamma::data::populate_test_db(&connection).unwrap(); + linoquotes_gamma::data::populate_test_db(&state.connection).unwrap(); } - info!("Starting"); - linoquotes_gamma::server::serve(port); + linoquotes_gamma::server::serve(state, port); } diff --git a/src/server.rs b/src/server.rs index 37c5713..4af8c83 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,37 +1,72 @@ -use iron::headers::{ContentType}; -use iron::modifiers::{Header}; -use iron::{Iron, Chain, Request, Response, IronResult, status}; +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 data; +use persistent::Write; +use handlebars_iron::handlebars::to_json; +use serde_json::{Value, Map}; -fn renderer() -> HandlebarsEngine { - let mut e = HandlebarsEngine::new(); +#[derive(Debug)] +pub struct State { + pub connection: Connection, +} +impl iron::typemap::Key for State { + type Value = State; +} - let mut templates = BTreeMap::new(); - templates.insert("quotes".to_string(), - include_str!("data/templates/quotes.hbs").to_string()); +fn make_renderer() -> HandlebarsEngine { + let mut e = HandlebarsEngine::new(); - e.add(Box::new(MemorySource(templates))); - if let Err(r) = e.reload() { - panic!("Error loading templates: {}", r) - } - e + let mut templates = BTreeMap::new(); + templates.insert( + "quotes".to_string(), + include_str!("data/templates/quotes.hbs").to_string(), + ); + + e.add(Box::new(MemorySource(templates))); + if let Err(r) = e.reload() { + panic!("Error loading templates: {}", r) + } + e } fn info(_r: &mut Request) -> IronResult<Response> { - let data = data::make_data(); - Ok(Response::with((status::Ok, - Header(ContentType::html()), - Template::new("quotes", data)))) + let data = data::make_data(); + Ok(Response::with(( + status::Ok, + Header(ContentType::html()), + Template::new("quotes", data), + ))) +} + +fn quotes(r: &mut Request) -> IronResult<Response> { + let mut result = Map::new(); + let quotes = { + let mu = r.get::<Write<State>>().unwrap(); + let state = mu.lock().unwrap(); + try!(data::get_quotes(&state.connection)) + }; + result.insert("quotes".to_string(), to_json("es)); + Ok(Response::with(( + status::Ok, + Header(ContentType::html()), + Template::new("quotes", result), + ))) } -pub fn serve(port: u16) { - let router = router!( - info: get "/" => info, +pub fn serve(state: State, port: u16) { + let router = + router!( + info: get "/info" => info, + index: get "/" => quotes, ); let mut chain = Chain::new(router); - chain.link_after(renderer()); + chain.link_after(make_renderer()); + chain.link(Write::<State>::both(state)); let bind_address = format!("{}:{}", "::", port); let _server = Iron::new(chain).http(bind_address.as_str()); info!("Serving on {}", bind_address); |