Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 118 additions & 119 deletions src/controllers/krate/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use crate::models::{
Version, VersionOwnerAction,
};
use crate::schema::*;
use crate::tasks::spawn_blocking;
use crate::util::diesel::prelude::*;
use crate::util::errors::{bad_request, crate_not_found, AppResult, BoxedAppError};
use crate::util::{redirect, RequestUtils};
Expand All @@ -22,7 +21,6 @@ use axum::extract::Path;
use axum::response::{IntoResponse, Response};
use axum_extra::json;
use axum_extra::response::ErasedJson;
use diesel_async::async_connection_wrapper::AsyncConnectionWrapper;
use http::request::Parts;
use std::cmp::Reverse;
use std::str::FromStr;
Expand All @@ -34,25 +32,19 @@ pub async fn show_new(app: AppState, req: Parts) -> AppResult<ErasedJson> {

/// Handles the `GET /crates/:crate_id` route.
pub async fn show(app: AppState, Path(name): Path<String>, req: Parts) -> AppResult<ErasedJson> {
let conn = app.db_read().await?;
spawn_blocking(move || {
use diesel::RunQueryDsl;

let conn: &mut AsyncConnectionWrapper<_> = &mut conn.into();

let include = req
.query()
.get("include")
.map(|mode| ShowIncludeMode::from_str(mode))
.transpose()?
.unwrap_or_default();

let (krate, downloads, default_version, yanked): (
Crate,
i64,
Option<String>,
Option<bool>,
) = Crate::by_name(&name)
use diesel_async::RunQueryDsl;

let mut conn = app.db_read().await?;

let include = req
.query()
.get("include")
.map(|mode| ShowIncludeMode::from_str(mode))
.transpose()?
.unwrap_or_default();

let (krate, downloads, default_version, yanked): (Crate, i64, Option<String>, Option<bool>) =
Crate::by_name(&name)
.inner_join(crate_downloads::table)
.left_join(default_versions::table)
.left_join(versions::table.on(default_versions::version_id.eq(versions::id)))
Expand All @@ -62,109 +54,116 @@ pub async fn show(app: AppState, Path(name): Path<String>, req: Parts) -> AppRes
versions::num.nullable(),
versions::yanked.nullable(),
))
.first(conn)
.first(&mut conn)
.await
.optional()?
.ok_or_else(|| crate_not_found(&name))?;

let versions_publishers_and_audit_actions = if include.versions {
let mut versions_and_publishers: Vec<(Version, Option<User>)> =
Version::belonging_to(&krate)
.left_outer_join(users::table)
.select(<(Version, Option<User>)>::as_select())
.load(conn)?;
versions_and_publishers.sort_by_cached_key(|(version, _)| {
Reverse(semver::Version::parse(&version.num).ok())
});

let versions = versions_and_publishers
.iter()
.map(|(v, _)| v)
.collect::<Vec<_>>();
let actions = VersionOwnerAction::for_versions(conn, &versions)?;
Some(
versions_and_publishers
.into_iter()
.zip(actions)
.map(|((v, pb), aas)| (v, pb, aas))
.collect::<Vec<_>>(),
)
} else {
None
};
let ids = versions_publishers_and_audit_actions
.as_ref()
.map(|vps| vps.iter().map(|v| v.0.id).collect());

let kws = if include.keywords {
Some(
CrateKeyword::belonging_to(&krate)
.inner_join(keywords::table)
.select(Keyword::as_select())
.load(conn)?,
)
} else {
None
};
let cats = if include.categories {
Some(
CrateCategory::belonging_to(&krate)
.inner_join(categories::table)
.select(Category::as_select())
.load(conn)?,
)
} else {
None
};
let recent_downloads = if include.downloads {
RecentCrateDownloads::belonging_to(&krate)
.select(recent_crate_downloads::downloads)
.get_result(conn)
.optional()?
} else {
None
};
let versions_publishers_and_audit_actions = if include.versions {
let mut versions_and_publishers: Vec<(Version, Option<User>)> =
Version::belonging_to(&krate)
.left_outer_join(users::table)
.select(<(Version, Option<User>)>::as_select())
.load(&mut conn)
.await?;

let top_versions = if include.versions {
Some(krate.top_versions(conn)?)
} else {
None
};
versions_and_publishers
.sort_by_cached_key(|(version, _)| Reverse(semver::Version::parse(&version.num).ok()));

let encodable_crate = EncodableCrate::from(
krate.clone(),
default_version.as_deref(),
yanked,
top_versions.as_ref(),
ids,
kws.as_deref(),
cats.as_deref(),
false,
downloads,
recent_downloads,
);
let encodable_versions = versions_publishers_and_audit_actions.map(|vpa| {
vpa.into_iter()
.map(|(v, pb, aas)| EncodableVersion::from(v, &krate.name, pb, aas))
.collect::<Vec<_>>()
});
let encodable_keywords = kws.map(|kws| {
kws.into_iter()
.map(Keyword::into)
.collect::<Vec<EncodableKeyword>>()
});
let encodable_cats = cats.map(|cats| {
cats.into_iter()
.map(Category::into)
.collect::<Vec<EncodableCategory>>()
});
Ok(json!({
"crate": encodable_crate,
"versions": encodable_versions,
"keywords": encodable_keywords,
"categories": encodable_cats,
}))
})
.await?
let versions = versions_and_publishers
.iter()
.map(|(v, _)| v)
.collect::<Vec<_>>();
let actions = VersionOwnerAction::async_for_versions(&mut conn, &versions).await?;
Some(
versions_and_publishers
.into_iter()
.zip(actions)
.map(|((v, pb), aas)| (v, pb, aas))
.collect::<Vec<_>>(),
)
} else {
None
};
let ids = versions_publishers_and_audit_actions
.as_ref()
.map(|vps| vps.iter().map(|v| v.0.id).collect());

