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
72 changes: 45 additions & 27 deletions src/controllers/krate/downloads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ use crate::models::{User, Version as FullVersion, VersionDownload, VersionOwnerA
use crate::schema::{version_downloads, version_owner_actions, versions};
use crate::util::errors::{AppResult, BoxedAppError, bad_request};
use crate::views::{EncodableVersion, EncodableVersionDownload};
use axum::Json;
use axum::extract::FromRequestParts;
use axum_extra::extract::Query;
use axum_extra::json;
use axum_extra::response::ErasedJson;
use crates_io_database::schema::users;
use crates_io_diesel_helpers::to_char;
use diesel::prelude::*;
Expand All @@ -37,6 +36,37 @@ pub struct DownloadsQueryParams {
include: Option<String>,
}

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct DownloadsResponse {
/// The per-day download counts for the last 90 days.
pub version_downloads: Vec<EncodableVersionDownload>,

/// The versions referenced in the download counts, if `?include=versions`
/// was requested.
#[serde(skip_serializing_if = "Option::is_none")]
pub versions: Option<Vec<EncodableVersion>>,

#[schema(inline)]
pub meta: DownloadsMeta,
}

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct DownloadsMeta {
#[schema(inline)]
pub extra_downloads: Vec<ExtraDownload>,
}

#[derive(Debug, Serialize, Queryable, utoipa::ToSchema)]
pub struct ExtraDownload {
/// The date this download count is for.
#[schema(example = "2019-12-13")]
date: String,

/// The number of downloads on the given date.
#[schema(example = 123)]
downloads: i64,
}

/// Get the download counts for a crate.
///
/// This includes the per-day downloads for the last 90 days and for the
Expand All @@ -46,14 +76,13 @@ pub struct DownloadsQueryParams {
path = "/api/v1/crates/{name}/downloads",
params(CratePath, DownloadsQueryParams),
tag = "crates",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(DownloadsResponse))),
)]

