diff --git a/app/styles/home.scss b/app/styles/home.scss
index 330287d2c9f..b94872b5c55 100644
--- a/app/styles/home.scss
+++ b/app/styles/home.scss
@@ -125,7 +125,7 @@
#home-crates {
@include flex-wrap(wrap);
- @include justify-content(center);
+ @include justify-content(left);
> div {
margin: 0;
diff --git a/app/templates/components/category-list.hbs b/app/templates/components/category-list.hbs
new file mode 100644
index 00000000000..f2dc5f7ca04
--- /dev/null
+++ b/app/templates/components/category-list.hbs
@@ -0,0 +1,10 @@
+
+ {{#each categories as |category|}}
+ -
+ {{#link-to 'category' category.slug class='name'}}
+ {{ category.category }} ({{ format-num category.crates_cnt }})
+
+ {{/link-to}}
+
+ {{/each}}
+
diff --git a/app/templates/components/keyword-list.hbs b/app/templates/components/keyword-list.hbs
new file mode 100644
index 00000000000..851295fe757
--- /dev/null
+++ b/app/templates/components/keyword-list.hbs
@@ -0,0 +1,10 @@
+
+ {{#each keywords as |keyword|}}
+ -
+ {{#link-to 'keyword' keyword class='name'}}
+ {{ keyword.id }} ({{ format-num keyword.crates_cnt }})
+
+ {{/link-to}}
+
+ {{/each}}
+
diff --git a/app/templates/index.hbs b/app/templates/index.hbs
index faf8164e255..e88ac9d6529 100644
--- a/app/templates/index.hbs
+++ b/app/templates/index.hbs
@@ -48,4 +48,12 @@
Just Updated
{{crate-list crates=model.just_updated}}
+
+
Popular Keywords {{#link-to 'keywords'}}(see all){{/link-to}}
+ {{keyword-list keywords=model.popular_keywords}}
+
+
+
Popular Categories {{#link-to 'categories'}}(see all){{/link-to}}
+ {{category-list categories=model.popular_categories}}
+
diff --git a/src/category.rs b/src/category.rs
index 97cbde9ed48..49920dfeb3c 100644
--- a/src/category.rs
+++ b/src/category.rs
@@ -144,6 +144,40 @@ impl Category {
Ok(rows.iter().next().unwrap().get("count"))
}
+ pub fn toplevel(conn: &GenericConnection,
+ sort: &str,
+ limit: i64,
+ offset: i64) -> CargoResult> {
+
+ let sort_sql = match sort {
+ "crates" => "ORDER BY crates_cnt DESC",
+ _ => "ORDER BY category ASC",
+ };
+
+ // Collect all the top-level categories and sum up the crates_cnt of
+ // the crates in all subcategories
+ let stmt = try!(conn.prepare(&format!(
+ "SELECT c.id, c.category, c.slug, c.description, c.created_at, \
+ COALESCE (( \
+ SELECT sum(c2.crates_cnt)::int \
+ FROM categories as c2 \
+ WHERE c2.slug = c.slug \
+ OR c2.slug LIKE c.slug || '::%' \
+ ), 0) as crates_cnt \
+ FROM categories as c \
+ WHERE c.category NOT LIKE '%::%' {} \
+ LIMIT $1 OFFSET $2",
+ sort_sql
+ )));
+
+ let categories: Vec<_> = try!(stmt.query(&[&limit, &offset]))
+ .iter()
+ .map(|row| Model::from_row(&row))
+ .collect();
+
+ Ok(categories)
+ }
+
pub fn subcategories(&self, conn: &GenericConnection)
-> CargoResult> {
let stmt = try!(conn.prepare("\
@@ -183,34 +217,9 @@ pub fn index(req: &mut Request) -> CargoResult {
let (offset, limit) = try!(req.pagination(10, 100));
let query = req.query();
let sort = query.get("sort").map_or("alpha", String::as_str);
- let sort_sql = match sort {
- "crates" => "ORDER BY crates_cnt DESC",
- _ => "ORDER BY category ASC",
- };
- // Collect all the top-level categories and sum up the crates_cnt of
- // the crates in all subcategories
- let stmt = try!(conn.prepare(&format!(
- "SELECT c.id, c.category, c.slug, c.description, c.created_at, \
- COALESCE (( \
- SELECT sum(c2.crates_cnt)::int \
- FROM categories as c2 \
- WHERE c2.slug = c.slug \
- OR c2.slug LIKE c.slug || '::%' \
- ), 0) as crates_cnt \
- FROM categories as c \
- WHERE c.category NOT LIKE '%::%' {} \
- LIMIT $1 OFFSET $2",
- sort_sql
- )));
-
- let categories: Vec<_> = try!(stmt.query(&[&limit, &offset]))
- .iter()
- .map(|row| {
- let category: Category = Model::from_row(&row);
- category.encodable()
- })
- .collect();
+ let categories = try!(Category::toplevel(conn, sort, limit, offset));
+ let categories = categories.into_iter().map(Category::encodable).collect();
// Query for the total count of categories
let total = try!(Category::count_toplevel(conn));
diff --git a/src/keyword.rs b/src/keyword.rs
index dfdd6bf176c..f82615c6fb2 100644
--- a/src/keyword.rs
+++ b/src/keyword.rs
@@ -54,6 +54,26 @@ impl Keyword {
}))))
}
+ pub fn all(conn: &GenericConnection, sort: &str, limit: i64, offset: i64)
+ -> CargoResult> {
+
+ let sort_sql = match sort {
+ "crates" => "ORDER BY crates_cnt DESC",
+ _ => "ORDER BY keyword ASC",
+ };
+
+ let stmt = try!(conn.prepare(&format!("SELECT * FROM keywords {}
+ LIMIT $1 OFFSET $2",
+ sort_sql)));
+
+ let keywords: Vec<_> = try!(stmt.query(&[&limit, &offset]))
+ .iter()
+ .map(|row| Model::from_row(&row))
+ .collect();
+
+ Ok(keywords)
+ }
+
pub fn valid_name(name: &str) -> bool {
if name.len() == 0 { return false }
name.chars().next().unwrap().is_alphanumeric() &&
@@ -131,20 +151,9 @@ pub fn index(req: &mut Request) -> CargoResult {
let (offset, limit) = try!(req.pagination(10, 100));
let query = req.query();
let sort = query.get("sort").map(|s| &s[..]).unwrap_or("alpha");
- let sort_sql = match sort {
- "crates" => "ORDER BY crates_cnt DESC",
- _ => "ORDER BY keyword ASC",
- };
-
- // Collect all the keywords
- let stmt = try!(conn.prepare(&format!("SELECT * FROM keywords {}
- LIMIT $1 OFFSET $2",
- sort_sql)));
- let mut keywords = Vec::new();
- for row in try!(stmt.query(&[&limit, &offset])).iter() {
- let keyword: Keyword = Model::from_row(&row);
- keywords.push(keyword.encodable());
- }
+
+ let keywords = try!(Keyword::all(conn, sort, limit, offset));
+ let keywords = keywords.into_iter().map(Keyword::encodable).collect();
// Query for the total count of keywords
let total = try!(Keyword::count(conn));
diff --git a/src/krate.rs b/src/krate.rs
index 1fc5a486a79..c627ff7823f 100644
--- a/src/krate.rs
+++ b/src/krate.rs
@@ -650,6 +650,16 @@ pub fn summary(req: &mut Request) -> CargoResult {
let most_downloaded = try!(tx.prepare("SELECT * FROM crates \
ORDER BY downloads DESC LIMIT 10"));
+ let popular_keywords = try!(Keyword::all(tx, "crates", 10, 0));
+ let popular_keywords = popular_keywords.into_iter()
+ .map(Keyword::encodable)
+ .collect();
+
+ let popular_categories = try!(Category::toplevel(tx, "crates", 10, 0));
+ let popular_categories = popular_categories.into_iter()
+ .map(Category::encodable)
+ .collect();
+
#[derive(RustcEncodable)]
struct R {
num_downloads: i64,
@@ -657,6 +667,8 @@ pub fn summary(req: &mut Request) -> CargoResult {
new_crates: Vec,
most_downloaded: Vec,
just_updated: Vec,
+ popular_keywords: Vec,
+ popular_categories: Vec,
}
Ok(req.json(&R {
num_downloads: num_downloads,
@@ -664,6 +676,8 @@ pub fn summary(req: &mut Request) -> CargoResult {
new_crates: try!(to_crates(new_crates)),
most_downloaded: try!(to_crates(most_downloaded)),
just_updated: try!(to_crates(just_updated)),
+ popular_keywords: popular_keywords,
+ popular_categories: popular_categories,
}))
}