From 432d3fce601e9ddc2060519053791f832cb98492 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Sun, 9 Jul 2017 05:23:03 -0400 Subject: add: Display quotes from sqlite. --- Cargo.lock | 11 ++++++++ Cargo.toml | 1 + src/data.rs | 86 +++++++++++++++++++++++++++++++++++++++++++---------------- src/lib.rs | 3 +++ src/main.rs | 56 +++++++++++++++++++++++--------------- src/server.rs | 79 +++++++++++++++++++++++++++++++++++++++--------------- 6 files changed, 169 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c4291d..cd32aa3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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)", @@ -297,6 +298,15 @@ name = "percent-encoding" 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" @@ -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" diff --git a/Cargo.toml b/Cargo.toml index bbed912..38ed955 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 { 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: " 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: " hvor er jantho?".to_owned(), + }, + ]), + ); data } -type Result = std::result::Result>; 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> { + 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::(4) / row.get::(5)), + votes: row.get(5), + } + })?; + let result = rows.map(|r| r.map_err(|e| From::from(e))).collect(); + result +} diff --git a/src/lib.rs b/src/lib.rs index 6d73a2f..b06f517 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 { - 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 { + let mut result = Map::new(); + let quotes = { + let mu = r.get::>().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::::both(state)); let bind_address = format!("{}:{}", "::", port); let _server = Iron::new(chain).http(bind_address.as_str()); info!("Serving on {}", bind_address); -- cgit v1.2.3