pub async fn get_crate_downloads(
state: AppState,
path: CratePath,
params: DownloadsQueryParams,
) -> AppResult<ErasedJson> {
) -> AppResult<Json<DownloadsResponse>> {
let mut conn = state.db_read().await?;

use diesel::dsl::*;
Expand All @@ -78,7 +107,7 @@ pub async fn get_crate_downloads(
.unwrap_or_default();

let sum_downloads = sql::<BigInt>("SUM(version_downloads.downloads)");
let (downloads, extra, versions_and_publishers, actions) = tokio::try_join!(
let (downloads, extra_downloads, versions_and_publishers, actions) = tokio::try_join!(
VersionDownload::belonging_to(latest_five)
.filter(version_downloads::date.gt(date(now - 90.days())))
.order((
Expand All @@ -101,18 +130,12 @@ pub async fn get_crate_downloads(
load_actions(&mut conn, latest_five, include.versions),
)?;

let downloads = downloads
let version_downloads = downloads
.into_iter()
.map(VersionDownload::into)
.collect::<Vec<EncodableVersionDownload>>();

#[derive(Serialize, Queryable)]
struct ExtraDownload {
date: String,
downloads: i64,
}

if include.versions {
let versions = if include.versions {
let versions_and_publishers = versions_and_publishers.grouped_by(latest_five);
let actions = actions.grouped_by(latest_five);
let versions = versions_and_publishers
Expand All @@ -125,20 +148,15 @@ pub async fn get_crate_downloads(
})
.collect::<Vec<_>>();

return Ok(json!({
"version_downloads": downloads,
"versions": versions,
"meta": {
"extra_downloads": extra,
},
}));
}
Some(versions)
} else {
None
};

Ok(json!({
"version_downloads": downloads,
"meta": {
"extra_downloads": extra,
},
Ok(Json(DownloadsResponse {
version_downloads,
versions,
meta: DownloadsMeta { extra_downloads },
}))
}

Expand Down
17 changes: 11 additions & 6 deletions src/controllers/krate/follow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ use crate::controllers::krate::CratePath;
use crate::models::{Crate, Follow};
use crate::schema::*;
use crate::util::errors::{AppResult, crate_not_found};
use axum_extra::json;
use axum_extra::response::ErasedJson;
use axum::Json;
use diesel::prelude::*;
use diesel_async::{AsyncPgConnection, RunQueryDsl};
use http::request::Parts;
Expand Down Expand Up @@ -74,20 +73,26 @@ pub async fn unfollow_crate(app: AppState, path: CratePath, req: Parts) -> AppRe
Ok(OkResponse::new())
}

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct FollowingResponse {
/// Whether the authenticated user is following the crate.
pub following: bool,
}

/// Check if a crate is followed.
#[utoipa::path(
get,
path = "/api/v1/crates/{name}/following",
params(CratePath),
security(("cookie" = [])),
tag = "crates",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(FollowingResponse))),
)]
pub async fn get_following_crate(
app: AppState,
path: CratePath,
req: Parts,
) -> AppResult<ErasedJson> {
) -> AppResult<Json<FollowingResponse>> {
use diesel::dsl::exists;

let mut conn = app.db_read_prefer_primary().await?;
Expand All @@ -98,8 +103,8 @@ pub async fn get_following_crate(

let follow = follow_target(&path.name, &mut conn, user_id).await?;
let following = diesel::select(exists(follows::table.find(follow.id())))
.get_result::<bool>(&mut conn)
.get_result(&mut conn)
.await?;

Ok(json!({ "following": following }))
Ok(Json(FollowingResponse { following }))
}
43 changes: 32 additions & 11 deletions src/controllers/krate/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ use crate::util::errors::{
AppResult, BoxedAppError, bad_request, crate_not_found, version_not_found,
};
use crate::views::{EncodableCategory, EncodableCrate, EncodableKeyword, EncodableVersion};
use axum::Json;
use axum::extract::{FromRequestParts, Query};
use axum_extra::json;
use axum_extra::response::ErasedJson;
use diesel::prelude::*;
use diesel_async::{AsyncPgConnection, RunQueryDsl};
use futures_util::FutureExt;
Expand All @@ -41,6 +40,25 @@ pub struct FindQueryParams {
include: Option<String>,
}

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct GetResponse {
/// The crate metadata.
#[serde(rename = "crate")]
krate: EncodableCrate,

/// The versions of the crate.
#[schema(example = json!(null))]
versions: Option<Vec<EncodableVersion>>,

/// The keywords of the crate.
#[schema(example = json!(null))]
keywords: Option<Vec<EncodableKeyword>>,

/// The categories of the crate.
#[schema(example = json!(null))]
categories: Option<Vec<EncodableCategory>>,
}

/// Get crate metadata (for the `new` crate).
///
/// This endpoint works around a small limitation in `axum` and is delegating
Expand All @@ -49,9 +67,12 @@ pub struct FindQueryParams {
get,
path = "/api/v1/crates/new",
tag = "crates",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(GetResponse))),
)]
pub async fn find_new_crate(app: AppState, params: FindQueryParams) -> AppResult<ErasedJson> {
pub async fn find_new_crate(
app: AppState,
params: FindQueryParams,
) -> AppResult<Json<GetResponse>> {
let name = "new".to_string();
find_crate(app, CratePath { name }, params).await
}
Expand All @@ -62,13 +83,13 @@ pub async fn find_new_crate(app: AppState, params: FindQueryParams) -> AppResult
path = "/api/v1/crates/{name}",
params(CratePath, FindQueryParams),
tag = "crates",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(GetResponse))),
)]
pub async fn find_crate(
app: AppState,
path: CratePath,
params: FindQueryParams,
) -> AppResult<ErasedJson> {
) -> AppResult<Json<GetResponse>> {
let mut conn = app.db_read().await?;

let include = params
Expand Down Expand Up @@ -186,11 +207,11 @@ pub async fn find_crate(
.collect::<Vec<EncodableCategory>>()
});

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

Expand Down
Loading