From 7e6629fb95121306c43963816c955310dc896934 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Sun, 21 Jun 2020 16:37:23 -0500 Subject: [PATCH 01/13] Release Feed --- src/web/releases.rs | 48 ++++++++++++++++++++++---------- src/web/sitemap.rs | 10 +++---- tera-templates/releases/feed.xml | 39 ++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 19 deletions(-) create mode 100644 tera-templates/releases/feed.xml diff --git a/src/web/releases.rs b/src/web/releases.rs index e25099f09..57650eab0 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -1,16 +1,29 @@ //! Releases web handlers -use super::error::Nope; -use super::page::Page; -use super::{duration_to_str, match_version, redirect_base}; -use crate::db::Pool; -use crate::BuildQueue; +use crate::{ + db::Pool, + impl_webpage, + web::{ + duration_to_str, + error::Nope, + match_version, + page::{Page, WebPage}, + redirect_base, + }, + BuildQueue, +}; use chrono::{DateTime, NaiveDateTime, Utc}; -use iron::prelude::*; -use iron::status; +use iron::{ + headers::ContentType, + mime::{Mime, SubLevel, TopLevel}, + status, IronError, IronResult, Plugin, Request, Response, +}; use postgres::Connection; use router::Router; -use serde::ser::{Serialize, SerializeStruct, Serializer}; +use serde::{ + ser::{SerializeStruct, Serializer}, + Serialize, +}; use serde_json::Value; /// Number of release in home page @@ -352,14 +365,21 @@ pub fn home_page(req: &mut Request) -> IronResult { .to_resp("releases") } +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +struct ReleaseFeed { + recent_releases: Vec, +} + +impl_webpage! { + ReleaseFeed = "releases/feed.xml", + content_type = ContentType(Mime(TopLevel::Application, SubLevel::Xml, vec![])), +} + pub fn releases_feed_handler(req: &mut Request) -> IronResult { let conn = extension!(req, Pool).get()?; - let packages = get_releases(&conn, 1, RELEASES_IN_FEED, Order::ReleaseTime); - let mut resp = ctry!(Page::new(packages).to_resp("releases_feed")); - resp.headers.set(::iron::headers::ContentType( - "application/atom+xml".parse().unwrap(), - )); - Ok(resp) + let recent_releases = get_releases(&conn, 1, RELEASES_IN_FEED, Order::ReleaseTime); + + ReleaseFeed { recent_releases }.into_response(req) } fn releases_handler( diff --git a/src/web/sitemap.rs b/src/web/sitemap.rs index 73068c318..79d31c749 100644 --- a/src/web/sitemap.rs +++ b/src/web/sitemap.rs @@ -11,9 +11,9 @@ use serde_json::Value; /// The sitemap #[derive(Debug, Clone, PartialEq, Eq, Serialize)] -pub(crate) struct SitemapXml { +struct SitemapXml { /// The release's names and RFC 3339 timestamp to be displayed on the sitemap - pub releases: Vec<(String, String)>, + releases: Vec<(String, String)>, } impl_webpage! { @@ -57,11 +57,11 @@ pub fn robots_txt_handler(_: &mut Request) -> IronResult { } #[derive(Debug, Clone, PartialEq, Eq, Serialize)] -pub(crate) struct About { +struct About { /// The current version of rustc that docs.rs is using to build crates - pub rustc_version: Option, + rustc_version: Option, /// The default crate build limits - pub limits: Limits, + limits: Limits, } impl_webpage!(About = "core/about.html"); diff --git a/tera-templates/releases/feed.xml b/tera-templates/releases/feed.xml new file mode 100644 index 000000000..3730857ef --- /dev/null +++ b/tera-templates/releases/feed.xml @@ -0,0 +1,39 @@ + + + Docs.rs + Recent Rust crates + + + + + + + urn:docs-rs:{{ docsrs_version() }} + {{ recent_releases[0].release_time | default(value=now()) | date(format="%+") }} + + {%- for release in recent_releases -%} + {%- set name = release.name | escape_xml -%} + {%- set version = release.version | escape_xml -%} + {%- if rustdoc_status -%} + {%- set link = "/" ~ release.name ~ "/" ~ release.version ~ "/" ~ release.target_name -%} + {%- else -%} + {%- set link = "/crate/" ~ release.name ~ "/" ~ version -%} + {%- endif -%} + + + {{ name }}-{{ version }} + + + urn:docs-rs:{{ name }}:{{ version }} + {{ release.release_time | date(format="%+") }} + + + {{ release.description | default(value="-") | escape_xml }} + + + + docs.rs + + + {%- endfor -%} + \ No newline at end of file From 049b24b63bbafb8b416943f7dfaacb292e1671d6 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Mon, 22 Jun 2020 10:10:08 -0500 Subject: [PATCH 02/13] Home --- src/web/crate_details.rs | 5 +- src/web/mod.rs | 4 +- src/web/page/templates.rs | 2 +- src/web/releases.rs | 47 ++++---------- tera-templates/core/home.html | 108 +++++++++++++++++++++++++++++++ tera-templates/releases/feed.xml | 4 +- 6 files changed, 129 insertions(+), 41 deletions(-) create mode 100644 tera-templates/core/home.html diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs index c2baa84bf..60fe318b4 100644 --- a/src/web/crate_details.rs +++ b/src/web/crate_details.rs @@ -17,7 +17,7 @@ use serde_json::Value; // TODO: Add target name and versions -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq)] pub struct CrateDetails { name: String, version: String, @@ -42,6 +42,7 @@ pub struct CrateDetails { github_stars: Option, github_forks: Option, github_issues: Option, + // TODO: Is this even needed for rendering pages? pub(crate) metadata: MetaData, is_library: bool, yanked: bool, @@ -106,7 +107,7 @@ impl Serialize for CrateDetails { } } -#[derive(Debug, Eq, PartialEq, Serialize)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] pub struct Release { pub version: String, pub build_status: bool, diff --git a/src/web/mod.rs b/src/web/mod.rs index d820a05d5..cc12c5365 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -76,9 +76,7 @@ use postgres::Connection; use router::NoRoute; use semver::{Version, VersionReq}; use staticfile::Static; -use std::net::SocketAddr; -use std::sync::Arc; -use std::{env, fmt, path::PathBuf, time::Duration}; +use std::{env, fmt, net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; /// Duration of static files for staticfile and DatabaseFileHandler (in seconds) const STATIC_FILE_CACHE_DURATION: u64 = 60 * 60 * 24 * 30 * 12; // 12 months diff --git a/src/web/page/templates.rs b/src/web/page/templates.rs index e7744e1a9..4f0c5f458 100644 --- a/src/web/page/templates.rs +++ b/src/web/page/templates.rs @@ -185,7 +185,7 @@ impl tera::Function for ReturnValue { // TODO: This can be replaced by chrono fn timeformat(value: &Value, args: &HashMap) -> TeraResult { let fmt = if let Some(Value::Bool(true)) = args.get("relative") { - let value = DateTime::parse_from_str(value.as_str().unwrap(), "%Y-%m-%dT%H:%M:%S%z") + let value = DateTime::parse_from_rfc3339(value.as_str().unwrap()) .unwrap() .with_timezone(&Utc); diff --git a/src/web/releases.rs b/src/web/releases.rs index 57650eab0..d93b2aaa1 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -4,7 +4,6 @@ use crate::{ db::Pool, impl_webpage, web::{ - duration_to_str, error::Nope, match_version, page::{Page, WebPage}, @@ -20,10 +19,7 @@ use iron::{ }; use postgres::Connection; use router::Router; -use serde::{ - ser::{SerializeStruct, Serializer}, - Serialize, -}; +use serde::Serialize; use serde_json::Value; /// Number of release in home page @@ -33,7 +29,7 @@ const RELEASES_IN_RELEASES: i64 = 30; /// Releases in recent releases feed const RELEASES_IN_FEED: i64 = 150; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct Release { pub(crate) name: String, pub(crate) version: String, @@ -58,28 +54,6 @@ impl Default for Release { } } -impl Serialize for Release { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_struct("Release", 8)?; - state.serialize_field("name", &self.name)?; - state.serialize_field("version", &self.version)?; - state.serialize_field("description", &self.description)?; - state.serialize_field("target_name", &self.target_name)?; - state.serialize_field("rustdoc_status", &self.rustdoc_status)?; - state.serialize_field("release_time", &duration_to_str(self.release_time))?; - state.serialize_field( - "release_time_rfc3339", - &self.release_time.format("%+").to_string(), - )?; - state.serialize_field("stars", &self.stars)?; - - state.end() - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub(crate) enum Order { ReleaseTime, // this is default order @@ -356,13 +330,20 @@ fn get_search_results( (total_results, packages) } +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +struct HomePage { + recent_releases: Vec, +} + +impl_webpage! { + HomePage = "core/home.html", +} + pub fn home_page(req: &mut Request) -> IronResult { let conn = extension!(req, Pool).get()?; - let packages = get_releases(&conn, 1, RELEASES_IN_HOME, Order::ReleaseTime); - Page::new(packages) - .set_true("show_search_form") - .set_true("hide_package_navigation") - .to_resp("releases") + let recent_releases = get_releases(&conn, 1, RELEASES_IN_HOME, Order::ReleaseTime); + + HomePage { recent_releases }.into_response(req) } #[derive(Debug, Clone, PartialEq, Eq, Serialize)] diff --git a/tera-templates/core/home.html b/tera-templates/core/home.html new file mode 100644 index 000000000..059bd1372 --- /dev/null +++ b/tera-templates/core/home.html @@ -0,0 +1,108 @@ +{%- extends "base.html" -%} + +{%- block title -%}Docs.rs{%- endblock title -%} + +{%- block body -%} +
+

