diff --git a/Cargo.lock b/Cargo.lock index 78a1c33b0a..d316d57827 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -133,6 +133,7 @@ dependencies = [ "flate2 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", "git2 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", + "handlebars 0.29.1 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -732,6 +733,20 @@ dependencies = [ "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "handlebars" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "pest 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "hex" version = "0.2.0" @@ -1139,6 +1154,11 @@ name = "percent-encoding" version = "1.0.0" source = "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" + [[package]] name = "phf" version = "0.7.21" @@ -1200,6 +1220,11 @@ dependencies = [ "getopts 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "quick-error" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "quine-mc_cluskey" version = "0.2.4" @@ -1928,6 +1953,7 @@ dependencies = [ "checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb" "checksum getopts 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "65922871abd2f101a2eb0eaebadc66668e54a87ad9c3dd82520b5f86ede5eff9" "checksum git2 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0c1c0203d653f4140241da0c1375a404f0a397249ec818cd2076c6280c50f6fa" +"checksum handlebars 0.29.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fb04af2006ea09d985fef82b81e0eb25337e51b691c76403332378a53d521edc" "checksum hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa" "checksum html5ever 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba3a1fd1857a714d410c191364c5d7bf8a6487c0ab5575146d37dd7eb17ef523" "checksum htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" @@ -1974,6 +2000,7 @@ dependencies = [ "checksum openssl-probe 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d98df0270d404ccd3c050a41d579c52d1db15375168bb3471e04ec0f5f378daf" "checksum openssl-sys 0.9.20 (registry+https://github.com/rust-lang/crates.io-index)" = "0ad395f1cee51b64a8d07cc8063498dc7554db62d5f3ca87a67f4eed2791d0c8" "checksum percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de154f638187706bde41d9b4738748933d64e6b37bdbffc0b47a97d16a6ae356" +"checksum pest 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0a6dda33d67c26f0aac90d324ab2eb7239c819fc7b2552fe9faa4fe88441edc8" "checksum phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "cb325642290f28ee14d8c6201159949a872f220c62af6e110a56ea914fbe42fc" "checksum phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d62594c0bb54c464f633175d502038177e90309daf2e0158be42ed5f023ce88f" "checksum phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6b07ffcc532ccc85e3afc45865469bf5d9e4ef5bfcf9622e3cfe80c2d275ec03" @@ -1982,6 +2009,7 @@ dependencies = [ "checksum pq-sys 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4dfb5e575ef93a1b7b2a381d47ba7c5d4e4f73bff37cee932195de769aad9a54" "checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" "checksum pulldown-cmark 0.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "378e941dbd392c101f2cb88097fa4d7167bc421d4b88de3ff7dbee503bc3233b" +"checksum quick-error 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eda5fe9b71976e62bc81b781206aaa076401769b2143379d3eb2118388babac4" "checksum quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "07589615d719a60c8dd8a4622e7946465dfef20d1a428f969e3443e7386d5f45" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum r2d2 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2c8284508b38df440f8f3527395e23c4780b22f74226b270daf58fee38e4bcce" diff --git a/Cargo.toml b/Cargo.toml index 014b17c24a..a2e258409e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,8 @@ conduit-static = "0.8" conduit-git-http-backend = "0.8" civet = "0.9" +handlebars = "0.29.1" + [dev-dependencies] conduit-test = "0.8" hyper = "0.11" diff --git a/app/styles/app.scss b/app/styles/app.scss index d21c08f0ae..63a7855714 100644 --- a/app/styles/app.scss +++ b/app/styles/app.scss @@ -33,7 +33,7 @@ body { @include align-items(center); } -.ember-application > div { +body > div { width: 960px; @media only screen and (max-width: 960px) { width: 100%; @@ -99,19 +99,13 @@ body { } .current-user-links { - display: none; left: auto; right: 0; min-width: 200px; - - &.open { display: block; } } #doc-links { - display: none; left: auto; min-width: 150px; - - &.open { display: block; } } form.search { @include display-flex; @@ -266,9 +260,13 @@ pre { button.dropdown, a.dropdown { color: inherit; cursor: pointer; - .arrow { font-size: 50%; display: inline-block; vertical-align: middle; } + .arrow { + font-size: 50%; + display: inline-block; + vertical-align: middle; + } .arrow::after { content: "▼"; } - &.active .arrow::after { content: "▲"; } + .dropdown-container:hover & .arrow::after { content: "▲"; } } ul.dropdown { @@ -300,12 +298,16 @@ ul.dropdown { } } li.last { border-top: 1px solid $gray-border; } - &.open { + + display: none; + + .dropdown-container:hover & { display: block; visibility: visible; opacity: 1; } } + .dropdown-container { display: inline-block; position: relative; diff --git a/app/styles/crate.scss b/app/styles/crate.scss index dcf8a18253..f4a1749f85 100644 --- a/app/styles/crate.scss +++ b/app/styles/crate.scss @@ -87,11 +87,12 @@ .cur, .total { color: $main-color; font-weight: bold; } .dropdown-container { font-size: 85%; } - a.dropdown { + button.dropdown, a.dropdown { background-color: $main-bg-dark; padding: 10px; display: inline-block; @include border-radius(5px); + border-color: transparent; } } diff --git a/src/bin/server.rs b/src/bin/server.rs index 99eccba50a..563bd9a343 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -63,7 +63,7 @@ fn main() { .unwrap_or(8888) }; let threads = if config.env == Env::Development { - 1 + 4 } else { 50 }; diff --git a/src/category.rs b/src/category.rs index 772ab59fb4..d628567dd7 100644 --- a/src/category.rs +++ b/src/category.rs @@ -186,6 +186,10 @@ impl<'a> NewCategory<'a> { /// Handles the `GET /categories` route. pub fn index(req: &mut Request) -> CargoResult { + Ok(req.json(&index_json(req)?)) +} + +pub fn index_json(req: &Request) -> CargoResult { let conn = req.db_conn()?; let (offset, limit) = req.pagination(10, 100)?; let query = req.query(); @@ -197,20 +201,21 @@ pub fn index(req: &mut Request) -> CargoResult { // Query for the total count of categories let total = Category::count_toplevel(&conn)?; - #[derive(Serialize)] - struct R { - categories: Vec, - meta: Meta, - } - #[derive(Serialize)] - struct Meta { - total: i64, - } - - Ok(req.json(&R { + Ok(R { categories: categories, meta: Meta { total: total }, - })) + }) +} + +#[derive(Serialize, Debug)] +pub struct R { + categories: Vec, + meta: Meta, +} +#[derive(Serialize, Debug)] +#[allow(missing_copy_implementations)] +pub struct Meta { + total: i64, } /// Handles the `GET /categories/:category_id` route. diff --git a/src/dist.rs b/src/dist.rs index 7526b8c0bb..9adb5fe81e 100644 --- a/src/dist.rs +++ b/src/dist.rs @@ -1,61 +1,26 @@ -//! This module implements middleware to serve the compiled emberjs -//! frontend +//! This module implements middleware to serve the compiled, static assets. use std::error::Error; use conduit::{Handler, Request, Response}; -use conduit_static::Static; -use conduit_middleware::AroundMiddleware; +use conduit_static; +use conduit_middleware::Middleware; -use util::RequestProxy; +#[allow(missing_debug_implementations, missing_copy_implementations)] +pub struct Static; -// Can't derive debug because of Handler and Static. -#[allow(missing_debug_implementations)] -pub struct Middleware { - handler: Option>, - dist: Static, -} - -impl Default for Middleware { - fn default() -> Middleware { - Middleware { - handler: None, - dist: Static::new("dist"), - } - } -} - -impl AroundMiddleware for Middleware { - fn with_handler(&mut self, handler: Box) { - self.handler = Some(handler); - } -} - -impl Handler for Middleware { - fn call(&self, req: &mut Request) -> Result> { - // First, attempt to serve a static file. If we're missing a static - // file, then keep going. - match self.dist.call(req) { - Ok(ref resp) if resp.status.0 == 404 => {} - ret => return ret, - } - - // Second, if we're requesting html, then we've only got one page so - // serve up that page. Otherwise proxy on to the rest of the app. - let wants_html = req.headers() - .find("Accept") - .map(|accept| accept.iter().any(|s| s.contains("html"))) - .unwrap_or(false); - // If the route starts with /api, just assume they want the API - // response. Someone is either debugging or trying to download a crate. - let is_api_path = req.path().starts_with("/api"); - if wants_html && !is_api_path { - self.dist.call(&mut RequestProxy { - other: req, - path: Some("/index.html"), - method: None, - }) - } else { - self.handler.as_ref().unwrap().call(req) +impl Middleware for Static { + fn after(&self, req: &mut Request, resp: Result>) + -> Result> + { + match resp { + Ok(resp) => { + if resp.status.0 == 404 { + conduit_static::Static::new("dist").call(req) + } else { + Ok(resp) + } + } + Err(resp) => Err(resp) } } } diff --git a/src/html/_categories.hbs b/src/html/_categories.hbs new file mode 100644 index 0000000000..2e6aca9d82 --- /dev/null +++ b/src/html/_categories.hbs @@ -0,0 +1,12 @@ + diff --git a/src/html/_crates.hbs b/src/html/_crates.hbs new file mode 100644 index 0000000000..91630c02bf --- /dev/null +++ b/src/html/_crates.hbs @@ -0,0 +1,12 @@ + diff --git a/src/html/_keywords.hbs b/src/html/_keywords.hbs new file mode 100644 index 0000000000..4868084259 --- /dev/null +++ b/src/html/_keywords.hbs @@ -0,0 +1,12 @@ + diff --git a/src/html/categories/index.hbs b/src/html/categories/index.hbs new file mode 100644 index 0000000000..ca34720b34 --- /dev/null +++ b/src/html/categories/index.hbs @@ -0,0 +1,74 @@ +{{#>layout}} + {{#*inline "head"}} + Categories + {{/inline}} + + {{#*inline "main"}} +
+ {{embed_svg "crate"}} +

All Categories

+
+ +
+ + +
+ Sort by + + +
+
+ +
+ {{#each categories as |category|}} +
+
+
+ {{category.category}} + + {{category.crates_cnt}} {{pluralize "crate" category.crates_cnt}} + +
+
+ + {{category.description}} + +
+
+
+ {{/each}} +
+ + + {{/inline}} +{{/layout}} diff --git a/src/html/categories/mod.rs b/src/html/categories/mod.rs new file mode 100644 index 0000000000..be34e5606a --- /dev/null +++ b/src/html/categories/mod.rs @@ -0,0 +1,76 @@ +use conduit::{Request, Response}; + +use std::collections::HashMap; +use util::{CargoResult, RequestUtils}; + +extern crate handlebars; +use self::handlebars::*; + +use category; + +pub fn index(req: &mut Request) -> CargoResult { + let mut handlebars = Handlebars::new(); + handlebars.register_helper("embed_svg", Box::new(embed_svg)); + handlebars.register_helper("link_to", Box::new(link_to)); + handlebars.register_helper("pluralize", Box::new(pluralize)); + handlebars.register_template_string("layout", include_str!("../layout.hbs"))?; + handlebars.register_template_string("index", include_str!("index.hbs"))?; + + let mut json = json!(&category::index_json(req)?); + json["current_user"] = json!({"id": 1, "name": "Sean Linsley"}); + json["params"] = json!(req.query()); + + #[derive(Serialize)] + struct Sort {key: String, name: String}; + + let sorts = vec![ + Sort{key: "alpha".into(), name: "Alphabetical".into()}, + Sort{key: "crates".into(), name: "# Crates".into()}, + ]; + json["sorts"] = json!(sorts); + + let sort = req.query().get("sort").unwrap_or(&sorts.first().unwrap().key).to_string(); + json["current_sort"] = json!(sorts.iter().find(|s| s.key == sort).expect("invalid sort key").name); + + // duplicated with `category::index_json` :( + let (offset, limit) = req.pagination(10, 100)?; + json["current_page_start"] = json!(offset); + json["current_page_end"] = json!(limit); + + // TODO: build pagination links + // TODO: limit is 10 here and in `category::index_json`, but it's not actually being applied? (also a problem in production) + + let html = handlebars.render("index", &json).expect("failed to render"); + + Ok(req.html(&html)) +} + +fn embed_svg(h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + let param = h.param(0).expect("no argument passed").value().to_string().replace("\"", ""); + + let mut svgs = HashMap::new(); + svgs.insert("crate".to_string(), include_str!("../svgs/crate.svg").to_string()); + svgs.insert("left-pag".to_string(), include_str!("../svgs/left-pag.svg").to_string()); + svgs.insert("right-pag".to_string(), include_str!("../svgs/right-pag.svg").to_string()); + svgs.insert("sort".to_string(), include_str!("../svgs/sort.svg").to_string()); + + let svg = &svgs[¶m]; + + try!(rc.writer.write(svg.as_ref())); + + Ok(()) +} + +fn link_to(h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + h.template().map(|t| t.render(r, rc)).unwrap_or(Ok(())) +} + +fn pluralize(h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + let string = h.param(0).expect("no argument passed").value().to_string().replace("\"", ""); + let num: u32 = h.param(1).expect("number not passed").value().to_string().parse().expect("invalid number"); + + let plural = if num == 1 { string } else { string + "s" }; + + try!(rc.writer.write(plural.into_bytes().as_ref())); + Ok(()) +} diff --git a/src/html/index.hbs b/src/html/index.hbs new file mode 100644 index 0000000000..5a44d97bd6 --- /dev/null +++ b/src/html/index.hbs @@ -0,0 +1,71 @@ +{{#>layout}} + {{#*inline "head"}} + Cargo: packages for Rust + {{/inline}} + + {{#*inline "main"}} + + +
+
+ Instantly publish your crates and install them. Use the API to + interact and find out more information about available crates. Become + a contributor and enhance the site with your work. +
+ +
+
+ {{embed_svg "download"}} + {{format_num num_downloads}} + Downloads +
+
+ {{embed_svg "crate"}} + {{format_num num_crates}} + Crates in stock +
+
+
+ +
+
+

New Crates

+ {{>crates crates=new_crates}} +
+
+

Most Downloaded

+ {{>crates crates=most_downloaded}} +
+
+

Just Updated

+ {{>crates crates=just_updated}} +
+
+

Most Recent Downloads

+ {{>crates crates=most_recently_downloaded}} +
+
+

Popular Keywords (see all)

+ {{>keywords keywords=popular_keywords}} +
+
+

Popular Categories (see all)

+ {{>categories categories=popular_categories}} +
+
+ {{/inline}} +{{/layout}} diff --git a/src/html/layout.hbs b/src/html/layout.hbs new file mode 100644 index 0000000000..0b1d48e510 --- /dev/null +++ b/src/html/layout.hbs @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + {{!google-jsapi}} + + {{>head}} + + + + + + + + + + + +
+ {{!flash-message}} + {{>main}} +
+ + + + + + {{!content-for "body-footer"}} + + diff --git a/src/html/mod.rs b/src/html/mod.rs new file mode 100644 index 0000000000..f59deab4cf --- /dev/null +++ b/src/html/mod.rs @@ -0,0 +1,59 @@ +use std::collections::HashMap; + +use conduit::{Request, Response}; + +use util::{CargoResult, RequestUtils}; + +extern crate handlebars; +use self::handlebars::*; + +use krate; + +use db::RequestTransaction; + +pub mod categories; + +pub fn index(req: &mut Request) -> CargoResult { + let mut handlebars = Handlebars::new(); + handlebars.register_helper("format_num", Box::new(format_num)); + handlebars.register_helper("embed_svg", Box::new(embed_svg)); + handlebars.register_template_string("layout", include_str!("layout.hbs"))?; + handlebars.register_template_string("index", include_str!("index.hbs"))?; + handlebars.register_template_string("categories", include_str!("_categories.hbs"))?; + handlebars.register_template_string("crates", include_str!("_crates.hbs"))?; + handlebars.register_template_string("keywords", include_str!("_keywords.hbs"))?; + + let mut json = json!(&krate::metadata::summary_json(&*req.db_conn()?)?); + json["current_user"] = json!({"id": 1, "name": "Sean Linsley"}); + // TODO: flash message + // TODO: user image in header + + let html = handlebars.render("index", &json).expect("failed to render"); + + Ok(req.html(&html)) +} + +fn format_num(h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + let param = h.param(0).expect("no argument passed").value(); + let rendered = format!("{}", param); + try!(rc.writer.write(rendered.into_bytes().as_ref())); + Ok(()) +} + +fn embed_svg(h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + let param = h.param(0).expect("no argument passed").value().to_string().replace("\"", ""); + + let mut svgs = HashMap::new(); + svgs.insert("crate".to_string(), include_str!("svgs/crate.svg").to_string()); + svgs.insert("button-download".to_string(), include_str!("svgs/button-download.svg").to_string()); + svgs.insert("download".to_string(), include_str!("svgs/download.svg").to_string()); + svgs.insert("flag".to_string(), include_str!("svgs/flag.svg").to_string()); + svgs.insert("lock".to_string(), include_str!("svgs/lock.svg").to_string()); + svgs.insert("right-arrow".to_string(), include_str!("svgs/right-arrow.svg").to_string()); + + let svg = &svgs[¶m]; + + try!(rc.writer.write(svg.as_ref())); + + Ok(()) +} diff --git a/src/html/svgs/button-download.svg b/src/html/svgs/button-download.svg new file mode 100644 index 0000000000..5afae471fd --- /dev/null +++ b/src/html/svgs/button-download.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/html/svgs/crate.svg b/src/html/svgs/crate.svg new file mode 100644 index 0000000000..9032b25579 --- /dev/null +++ b/src/html/svgs/crate.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/html/svgs/download.svg b/src/html/svgs/download.svg new file mode 100644 index 0000000000..a754591d5e --- /dev/null +++ b/src/html/svgs/download.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/html/svgs/flag.svg b/src/html/svgs/flag.svg new file mode 100644 index 0000000000..ae3735d3c6 --- /dev/null +++ b/src/html/svgs/flag.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/html/svgs/left-pag.svg b/src/html/svgs/left-pag.svg new file mode 100644 index 0000000000..8b619f3221 --- /dev/null +++ b/src/html/svgs/left-pag.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/html/svgs/lock.svg b/src/html/svgs/lock.svg new file mode 100644 index 0000000000..969c618c91 --- /dev/null +++ b/src/html/svgs/lock.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/html/svgs/right-arrow.svg b/src/html/svgs/right-arrow.svg new file mode 100644 index 0000000000..f44739eb1f --- /dev/null +++ b/src/html/svgs/right-arrow.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/html/svgs/right-pag.svg b/src/html/svgs/right-pag.svg new file mode 100644 index 0000000000..c2dbbe17c3 --- /dev/null +++ b/src/html/svgs/right-pag.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/html/svgs/sort.svg b/src/html/svgs/sort.svg new file mode 100644 index 0000000000..5d0ec018d5 --- /dev/null +++ b/src/html/svgs/sort.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/krate/metadata.rs b/src/krate/metadata.rs index b9692eeb90..06ce0098fc 100644 --- a/src/krate/metadata.rs +++ b/src/krate/metadata.rs @@ -22,11 +22,14 @@ use super::{Crate, CrateDownload, EncodableCrate, ALL_COLUMNS}; /// Handles the `GET /summary` route. pub fn summary(req: &mut Request) -> CargoResult { + Ok(req.json(&summary_json(&*req.db_conn()?)?)) +} + +pub fn summary_json(conn: &PgConnection) -> CargoResult { use diesel::dsl::*; use diesel::types::{BigInt, Nullable}; use schema::crates::dsl::*; - let conn = req.db_conn()?; let num_crates = crates.count().get_result(&*conn)?; let num_downloads = metadata::table .select(metadata::total_downloads) @@ -90,18 +93,7 @@ pub fn summary(req: &mut Request) -> CargoResult { .map(Category::encodable) .collect(); - #[derive(Serialize)] - struct R { - num_downloads: i64, - num_crates: i64, - new_crates: Vec, - most_downloaded: Vec, - most_recently_downloaded: Vec, - just_updated: Vec, - popular_keywords: Vec, - popular_categories: Vec, - } - Ok(req.json(&R { + Ok(SummaryResponse { num_downloads: num_downloads, num_crates: num_crates, new_crates: encode_crates(new_crates)?, @@ -110,7 +102,19 @@ pub fn summary(req: &mut Request) -> CargoResult { just_updated: encode_crates(just_updated)?, popular_keywords: popular_keywords, popular_categories: popular_categories, - })) + }) +} + +#[derive(Serialize, Debug)] +pub struct SummaryResponse { + num_downloads: i64, + num_crates: i64, + new_crates: Vec, + most_downloaded: Vec, + most_recently_downloaded: Vec, + just_updated: Vec, + popular_keywords: Vec, + popular_categories: Vec, } /// Handles the `GET /crates/:crate_id` route. diff --git a/src/lib.rs b/src/lib.rs index 80c4e7a51e..623e573c1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,6 +86,7 @@ pub mod download; pub mod git; pub mod github; pub mod http; +pub mod html; pub mod keyword; pub mod krate; pub mod owner; @@ -224,6 +225,9 @@ pub fn middleware(app: Arc) -> MiddlewareBuilder { let mut router = RouteBuilder::new(); + router.get("/", C(html::index)); + router.get("/categories", C(html::categories::index)); + // Mount the router under the /api/v1 path so we're at least somewhat at the // liberty to change things in the future! router.get("/api/v1/*path", R(Arc::clone(&api_router))); @@ -278,7 +282,7 @@ pub fn middleware(app: Arc) -> MiddlewareBuilder { // Serve the static files in the *dist* directory, which are the frontend assets. // Not needed for the backend tests. if env != Env::Test { - m.around(dist::Middleware::default()); + m.add(dist::Static); } return m; diff --git a/src/util/mod.rs b/src/util/mod.rs index 6086088fe9..98008b19b0 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -29,6 +29,7 @@ pub trait RequestUtils { fn redirect(&self, url: String) -> Response; fn json(&self, t: &T) -> Response; + fn html(&self, &str) -> Response; fn query(&self) -> HashMap; fn wants_json(&self) -> bool; fn pagination(&self, default: usize, max: usize) -> CargoResult<(i64, i64)>; @@ -49,6 +50,16 @@ pub fn json_response(t: &T) -> Response { } } +pub fn html_response(html: &str) -> Response { + let mut headers = HashMap::new(); + headers.insert("Content-Type".to_string(), vec!["text/html; charset=utf-8".to_string()]); + headers.insert("Content-Length".to_string(), vec![html.len().to_string()]); + Response { + status: (200, "OK"), + headers: headers, + body: Box::new(Cursor::new(html.to_string())), + } +} impl<'a> RequestUtils for Request + 'a { fn json(&self, t: &T) -> Response { @@ -98,6 +109,10 @@ impl<'a> RequestUtils for Request + 'a { } Ok((((page - 1) * limit) as i64, limit as i64)) } + + fn html(&self, html: &str) -> Response { + html_response(html) + } } // Can't Copy or Debug the fn.