summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKjetil Orbekk <kjetil.orbekk@gmail.com>2017-07-09 05:23:03 -0400
committerKjetil Orbekk <kjetil.orbekk@gmail.com>2017-07-09 05:23:03 -0400
commit432d3fce601e9ddc2060519053791f832cb98492 (patch)
tree7045140909aadf388517960b249ea2631974ee91
parentda3cfff817b0eaead9be11d945e8f7914c8e801c (diff)
add: Display quotes from sqlite.
-rw-r--r--Cargo.lock11
-rw-r--r--Cargo.toml1
-rw-r--r--src/data.rs86
-rw-r--r--src/lib.rs3
-rw-r--r--src/main.rs56
-rw-r--r--src/server.rs79
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)",
@@ -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"
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<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
+}
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<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(&quotes));
+ 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);