Docs.rs

+ +
+
+ +
+ +
+ + +
+
+
+ +
+
+ + + +
+
+{%- endblock body -%} + +{%- block javascript -%} + +{%- endblock javascript -%} diff --git a/tera-templates/releases/feed.xml b/tera-templates/releases/feed.xml index 3730857ef..6715f1c60 100644 --- a/tera-templates/releases/feed.xml +++ b/tera-templates/releases/feed.xml @@ -18,7 +18,7 @@ {%- set link = "/" ~ release.name ~ "/" ~ release.version ~ "/" ~ release.target_name -%} {%- else -%} {%- set link = "/crate/" ~ release.name ~ "/" ~ version -%} - {%- endif -%} + {%- endif %} {{ name }}-{{ version }} @@ -35,5 +35,5 @@ docs.rs - {%- endfor -%} + {%- endfor %} \ No newline at end of file From 2c977e3299bac45cee1e805b24e9bfc6ee77d576 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Mon, 22 Jun 2020 10:43:59 -0500 Subject: [PATCH 03/13] Recent, Stars, Recent Failures and Failures by Stars --- src/web/releases.rs | 166 ++++++++++++-------------- tera-templates/releases/header.html | 77 ++++++++++++ tera-templates/releases/releases.html | 101 ++++++++++++++++ 3 files changed, 255 insertions(+), 89 deletions(-) create mode 100644 tera-templates/releases/header.html create mode 100644 tera-templates/releases/releases.html diff --git a/src/web/releases.rs b/src/web/releases.rs index d93b2aaa1..1bda4d50d 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -363,114 +363,102 @@ pub fn releases_feed_handler(req: &mut Request) -> IronResult { ReleaseFeed { recent_releases }.into_response(req) } -fn releases_handler( - packages: Vec, +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +struct ViewReleases { + releases: Vec, + description: String, + release_type: ReleaseType, + show_next_page: bool, + show_previous_page: bool, page_number: i64, - release_type: &str, - tab: &str, - title: &str, -) -> IronResult { - if packages.is_empty() { - return Err(IronError::new(Nope::CrateNotFound, status::NotFound)); - } + author: Option, +} + +impl_webpage! { + ViewReleases = "releases/releases.html", +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)] +#[serde(rename_all = "kebab-case")] +enum ReleaseType { + Recent, + Stars, + RecentFailures, + Failures, + Author, + Search, +} + +fn releases_handler(req: &mut Request, release_type: ReleaseType) -> IronResult { + let page_number: i64 = extension!(req, Router) + .find("page") + .and_then(|page_num| page_num.parse().ok()) + .unwrap_or(1); + + let (description, number_releases, release_order) = match release_type { + ReleaseType::Recent => ( + "Recently uploaded crates", + RELEASES_IN_RELEASES, + Order::ReleaseTime, + ), + ReleaseType::Stars => ( + "Crates with most stars", + RELEASES_IN_RELEASES, + Order::GithubStars, + ), + ReleaseType::RecentFailures => ( + "Recent crates failed to build", + RELEASES_IN_RELEASES, + Order::RecentFailures, + ), + ReleaseType::Failures => ( + "Crates with most stars failed to build", + RELEASES_IN_RELEASES, + Order::FailuresByGithubStars, + ), + + ReleaseType::Author | ReleaseType::Search => unreachable!( + "The authors and search page have special requirements and cannot use this handler", + ), + }; + + let releases = { + let conn = extension!(req, Pool).get()?; + get_releases(&conn, page_number, number_releases, release_order) + }; // Show next and previous page buttons - // This is a temporary solution to avoid expensive COUNT(*) let (show_next_page, show_previous_page) = ( - packages.len() == RELEASES_IN_RELEASES as usize, + releases.len() == RELEASES_IN_RELEASES as usize, page_number != 1, ); - Page::new(packages) - .title("Releases") - .set("description", title) - .set("release_type", release_type) - .set_true("show_releases_navigation") - .set_true(tab) - .set_bool("show_next_page_button", show_next_page) - .set_int("next_page", page_number + 1) - .set_bool("show_previous_page_button", show_previous_page) - .set_int("previous_page", page_number - 1) - .to_resp("releases") + ViewReleases { + releases, + description: description.into(), + release_type, + show_next_page, + show_previous_page, + page_number, + author: None, + } + .into_response(req) } -// Following functions caused a code repeat due to design of our /releases/ URL routes pub fn recent_releases_handler(req: &mut Request) -> IronResult { - let page_number: i64 = extension!(req, Router) - .find("page") - .unwrap_or("1") - .parse() - .unwrap_or(1); - let conn = extension!(req, Pool).get()?; - let packages = get_releases(&conn, page_number, RELEASES_IN_RELEASES, Order::ReleaseTime); - releases_handler( - packages, - page_number, - "recent", - "releases_navigation_recent_tab", - "Recently uploaded crates", - ) + releases_handler(req, ReleaseType::Recent) } pub fn releases_by_stars_handler(req: &mut Request) -> IronResult { - let page_number: i64 = extension!(req, Router) - .find("page") - .unwrap_or("1") - .parse() - .unwrap_or(1); - let conn = extension!(req, Pool).get()?; - let packages = get_releases(&conn, page_number, RELEASES_IN_RELEASES, Order::GithubStars); - releases_handler( - packages, - page_number, - "stars", - "releases_navigation_stars_tab", - "Crates with most stars", - ) + releases_handler(req, ReleaseType::Stars) } pub fn releases_recent_failures_handler(req: &mut Request) -> IronResult { - let page_number: i64 = extension!(req, Router) - .find("page") - .unwrap_or("1") - .parse() - .unwrap_or(1); - let conn = extension!(req, Pool).get()?; - let packages = get_releases( - &conn, - page_number, - RELEASES_IN_RELEASES, - Order::RecentFailures, - ); - releases_handler( - packages, - page_number, - "recent-failures", - "releases_navigation_recent_failures_tab", - "Recent crates failed to build", - ) + releases_handler(req, ReleaseType::RecentFailures) } pub fn releases_failures_by_stars_handler(req: &mut Request) -> IronResult { - let page_number: i64 = extension!(req, Router) - .find("page") - .unwrap_or("1") - .parse() - .unwrap_or(1); - let conn = extension!(req, Pool).get()?; - let packages = get_releases( - &conn, - page_number, - RELEASES_IN_RELEASES, - Order::FailuresByGithubStars, - ); - releases_handler( - packages, - page_number, - "failures", - "releases_navigation_failures_by_stars_tab", - "Crates with most stars failed to build", - ) + releases_handler(req, ReleaseType::Failures) } pub fn author_handler(req: &mut Request) -> IronResult { diff --git a/tera-templates/releases/header.html b/tera-templates/releases/header.html new file mode 100644 index 000000000..4d6b0df60 --- /dev/null +++ b/tera-templates/releases/header.html @@ -0,0 +1,77 @@ +{# + Builds the header for the release dashboard + * `title` A string + * `description` A string + * `tab` A string with one of the following values + * `recent` + * `stars` + * `recent-failures` + * `failures` + * `activity` + * `queue` + * `author` + * `author` A string, used for the authors page +#} +{% macro header(title, description, tab, author=false) %} +
+
+

