diff --git a/src/krate/mod.rs b/src/krate/mod.rs index f14e64f98b2..d9793805517 100644 --- a/src/krate/mod.rs +++ b/src/krate/mod.rs @@ -755,6 +755,8 @@ pub fn index(req: &mut Request) -> CargoResult { /// Handles the `GET /summary` route. pub fn summary(req: &mut Request) -> CargoResult { + use diesel::expression::{date, now, sql, DayAndMonthIntervalDsl}; + use diesel::types::{BigInt, Nullable}; use schema::crates::dsl::*; let conn = req.db_conn()?; @@ -794,6 +796,20 @@ pub fn summary(req: &mut Request) -> CargoResult { .limit(10) .load(&*conn)?; + let recent_downloads = sql::>("SUM(crate_downloads.downloads)"); + let most_recently_downloaded = crates + .left_join( + crate_downloads::table.on( + id.eq(crate_downloads::crate_id) + .and(crate_downloads::date.gt(date(now - 90.days()))), + ), + ) + .group_by(id) + .order(recent_downloads.desc().nulls_last()) + .limit(10) + .select(ALL_COLUMNS) + .load::(&*conn)?; + let popular_keywords = keywords::table .order(keywords::crates_cnt.desc()) .limit(10) @@ -813,6 +829,7 @@ pub fn summary(req: &mut Request) -> CargoResult { num_crates: i64, new_crates: Vec, most_downloaded: Vec, + most_recently_downloaded: Vec, just_updated: Vec, popular_keywords: Vec, popular_categories: Vec, @@ -822,6 +839,7 @@ pub fn summary(req: &mut Request) -> CargoResult { num_crates: num_crates, new_crates: encode_crates(new_crates)?, most_downloaded: encode_crates(most_downloaded)?, + most_recently_downloaded: encode_crates(most_recently_downloaded)?, just_updated: encode_crates(just_updated)?, popular_keywords: popular_keywords, popular_categories: popular_categories, diff --git a/src/tests/krate.rs b/src/tests/krate.rs index 0cdab179180..6b20656c2e3 100644 --- a/src/tests/krate.rs +++ b/src/tests/krate.rs @@ -4,12 +4,13 @@ use std::collections::HashMap; use std::io::prelude::*; use std::fs::{self, File}; +use chrono::Utc; use conduit::{Handler, Method}; - -use git2; +use diesel::update; use self::diesel::prelude::*; -use serde_json; +use git2; use semver; +use serde_json; use cargo_registry::dependency::EncodableDependency; use cargo_registry::download::EncodableVersionDownload; @@ -18,11 +19,11 @@ use cargo_registry::keyword::EncodableKeyword; use cargo_registry::krate::{Crate, EncodableCrate, MAX_NAME_LENGTH}; use cargo_registry::token::ApiToken; -use cargo_registry::schema::{crates, versions}; +use cargo_registry::schema::{crates, metadata, versions}; use cargo_registry::upload as u; use cargo_registry::version::EncodableVersion; -use cargo_registry::category::Category; +use cargo_registry::category::{Category, EncodableCategory}; use {CrateList, CrateMeta, GoodCrate}; @@ -51,6 +52,18 @@ struct Downloads { version_downloads: Vec, } +#[derive(Deserialize)] +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, +} + fn new_crate(name: &str) -> u::NewCrate { u::NewCrate { name: u::CrateName(name.to_string()), @@ -982,6 +995,80 @@ fn summary_doesnt_die() { ok_resp!(middle.call(&mut req)); } +#[test] +fn summary_new_crates() { + let (_b, app, middle) = ::app(); + let u; + let krate; + let krate2; + let krate3; + { + let conn = app.diesel_database.get().unwrap(); + u = ::new_user("foo").create_or_update(&conn).unwrap(); + + krate = ::CrateBuilder::new("some_downloads", u.id) + .version(::VersionBuilder::new("0.1.0")) + .description("description") + .keyword("popular") + .downloads(20) + .recent_downloads(10) + .expect_build(&conn); + + krate2 = ::CrateBuilder::new("most_recent_downloads", u.id) + .version(::VersionBuilder::new("0.2.0")) + .keyword("popular") + .downloads(5000) + .recent_downloads(50) + .expect_build(&conn); + + krate3 = ::CrateBuilder::new("just_updated", u.id) + .version(::VersionBuilder::new("0.1.0")) + .expect_build(&conn); + + ::CrateBuilder::new("with_downloads", u.id) + .version(::VersionBuilder::new("0.3.0")) + .keyword("popular") + .downloads(1000) + .expect_build(&conn); + + ::new_category("Category 1", "cat1") + .create_or_update(&conn) + .unwrap(); + Category::update_crate(&conn, &krate, &["cat1"]).unwrap(); + Category::update_crate(&conn, &krate2, &["cat1"]).unwrap(); + + // set total_downloads global value for `num_downloads` prop + update(metadata::table) + .set(metadata::total_downloads.eq(6000)) + .execute(&*conn) + .unwrap(); + + // update 'just_updated' krate. Others won't appear because updated_at == created_at. + let updated = Utc::now().naive_utc(); + update(&krate3) + .set(crates::updated_at.eq(updated)) + .execute(&*conn) + .unwrap(); + } + + let mut req = ::req(app.clone(), Method::Get, "/api/v1/summary"); + let mut response = ok_resp!(middle.call(&mut req)); + let json: SummaryResponse = ::json(&mut response); + + assert_eq!(json.num_crates, 4); + assert_eq!(json.num_downloads, 6000); + assert_eq!(json.most_downloaded[0].name, "most_recent_downloads"); + assert_eq!( + json.most_recently_downloaded[0].name, + "most_recent_downloads" + ); + assert_eq!(json.popular_keywords[0].keyword, "popular"); + assert_eq!(json.popular_categories[0].category, "Category 1"); + assert_eq!(json.just_updated.len(), 1); + assert_eq!(json.just_updated[0].name, "just_updated"); + assert_eq!(json.new_crates.len(), 4); +} + #[test] fn download() { use chrono::{Duration, Utc};