let kws = if include.keywords {
Some(
CrateKeyword::belonging_to(&krate)
.inner_join(keywords::table)
.select(Keyword::as_select())
.load(&mut conn)
.await?,
)
} else {
None
};
let cats = if include.categories {
Some(
CrateCategory::belonging_to(&krate)
.inner_join(categories::table)
.select(Category::as_select())
.load(&mut conn)
.await?,
)
} else {
None
};
let recent_downloads = if include.downloads {
RecentCrateDownloads::belonging_to(&krate)
.select(recent_crate_downloads::downloads)
.get_result(&mut conn)
.await
.optional()?
} else {
None
};

let top_versions = if include.versions {
Some(krate.async_top_versions(&mut conn).await?)
} else {
None
};

let encodable_crate = EncodableCrate::from(
krate.clone(),
default_version.as_deref(),
yanked,
top_versions.as_ref(),
ids,
kws.as_deref(),
cats.as_deref(),
false,
downloads,
recent_downloads,
);

let encodable_versions = versions_publishers_and_audit_actions.map(|vpa| {
vpa.into_iter()
.map(|(v, pb, aas)| EncodableVersion::from(v, &krate.name, pb, aas))
.collect::<Vec<_>>()
});

let encodable_keywords = kws.map(|kws| {
kws.into_iter()
.map(Keyword::into)
.collect::<Vec<EncodableKeyword>>()
});

let encodable_cats = cats.map(|cats| {
cats.into_iter()
.map(Category::into)
.collect::<Vec<EncodableCategory>>()
});

Ok(json!({
"crate": encodable_crate,
"versions": encodable_versions,
"keywords": encodable_keywords,
"categories": encodable_cats,
}))
}

#[derive(Debug)]
Expand Down
18 changes: 18 additions & 0 deletions src/models/krate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,24 @@ impl Crate {
}
}

/// Return both the newest (most recently updated) and
/// highest version (in semver order) for the current crate,
/// where all top versions are not yanked.
pub async fn async_top_versions(
&self,
conn: &mut AsyncPgConnection,
) -> QueryResult<TopVersions> {
use diesel_async::RunQueryDsl;

Ok(TopVersions::from_date_version_pairs(
Version::belonging_to(self)
.filter(versions::yanked.eq(false))
.select((versions::created_at, versions::num))
.load(conn)
.await?,
))
}

/// Return both the newest (most recently updated) and
/// highest version (in semver order) for the current crate,
/// where all top versions are not yanked.
Expand Down