{{ title }}

+
{{ description | default(value="") }}
+ + {# This does double-duty as the search, so hide all tabs when we're searching something #} + {%- if tab != "search" -%} +
+ +
+ {%- endif -%} +
+
+{% endmacro header %} diff --git a/tera-templates/releases/releases.html b/tera-templates/releases/releases.html new file mode 100644 index 000000000..a96814952 --- /dev/null +++ b/tera-templates/releases/releases.html @@ -0,0 +1,101 @@ +{%- extends "base.html" -%} +{%- import "releases/header.html" as release_macros -%} + +{%- block title -%}Releases - Docs.rs{%- endblock title -%} + +{%- block header -%} + {# These all have defaults so searches work #} + {{ release_macros::header(title="Releases", description=description | default(value=""), tab=release_type, author=author | default(value=false)) }} +{%- endblock header -%} + +{%- block body -%} +
+
+ + + +
+
+{%- endblock body -%} + +{%- block javascript -%} + +{%- endblock javascript -%} From 363b93e365c3961711c8486558f2fa8306e758c4 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Mon, 22 Jun 2020 10:55:01 -0500 Subject: [PATCH 04/13] Authors --- src/web/page/handlebars.rs | 6 --- src/web/releases.rs | 89 +++++++++++++++++--------------------- 2 files changed, 40 insertions(+), 55 deletions(-) diff --git a/src/web/page/handlebars.rs b/src/web/page/handlebars.rs index 8755fd089..91ee98994 100644 --- a/src/web/page/handlebars.rs +++ b/src/web/page/handlebars.rs @@ -84,12 +84,6 @@ impl Page { self } - /// Sets an integer variable - pub fn set_int(mut self, var: &str, val: i64) -> Page { - self.varsi.insert(var.to_owned(), val); - self - } - /// Sets title of page pub fn title(mut self, title: &str) -> Page { self.title = Some(title.to_owned()); diff --git a/src/web/releases.rs b/src/web/releases.rs index 1bda4d50d..c00798632 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -395,25 +395,12 @@ fn releases_handler(req: &mut Request, release_type: ReleaseType) -> IronResult< .and_then(|page_num| page_num.parse().ok()) .unwrap_or(1); - let (description, number_releases, release_order) = match release_type { - ReleaseType::Recent => ( - "Recently uploaded crates", - RELEASES_IN_RELEASES, - Order::ReleaseTime, - ), - ReleaseType::Stars => ( - "Crates with most stars", - RELEASES_IN_RELEASES, - Order::GithubStars, - ), - ReleaseType::RecentFailures => ( - "Recent crates failed to build", - RELEASES_IN_RELEASES, - Order::RecentFailures, - ), + let (description, release_order) = match release_type { + ReleaseType::Recent => ("Recently uploaded crates", Order::ReleaseTime), + ReleaseType::Stars => ("Crates with most stars", Order::GithubStars), + ReleaseType::RecentFailures => ("Recent crates failed to build", Order::RecentFailures), ReleaseType::Failures => ( "Crates with most stars failed to build", - RELEASES_IN_RELEASES, Order::FailuresByGithubStars, ), @@ -424,7 +411,7 @@ fn releases_handler(req: &mut Request, release_type: ReleaseType) -> IronResult< let releases = { let conn = extension!(req, Pool).get()?; - get_releases(&conn, page_number, number_releases, release_order) + get_releases(&conn, page_number, RELEASES_IN_RELEASES, release_order) }; // Show next and previous page buttons @@ -464,50 +451,54 @@ pub fn releases_failures_by_stars_handler(req: &mut Request) -> IronResult IronResult { let router = extension!(req, Router); // page number of releases - let page_number: i64 = router.find("page").unwrap_or("1").parse().unwrap_or(1); - - let conn = extension!(req, Pool).get()?; - - #[allow(clippy::or_fun_call)] + let page_number: i64 = router + .find("page") + .and_then(|page_num| page_num.parse().ok()) + .unwrap_or(1); let author = ctry!(router .find("author") - .ok_or(IronError::new(Nope::CrateNotFound, status::NotFound))); + // TODO: Accurate error here, the author wasn't provided + .ok_or_else(|| IronError::new(Nope::CrateNotFound, status::NotFound))); + + let (author_name, releases) = { + let conn = extension!(req, Pool).get()?; - let (author_name, packages) = if author.starts_with('@') { - let mut author = author.split('@'); + if author.starts_with('@') { + let mut author = author.split('@'); - get_releases_by_owner( - &conn, - page_number, - RELEASES_IN_RELEASES, - cexpect!(author.nth(1)), - ) - } else { - get_releases_by_author(&conn, page_number, RELEASES_IN_RELEASES, author) + get_releases_by_owner( + &conn, + page_number, + RELEASES_IN_RELEASES, + // TODO: Is this fallible? + cexpect!(author.nth(1)), + ) + } else { + get_releases_by_author(&conn, page_number, RELEASES_IN_RELEASES, author) + } }; - if packages.is_empty() { + if releases.is_empty() { + // TODO: Accurate error here, the author wasn't found return Err(IronError::new(Nope::CrateNotFound, status::NotFound)); } // Show next and previous page buttons - // This is a temporary solution to avoid expensive COUNT(*) let (show_next_page, show_previous_page) = ( - packages.len() == RELEASES_IN_RELEASES as usize, + releases.len() == RELEASES_IN_RELEASES as usize, page_number != 1, ); - Page::new(packages) - .title("Releases") - .set("description", &format!("Crates from {}", author_name)) - .set("author", &author_name) - .set("release_type", author) - .set_true("show_releases_navigation") - .set_true("show_stars") - .set_bool("show_next_page_button", show_next_page) - .set_int("next_page", page_number + 1) - .set_bool("show_previous_page_button", show_previous_page) - .set_int("previous_page", page_number - 1) - .to_resp("releases") + + ViewReleases { + releases, + description: format!("Crates from {}", author_name), + release_type: ReleaseType::Author, + show_next_page, + show_previous_page, + page_number, + author: Some(author_name), + } + .into_response(req) } pub fn search_handler(req: &mut Request) -> IronResult { From 8778d4f0c406fe100793fd087152ae1ed00de1ac Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Mon, 22 Jun 2020 16:13:37 -0500 Subject: [PATCH 05/13] Search --- src/web/page/web_page.rs | 4 +- src/web/releases.rs | 113 ++++++++++++++++++-------- tera-templates/releases/releases.html | 9 +- 3 files changed, 90 insertions(+), 36 deletions(-) diff --git a/src/web/page/web_page.rs b/src/web/page/web_page.rs index 07656b2d9..5c38b76e5 100644 --- a/src/web/page/web_page.rs +++ b/src/web/page/web_page.rs @@ -3,6 +3,7 @@ use iron::{headers::ContentType, response::Response, status::Status, IronResult, use serde::Serialize; use tera::Context; +/// When making using a custom status, use a closure that coerces to a `fn(&Self) -> Status` #[macro_export] macro_rules! impl_webpage { ($page:ty = $template:expr $(, status = $status:expr)? $(, content_type = $content_type:expr)? $(,)?) => { @@ -11,7 +12,8 @@ macro_rules! impl_webpage { $( fn get_status(&self) -> ::iron::status::Status { - $status + let status: fn(&Self) -> ::iron::status::Status = $status; + (status)(self) } )? diff --git a/src/web/releases.rs b/src/web/releases.rs index c00798632..9cf772ec6 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -13,9 +13,10 @@ use crate::{ }; use chrono::{DateTime, NaiveDateTime, Utc}; use iron::{ - headers::ContentType, + headers::{ContentType, Expires, HttpDate}, mime::{Mime, SubLevel, TopLevel}, - status, IronError, IronResult, Plugin, Request, Response, + modifiers::Redirect, + status, IronError, IronResult, Plugin, Request, Response, Url, }; use postgres::Connection; use router::Router; @@ -501,23 +502,58 @@ pub fn author_handler(req: &mut Request) -> IronResult { .into_response(req) } +#[derive(Debug, Clone, PartialEq, Serialize)] +struct Search { + title: String, + #[serde(rename = "releases")] + results: Vec, + search_query: Option, + previous_page_button: bool, + next_page_button: bool, + current_page: i64, + /// This should always be `ReleaseType::Search` + release_type: ReleaseType, + #[serde(skip)] + status: iron::status::Status, +} + +impl Default for Search { + fn default() -> Self { + Self { + title: String::default(), + results: Vec::default(), + search_query: None, + previous_page_button: false, + next_page_button: false, + current_page: 0, + release_type: ReleaseType::Search, + status: iron::status::Ok, + } + } +} + +impl_webpage! { + Search = "releases/releases.html", + status = |search| search.status, +} + pub fn search_handler(req: &mut Request) -> IronResult { use params::{Params, Value}; let params = ctry!(req.get::()); let query = params.find(&["query"]); - let conn = extension!(req, Pool).get()?; - if let Some(&Value::String(ref query)) = query { + + if let Some(Value::String(query)) = query { // check if I am feeling lucky button pressed and redirect user to crate page // if there is a match // TODO: Redirecting to latest doc might be more useful if params.find(&["i-am-feeling-lucky"]).is_some() { - use iron::modifiers::Redirect; - use iron::Url; - // redirect to a random crate if query is empty if query.is_empty() { + // FIXME: This is a fast query but using a constant + // There are currently 280 crates with docs and 100+ + // starts. This should be fine for a while. let rows = ctry!(conn.query( "SELECT crates.name, releases.version, @@ -529,13 +565,11 @@ pub fn search_handler(req: &mut Request) -> IronResult { OFFSET FLOOR(RANDOM() * 280) LIMIT 1", &[] )); - // ~~~~~~^ - // FIXME: This is a fast query but using a constant - // There are currently 280 crates with docs and 100+ - // starts. This should be fine for a while. - let name: String = rows.get(0).get(0); - let version: String = rows.get(0).get(1); - let target_name: String = rows.get(0).get(2); + let row = rows.into_iter().next().unwrap(); // TODO: Is this really infallible? + + let name: String = row.get("name"); + let version: String = row.get("version"); + let target_name: String = row.get("target_name"); let url = ctry!(Url::parse(&format!( "{}/{}/{}/{}", redirect_base(req), @@ -545,8 +579,8 @@ pub fn search_handler(req: &mut Request) -> IronResult { ))); let mut resp = Response::with((status::Found, Redirect(url))); - use iron::headers::{Expires, HttpDate}; resp.headers.set(Expires(HttpDate(time::now()))); + return Ok(resp); } @@ -556,35 +590,43 @@ pub fn search_handler(req: &mut Request) -> IronResult { if let Some(matchver) = match_version(&conn, &query, None) { let (version, id) = matchver.version.into_parts(); let query = matchver.corrected_name.unwrap_or_else(|| query.to_string()); + // FIXME: This is a super dirty way to check if crate have rustdocs generated. // match_version should handle this instead of this code block. // This block is introduced to fix #163 let rustdoc_status = { let rows = ctry!(conn.query( "SELECT rustdoc_status - FROM releases - WHERE releases.id = $1", + FROM releases + WHERE releases.id = $1", &[&id] )); - if rows.is_empty() { - false - } else { - rows.get(0).get(0) - } + + rows.into_iter() + .next() + .map(|r| r.get("rustdoc_status")) + .unwrap_or_default() }; + let url = if rustdoc_status { - ctry!(Url::parse( - &format!("{}/{}/{}", redirect_base(req), query, version)[..] - )) + ctry!(Url::parse(&format!( + "{}/{}/{}", + redirect_base(req), + query, + version, + ))) } else { - ctry!(Url::parse( - &format!("{}/crate/{}/{}", redirect_base(req), query, version)[..] - )) + ctry!(Url::parse(&format!( + "{}/crate/{}/{}", + redirect_base(req), + query, + version, + ))) }; - let mut resp = Response::with((status::Found, Redirect(url))); - use iron::headers::{Expires, HttpDate}; + let mut resp = Response::with((status::Found, Redirect(url))); resp.headers.set(Expires(HttpDate(time::now()))); + return Ok(resp); } } @@ -597,10 +639,13 @@ pub fn search_handler(req: &mut Request) -> IronResult { }; // FIXME: There is no pagination - Page::new(results) - .set("search_query", &query) - .title(&title) - .to_resp("releases") + Search { + title, + results, + search_query: Some(query.to_owned()), + ..Default::default() + } + .into_response(req) } else { Err(IronError::new(Nope::NoResults, status::NotFound)) } diff --git a/tera-templates/releases/releases.html b/tera-templates/releases/releases.html index a96814952..b267569ce 100644 --- a/tera-templates/releases/releases.html +++ b/tera-templates/releases/releases.html @@ -5,7 +5,14 @@ {%- block header -%} {# These all have defaults so searches work #} - {{ release_macros::header(title="Releases", description=description | default(value=""), tab=release_type, author=author | default(value=false)) }} + {{ + release_macros::header( + title=title | default(value="Releases"), + description=description | default(value=""), + tab=release_type, + author=author | default(value=false) + ) + }} {%- endblock header -%} {%- block body -%} From ee5000ec251b4cc5d949026e92f04235b98d9369 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Mon, 22 Jun 2020 16:26:11 -0500 Subject: [PATCH 06/13] Search errors --- src/web/error.rs | 46 ++++++++++++++++----------- src/web/releases.rs | 77 ++++++--------------------------------------- 2 files changed, 38 insertions(+), 85 deletions(-) diff --git a/src/web/error.rs b/src/web/error.rs index d49054ac3..2c9060c36 100644 --- a/src/web/error.rs +++ b/src/web/error.rs @@ -1,11 +1,14 @@ -use crate::db::PoolError; -use crate::web::page::Page; +use crate::{ + db::PoolError, + web::{ + page::{Page, WebPage}, + releases::Search, + }, +}; use failure::Fail; -use iron::prelude::*; -use iron::status; -use iron::Handler; -use std::error::Error; -use std::fmt; +use iron::{status, Handler, IronError, IronResult, Plugin, Request, Response}; +use params::{Params, Value}; +use std::{error::Error, fmt}; #[derive(Debug, Copy, Clone)] pub enum Nope { @@ -38,6 +41,7 @@ impl Handler for Nope { .title("The requested resource does not exist") .to_resp("error") } + Nope::CrateNotFound => { // user tried to navigate to a crate that doesn't exist Page::new("no such crate".to_owned()) @@ -45,24 +49,30 @@ impl Handler for Nope { .title("The requested crate does not exist") .to_resp("error") } + Nope::NoResults => { - use params::{Params, Value}; let params = req.get::().unwrap(); - if let Some(&Value::String(ref query)) = params.find(&["query"]) { + + if let Some(Value::String(ref query)) = params.find(&["query"]) { // this used to be a search - Page::new(Vec::::new()) - .set_status(status::NotFound) - .set("search_query", &query) - .title(&format!("No crates found matching '{}'", query)) - .to_resp("releases") + Search { + title: format!("No crates found matching '{}'", query), + search_query: Some(query.to_owned()), + status: status::NotFound, + ..Default::default() + } + .into_response(req) } else { // user did a search with no search terms - Page::new(Vec::::new()) - .set_status(status::NotFound) - .title("No results given for empty search query") - .to_resp("releases") + Search { + title: "No results given for empty search query".to_owned(), + status: status::NotFound, + ..Default::default() + } + .into_response(req) } } + Nope::InternalServerError => { // something went wrong, details should have been logged Page::new("internal server error".to_owned()) diff --git a/src/web/releases.rs b/src/web/releases.rs index 9cf772ec6..78f668203 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -381,7 +381,7 @@ impl_webpage! { #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)] #[serde(rename_all = "kebab-case")] -enum ReleaseType { +pub(super) enum ReleaseType { Recent, Stars, RecentFailures, @@ -503,18 +503,18 @@ pub fn author_handler(req: &mut Request) -> IronResult { } #[derive(Debug, Clone, PartialEq, Serialize)] -struct Search { - title: String, +pub(super) struct Search { + pub(super) title: String, #[serde(rename = "releases")] - results: Vec, - search_query: Option, - previous_page_button: bool, - next_page_button: bool, - current_page: i64, + pub(super) results: Vec, + pub(super) search_query: Option, + pub(super) previous_page_button: bool, + pub(super) next_page_button: bool, + pub(super) current_page: i64, /// This should always be `ReleaseType::Search` - release_type: ReleaseType, + pub(super) release_type: ReleaseType, #[serde(skip)] - status: iron::status::Status, + pub(super) status: iron::status::Status, } impl Default for Search { @@ -695,7 +695,6 @@ mod tests { use crate::test::{assert_success, wrapper}; use chrono::TimeZone; use kuchiki::traits::TendrilSink; - use serde_json::json; #[test] fn database_search() { @@ -1028,62 +1027,6 @@ mod tests { }) } - #[test] - fn serialize_releases() { - let now = Utc::now(); - - let mut release = Release { - name: "serde".to_string(), - version: "0.0.0".to_string(), - description: Some("serde makes things other things".to_string()), - target_name: Some("x86_64-pc-windows-msvc".to_string()), - rustdoc_status: true, - release_time: now, - stars: 100, - }; - - let correct_json = json!({ - "name": "serde", - "version": "0.0.0", - "description": "serde makes things other things", - "target_name": "x86_64-pc-windows-msvc", - "rustdoc_status": true, - "release_time": duration_to_str(now), - "release_time_rfc3339": now.format("%+").to_string(), - "stars": 100 - }); - - assert_eq!(correct_json, serde_json::to_value(&release).unwrap()); - - release.target_name = None; - let correct_json = json!({ - "name": "serde", - "version": "0.0.0", - "description": "serde makes things other things", - "target_name": null, - "rustdoc_status": true, - "release_time": duration_to_str(now), - "release_time_rfc3339": now.format("%+").to_string(), - "stars": 100 - }); - - assert_eq!(correct_json, serde_json::to_value(&release).unwrap()); - - release.description = None; - let correct_json = json!({ - "name": "serde", - "version": "0.0.0", - "description": null, - "target_name": null, - "rustdoc_status": true, - "release_time": duration_to_str(now), - "release_time_rfc3339": now.format("%+").to_string(), - "stars": 100 - }); - - assert_eq!(correct_json, serde_json::to_value(&release).unwrap()); - } - #[test] fn release_feed() { wrapper(|env| { From 785b70a8125b1494d02302eca933ca2fd874752e Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Mon, 22 Jun 2020 16:36:03 -0500 Subject: [PATCH 07/13] Removed unneeded tests --- src/web/page/handlebars.rs | 49 -------------------------------------- 1 file changed, 49 deletions(-) diff --git a/src/web/page/handlebars.rs b/src/web/page/handlebars.rs index 91ee98994..558e32fea 100644 --- a/src/web/page/handlebars.rs +++ b/src/web/page/handlebars.rs @@ -157,55 +157,6 @@ mod tests { use iron::Url; use serde_json::json; - #[test] - fn serialize_page() { - let time = Utc::now(); - - let mut release = Release::default(); - release.name = "lasso".into(); - release.version = "0.1.0".into(); - release.release_time = time.clone(); - - let mut varss = BTreeMap::new(); - varss.insert("test".into(), "works".into()); - let mut varsb = BTreeMap::new(); - varsb.insert("test2".into(), true); - let mut varsi = BTreeMap::new(); - varsi.insert("test3".into(), 1337); - - let page = Page { - title: None, - content: vec![release.clone()], - status: status::Status::Ok, - varss, - varsb, - varsi, - rustc_resource_suffix: &*RUSTC_RESOURCE_SUFFIX, - }; - - let correct_json = json!({ - "content": [{ - "name": "lasso", - "version": "0.1.0", - "description": null, - "target_name": null, - "rustdoc_status": false, - "release_time": super::super::super::duration_to_str(time), - "release_time_rfc3339": time.format("%+").to_string(), - "stars": 0 - }], - "varss": { "test": "works" }, - "varsb": { "test2": true }, - "varsi": { "test3": 1337 }, - "rustc_resource_suffix": &*RUSTC_RESOURCE_SUFFIX, - "cratesfyi_version": crate::BUILD_VERSION, - "cratesfyi_version_safe": build_version_safe(crate::BUILD_VERSION), - "has_global_alert": crate::GLOBAL_ALERT.is_some() - }); - - assert_eq!(correct_json, serde_json::to_value(&page).unwrap()); - } - #[test] fn load_page_from_releases() { crate::test::wrapper(|env| { From 4c762dc73f106a0bc60720adc67bfa7fc37ec6b6 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Sun, 28 Jun 2020 17:33:27 -0500 Subject: [PATCH 08/13] Apply suggestions from code review Co-authored-by: Joshua Nelson --- src/web/page/web_page.rs | 1 - tera-templates/releases/releases.html | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/web/page/web_page.rs b/src/web/page/web_page.rs index 5c38b76e5..1c9572a39 100644 --- a/src/web/page/web_page.rs +++ b/src/web/page/web_page.rs @@ -4,7 +4,6 @@ use serde::Serialize; use tera::Context; /// When making using a custom status, use a closure that coerces to a `fn(&Self) -> Status` -#[macro_export] macro_rules! impl_webpage { ($page:ty = $template:expr $(, status = $status:expr)? $(, content_type = $content_type:expr)? $(,)?) => { impl $crate::web::page::WebPage for $page { diff --git a/tera-templates/releases/releases.html b/tera-templates/releases/releases.html index b267569ce..12ed19309 100644 --- a/tera-templates/releases/releases.html +++ b/tera-templates/releases/releases.html @@ -24,7 +24,7 @@ {%- if rustdoc_status -%} {% set link = "/" ~ release.name ~ "/" ~ release.version ~ "/" ~ release.target_name -%} {%- else -%} - {% set link = "/" ~ release.name ~ "/" ~ release.version -%} + {% set link = "/crate/" ~ release.name ~ "/" ~ release.version -%} {%- endif -%}
  • From 338842a3361f0421bc380fd4abedfd00c74b4103 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Sun, 28 Jun 2020 18:22:22 -0500 Subject: [PATCH 09/13] Added tests for multiple pages, path_slash for Windows and Unix builds and a status to robots.txt --- Cargo.lock | 6 +- Cargo.toml | 7 +- src/storage/mod.rs | 21 ++--- src/test/fakes.rs | 5 ++ src/web/crate_details.rs | 1 - src/web/page/handlebars.rs | 4 +- src/web/page/templates.rs | 22 ++--- src/web/page/web_page.rs | 1 + src/web/releases.rs | 124 +++++++++++++++++++++++++- src/web/sitemap.rs | 44 +++++++++- templates/releases.hbs | 144 ------------------------------- templates/releases_feed.hbs | 21 ----- tera-templates/releases/feed.xml | 2 +- 13 files changed, 196 insertions(+), 206 deletions(-) delete mode 100644 templates/releases.hbs delete mode 100644 templates/releases_feed.hbs diff --git a/Cargo.lock b/Cargo.lock index 7488ee738..e10bb11d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -384,7 +384,7 @@ dependencies = [ "notify 4.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "params 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "path-slash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "path-slash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "postgres 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "procfs 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", "prometheus 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1938,7 +1938,7 @@ dependencies = [ [[package]] name = "path-slash" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -4255,7 +4255,7 @@ dependencies = [ "checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" "checksum parking_lot_core 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" "checksum parse-zoneinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "feece9d0113b400182a7d00adcff81ccf29158c49c5abd11e2eed8589bf6ff07" -"checksum path-slash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a0858af4d9136275541f4eac7be1af70add84cf356d901799b065ac1b8ff6e2f" +"checksum path-slash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ddf9566c1063a197427135fb518ed42f2be18630fc2401aa267ed2dc95e9c85d" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" "checksum persistent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e8fa0009c4f3d350281309909c618abddf10bb7e3145f28410782f6a5ec74c5" diff --git a/Cargo.toml b/Cargo.toml index d838e602f..4a247bdab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,11 @@ mime_guess = "2" dotenv = "0.15" zstd = "0.5" git2 = { version = "0.13.6", default-features = false } +<<<<<<< HEAD once_cell = "1.2.0" +======= +path-slash = "0.1.2" +>>>>>>> ddcd932... Added tests for multiple pages, path_slash for Windows and Unix builds and a status to robots.txt # Data serialization and deserialization serde = { version = "1.0", features = ["derive"] } @@ -77,9 +81,6 @@ features = ["with-chrono", "with-serde_json"] # Process information procfs = "0.7" -[target.'cfg(windows)'.dependencies] -path-slash = "0.1.1" - [dev-dependencies] criterion = "0.3" rand = "0.7.3" diff --git a/src/storage/mod.rs b/src/storage/mod.rs index cb42a0450..ebc4a9fb3 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -5,13 +5,15 @@ pub(crate) use self::database::DatabaseBackend; pub(crate) use self::s3::S3Backend; use chrono::{DateTime, Utc}; use failure::{err_msg, Error}; +use path_slash::PathExt; use postgres::{transaction::Transaction, Connection}; -use std::collections::{HashMap, HashSet}; -use std::ffi::OsStr; -use std::fmt; -use std::fs; -use std::io::Read; -use std::path::{Path, PathBuf}; +use std::{ + collections::{HashMap, HashSet}, + ffi::OsStr, + fmt, fs, + io::Read, + path::{Path, PathBuf}, +}; const MAX_CONCURRENT_UPLOADS: usize = 1000; const DEFAULT_COMPRESSION: CompressionAlgorithm = CompressionAlgorithm::Zstd; @@ -171,12 +173,7 @@ impl<'a> Storage<'a> { .map(|(file_path, file)| -> Result<_, Error> { let alg = DEFAULT_COMPRESSION; let content = compress(file, alg)?; - let bucket_path = Path::new(prefix).join(&file_path); - - #[cfg(windows)] // On windows, we need to normalize \\ to / so the route logic works - let bucket_path = path_slash::PathBufExt::to_slash(&bucket_path).unwrap(); - #[cfg(not(windows))] - let bucket_path = bucket_path.into_os_string().into_string().unwrap(); + let bucket_path = Path::new(prefix).join(&file_path).to_slash().unwrap(); let mime = detect_mime(&file_path)?; file_paths_and_mimes.insert(file_path, mime.to_string()); diff --git a/src/test/fakes.rs b/src/test/fakes.rs index 5f1a5f1a9..fa899d75e 100644 --- a/src/test/fakes.rs +++ b/src/test/fakes.rs @@ -92,6 +92,11 @@ impl<'a> FakeRelease<'a> { self } + pub fn author(mut self, author: &str) -> Self { + self.package.authors = vec![author.into()]; + self + } + pub(crate) fn repo(mut self, repo: impl Into) -> Self { self.package.repository = Some(repo.into()); self diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs index 60fe318b4..4e5ff15a9 100644 --- a/src/web/crate_details.rs +++ b/src/web/crate_details.rs @@ -42,7 +42,6 @@ pub struct CrateDetails { github_stars: Option, github_forks: Option, github_issues: Option, - // TODO: Is this even needed for rendering pages? pub(crate) metadata: MetaData, is_library: bool, yanked: bool, diff --git a/src/web/page/handlebars.rs b/src/web/page/handlebars.rs index 558e32fea..988f29560 100644 --- a/src/web/page/handlebars.rs +++ b/src/web/page/handlebars.rs @@ -152,10 +152,8 @@ fn build_version_safe(version: &str) -> String { #[cfg(test)] mod tests { use super::*; - use crate::web::releases::{self, Release}; - use chrono::Utc; + use crate::web::releases; use iron::Url; - use serde_json::json; #[test] fn load_page_from_releases() { diff --git a/src/web/page/templates.rs b/src/web/page/templates.rs index 4f0c5f458..f93792144 100644 --- a/src/web/page/templates.rs +++ b/src/web/page/templates.rs @@ -1,16 +1,18 @@ -use crate::db::Pool; -use crate::error::Result; +use crate::{db::Pool, error::Result}; use arc_swap::ArcSwap; use chrono::{DateTime, Utc}; use failure::ResultExt; use notify::{watcher, RecursiveMode, Watcher}; +use path_slash::PathExt; use postgres::Connection; use serde_json::Value; -use std::collections::HashMap; -use std::path::PathBuf; -use std::sync::{mpsc::channel, Arc}; -use std::thread; -use std::time::Duration; +use std::{ + collections::HashMap, + path::PathBuf, + sync::{mpsc::channel, Arc}, + thread, + time::Duration, +}; use tera::{Result as TeraResult, Tera}; use walkdir::WalkDir; @@ -152,10 +154,8 @@ fn find_templates_in_filesystem(base: &str) -> Result Status` +#[macro_export] macro_rules! impl_webpage { ($page:ty = $template:expr $(, status = $status:expr)? $(, content_type = $content_type:expr)? $(,)?) => { impl $crate::web::page::WebPage for $page { diff --git a/src/web/releases.rs b/src/web/releases.rs index 78f668203..bf5b569e8 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -405,7 +405,7 @@ fn releases_handler(req: &mut Request, release_type: ReleaseType) -> IronResult< Order::FailuresByGithubStars, ), - ReleaseType::Author | ReleaseType::Search => unreachable!( + ReleaseType::Author | ReleaseType::Search => panic!( "The authors and search page have special requirements and cannot use this handler", ), }; @@ -657,8 +657,10 @@ pub fn activity_handler(req: &mut Request) -> IronResult { "SELECT value FROM config WHERE name = 'release_activity'", &[] )) - .get(0) - .get(0); + .iter() + .next() + .map_or(Value::Null, |row| row.get("value")); + Page::new(release_activity_data) .title("Releases") .set("description", "Monthly release activity") @@ -1027,10 +1029,114 @@ mod tests { }) } + #[test] + fn recent_releases() { + wrapper(|env| { + let web = env.frontend(); + assert_success("/releases", web)?; + + env.db().fake_release().name("some_random_crate").create()?; + env.db() + .fake_release() + .name("some_random_crate_that_failed") + .build_result_successful(false) + .create()?; + assert_success("/releases", web) + }) + } + + #[test] + fn recent_releases_by_stars() { + wrapper(|env| { + let web = env.frontend(); + assert_success("/releases/stars", web)?; + + env.db().fake_release().name("some_random_crate").create()?; + env.db() + .fake_release() + .name("some_random_crate_that_failed") + .build_result_successful(false) + .create()?; + assert_success("/releases/stars", web) + }) + } + + #[test] + fn recent_failures() { + wrapper(|env| { + let web = env.frontend(); + assert_success("/releases/recent-failures", web)?; + + env.db().fake_release().name("some_random_crate").create()?; + env.db() + .fake_release() + .name("some_random_crate_that_failed") + .build_result_successful(false) + .create()?; + assert_success("/releases/recent-failures", web) + }) + } + + #[test] + fn failures_by_stars() { + wrapper(|env| { + let web = env.frontend(); + assert_success("/releases/failures", web)?; + + env.db().fake_release().name("some_random_crate").create()?; + env.db() + .fake_release() + .name("some_random_crate_that_failed") + .build_result_successful(false) + .create()?; + assert_success("/releases/failures", web) + }) + } + + #[test] + fn release_activity() { + wrapper(|env| { + let web = env.frontend(); + assert_success("/releases/activity", web)?; + + env.db().fake_release().name("some_random_crate").create()?; + env.db() + .fake_release() + .name("some_random_crate_that_failed") + .build_result_successful(false) + .create()?; + assert_success("/releases/activity", web) + }) + } + + #[test] + fn recent_queue() { + wrapper(|env| { + let web = env.frontend(); + assert_success("/releases/queue", web)?; + + env.db().fake_release().name("some_random_crate").create()?; + env.db() + .fake_release() + .name("some_random_crate_that_failed") + .build_result_successful(false) + .create()?; + assert_success("/releases/queue", web) + }) + } + #[test] fn release_feed() { wrapper(|env| { let web = env.frontend(); + assert_success("/releases/feed", web)?; + + env.db().fake_release().name("some_random_crate").create()?; + env.db() + .fake_release() + .name("some_random_crate_that_failed") + .build_result_successful(false) + .create()?; assert_success("/releases/feed", web) }) } @@ -1078,4 +1184,16 @@ mod tests { Ok(()) }); } + + fn authors_page() { + wrapper(|env| { + let web = env.frontend(); + env.db() + .fake_release() + .name("some_random_crate") + .author("frankenstein ") + .create()?; + assert_success("/releases/frankenstein", web) + }) + } } diff --git a/src/web/sitemap.rs b/src/web/sitemap.rs index 79d31c749..7976e5bc2 100644 --- a/src/web/sitemap.rs +++ b/src/web/sitemap.rs @@ -1,10 +1,9 @@ -use crate::db::Pool; -use crate::{docbuilder::Limits, impl_webpage, web::page::WebPage}; +use crate::{db::Pool, docbuilder::Limits, impl_webpage, web::page::WebPage}; use chrono::{DateTime, NaiveDateTime, Utc}; use iron::{ headers::ContentType, mime::{Mime, SubLevel, TopLevel}, - IronResult, Request, Response, + status, IronResult, Request, Response, }; use serde::Serialize; use serde_json::Value; @@ -50,7 +49,7 @@ pub fn sitemap_handler(req: &mut Request) -> IronResult { } pub fn robots_txt_handler(_: &mut Request) -> IronResult { - let mut resp = Response::with("Sitemap: https://docs.rs/sitemap.xml"); + let mut resp = Response::with((status::Ok, "Sitemap: https://docs.rs/sitemap.xml")); resp.headers.set(ContentType::plaintext()); Ok(resp) @@ -82,3 +81,40 @@ pub fn about_handler(req: &mut Request) -> IronResult { } .into_response(req) } + +#[cfg(test)] +mod tests { + use crate::test::{assert_success, wrapper}; + + #[test] + fn sitemap() { + wrapper(|env| { + let web = env.frontend(); + assert_success("/sitemap.xml", web)?; + + env.db().fake_release().name("some_random_crate").create()?; + env.db() + .fake_release() + .name("some_random_crate_that_failed") + .build_result_successful(false) + .create()?; + assert_success("/sitemap.xml", web) + }) + } + + #[test] + fn about_page() { + wrapper(|env| { + let web = env.frontend(); + assert_success("/about", web) + }) + } + + #[test] + fn robots_txt() { + wrapper(|env| { + let web = env.frontend(); + assert_success("/robots.txt", web) + }) + } +} diff --git a/templates/releases.hbs b/templates/releases.hbs deleted file mode 100644 index 30bf47568..000000000 --- a/templates/releases.hbs +++ /dev/null @@ -1,144 +0,0 @@ -{{> header}} - - - -{{#if varsb.show_search_form}} -
    -

    Docs.rs

    - -
    -
    -
    - - -
    -
    - -
    -{{/if}} - -
    -
    - {{#if varsb.show_search_form}} - - {{else}} - - {{/if}} - - - {{#unless varsb.show_search_form}} - - {{/unless}} -
    -
    - -{{> footer}} diff --git a/templates/releases_feed.hbs b/templates/releases_feed.hbs deleted file mode 100644 index e60c4e1c1..000000000 --- a/templates/releases_feed.hbs +++ /dev/null @@ -1,21 +0,0 @@ - - -Docs.rs -Recent Rust crates - - - - -urn:docs-rs:{{cratesfyi_version_safe}} -{{content.[0].release_time_rfc3339}} -{{#each content}} - -{{name}}-{{version}} - -urn:docs-rs:{{name}}:{{version}} -{{release_time_rfc3339}} -{{#if description}}{{description}}{{else}}-{{/if}} -docs.rs - -{{/each}} - diff --git a/tera-templates/releases/feed.xml b/tera-templates/releases/feed.xml index 6715f1c60..88e0ed255 100644 --- a/tera-templates/releases/feed.xml +++ b/tera-templates/releases/feed.xml @@ -36,4 +36,4 @@ {%- endfor %} - \ No newline at end of file + From 7f931efb313f8b71bf39144e2e60c764a64e470f Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Mon, 29 Jun 2020 12:49:53 -0500 Subject: [PATCH 10/13] Added a few more tests and concrete dates for templates --- src/web/releases.rs | 25 +++++++++++++++++++++++++ src/web/sitemap.rs | 12 +++++++----- tera-templates/core/Cargo.toml.example | 8 ++++---- tera-templates/core/home.html | 3 ++- tera-templates/releases/header.html | 18 ++++++++++++------ tera-templates/releases/releases.html | 3 ++- 6 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/web/releases.rs b/src/web/releases.rs index bf5b569e8..db6e07b64 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -1029,6 +1029,31 @@ mod tests { }) } + #[test] + fn home_page() { + wrapper(|env| { + let web = env.frontend(); + assert_success("/", web)?; + + env.db().fake_release().name("some_random_crate").create()?; + env.db() + .fake_release() + .name("some_random_crate_that_failed") + .build_result_successful(false) + .create()?; + assert_success("/", web) + }) + } + + #[test] + fn search() { + wrapper(|env| { + let web = env.frontend(); + env.db().fake_release().name("some_random_crate").create()?; + assert_success("/releases/search?query=some_random_crate", web) + }) + } + #[test] fn recent_releases() { wrapper(|env| { diff --git a/src/web/sitemap.rs b/src/web/sitemap.rs index 7976e5bc2..ad0a6f1f7 100644 --- a/src/web/sitemap.rs +++ b/src/web/sitemap.rs @@ -69,11 +69,13 @@ pub fn about_handler(req: &mut Request) -> IronResult { let conn = extension!(req, Pool).get()?; let res = ctry!(conn.query("SELECT value FROM config WHERE name = 'rustc_version'", &[])); - let mut rustc_version = None; - - if let Some(Ok(Value::String(version))) = res.iter().next().and_then(|row| row.get_opt(0)) { - rustc_version = Some(version); - } + let rustc_version = res.iter().next().and_then(|row| { + if let Some(Ok(Value::String(version))) = row.get_opt(0) { + Some(version) + } else { + None + } + }); About { rustc_version, diff --git a/tera-templates/core/Cargo.toml.example b/tera-templates/core/Cargo.toml.example index b55157f81..22bf5c74d 100644 --- a/tera-templates/core/Cargo.toml.example +++ b/tera-templates/core/Cargo.toml.example @@ -5,7 +5,7 @@ name = "test" [package.metadata.docs.rs] # Features to pass to Cargo (default: []) -features = [ "feature1", "feature2" ] +features = ["feature1", "feature2"] # Whether to pass `--all-features` to Cargo (default: false) all-features = true @@ -35,10 +35,10 @@ default-target = "x86_64-unknown-linux-gnu" # Otherwise, these `targets` are built in addition to the default target. # If both `default-target` and `targets` are unset, # all tier-one targets will be built and `x86_64-unknown-linux-gnu` will be used as the default target. -targets = [ "x86_64-apple-darwin", "x86_64-pc-windows-msvc" ] +targets = ["x86_64-apple-darwin", "x86_64-pc-windows-msvc"] # Additional `RUSTFLAGS` to set (default: []) -rustc-args = [ "--example-rustc-arg" ] +rustc-args = ["--example-rustc-arg"] # Additional `RUSTDOCFLAGS` to set (default: []) -rustdoc-args = [ "--example-rustdoc-arg" ] +rustdoc-args = ["--example-rustdoc-arg"] diff --git a/tera-templates/core/home.html b/tera-templates/core/home.html index 059bd1372..ab2e5dc57 100644 --- a/tera-templates/core/home.html +++ b/tera-templates/core/home.html @@ -57,7 +57,8 @@

    Docs.rs

    {%- else -%} -
    +
    {{ release.release_time | timeformat(relative=true) }}
    {%- endif -%} diff --git a/tera-templates/releases/header.html b/tera-templates/releases/header.html index 4d6b0df60..f13b86b33 100644 --- a/tera-templates/releases/header.html +++ b/tera-templates/releases/header.html @@ -24,40 +24,46 @@

    {{ title }}

    -
    +
    {{ release.release_time | timeformat(relative=true) }}
    From 3c4f66a909c77c5ec7e7dba3de4aa36ae581909a Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Mon, 29 Jun 2020 13:15:03 -0500 Subject: [PATCH 11/13] Fixed authors stars --- tera-templates/releases/releases.html | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tera-templates/releases/releases.html b/tera-templates/releases/releases.html index 1e25d1915..ed3b55ad9 100644 --- a/tera-templates/releases/releases.html +++ b/tera-templates/releases/releases.html @@ -28,8 +28,7 @@ {%- endif -%}
  • - +
    {{ release.name }}-{{ release.version }} @@ -39,10 +38,18 @@ {{ release.description }}
    -
    - {{ release.release_time | timeformat(relative=true) }} -
    + {% if release_type == 'author' -%} +
    + {{ release.stars }} + +
    + {%- else -%} +
    + {{ release.release_time | timeformat(relative=true) }} +
    + {%- endif %}
  • From 5eedd2537f4382bec3f3742883276ec14422002d Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Tue, 30 Jun 2020 19:16:06 -0500 Subject: [PATCH 12/13] Update releases.rs --- Cargo.lock | 6 +++--- Cargo.toml | 7 ++----- src/web/releases.rs | 18 +----------------- 3 files changed, 6 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e10bb11d6..2b327c59c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -384,7 +384,7 @@ dependencies = [ "notify 4.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "params 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "path-slash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "path-slash 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "postgres 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "procfs 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", "prometheus 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1938,7 +1938,7 @@ dependencies = [ [[package]] name = "path-slash" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -4255,7 +4255,7 @@ dependencies = [ "checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" "checksum parking_lot_core 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" "checksum parse-zoneinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "feece9d0113b400182a7d00adcff81ccf29158c49c5abd11e2eed8589bf6ff07" -"checksum path-slash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ddf9566c1063a197427135fb518ed42f2be18630fc2401aa267ed2dc95e9c85d" +"checksum path-slash 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ff65715a17cba8979903db6294baef56c5d39e05c8b054cffa31e69e61f24c68" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" "checksum persistent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e8fa0009c4f3d350281309909c618abddf10bb7e3145f28410782f6a5ec74c5" diff --git a/Cargo.toml b/Cargo.toml index 4a247bdab..ee265de4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,11 +43,8 @@ mime_guess = "2" dotenv = "0.15" zstd = "0.5" git2 = { version = "0.13.6", default-features = false } -<<<<<<< HEAD -once_cell = "1.2.0" -======= -path-slash = "0.1.2" ->>>>>>> ddcd932... Added tests for multiple pages, path_slash for Windows and Unix builds and a status to robots.txt +once_cell = "1.4.0" +path-slash = "0.1.3" # Data serialization and deserialization serde = { version = "1.0", features = ["derive"] } diff --git a/src/web/releases.rs b/src/web/releases.rs index db6e07b64..b58da1885 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -565,7 +565,7 @@ pub fn search_handler(req: &mut Request) -> IronResult { OFFSET FLOOR(RANDOM() * 280) LIMIT 1", &[] )); - let row = rows.into_iter().next().unwrap(); // TODO: Is this really infallible? + let row = rows.into_iter().next().unwrap(); let name: String = row.get("name"); let version: String = row.get("version"); @@ -1134,22 +1134,6 @@ mod tests { }) } - #[test] - fn recent_queue() { - wrapper(|env| { - let web = env.frontend(); - assert_success("/releases/queue", web)?; - - env.db().fake_release().name("some_random_crate").create()?; - env.db() - .fake_release() - .name("some_random_crate_that_failed") - .build_result_successful(false) - .create()?; - assert_success("/releases/queue", web) - }) - } - #[test] fn release_feed() { wrapper(|env| { From 202457ff209f3101e8f5498ec86f37fe92550604 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Wed, 1 Jul 2020 11:55:51 -0500 Subject: [PATCH 13/13] Added test attribute for authors page test --- src/web/releases.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/web/releases.rs b/src/web/releases.rs index b58da1885..c083a1763 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -1194,6 +1194,7 @@ mod tests { }); } + #[test] fn authors_page() { wrapper(|env| { let web = env.frontend();