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
29 changes: 22 additions & 7 deletions src/controllers/version/downloads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,22 @@ use crate::schema::*;
use crate::util::errors::AppResult;
use crate::util::{RequestUtils, redirect};
use crate::views::EncodableVersionDownload;
use axum::Json;
use axum::extract::{FromRequestParts, Query};
use axum::response::{IntoResponse, Response};
use axum_extra::json;
use axum_extra::response::ErasedJson;
use chrono::{Duration, NaiveDate, Utc};
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use http::request::Parts;

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct UrlResponse {
/// The URL to the crate file.
#[schema(example = "https://static.crates.io/crates/serde/serde-1.0.0.crate")]
pub url: String,
}

/// Download a crate version.
///
/// This returns a URL to the location where the crate is stored.
Expand All @@ -26,7 +33,10 @@ use http::request::Parts;
path = "/api/v1/crates/{name}/{version}/download",
params(CrateVersionPath),
tag = "versions",
responses((status = 200, description = "Successful Response")),
responses(
(status = 302, description = "Successful Response (default)", headers(("location" = String, description = "The URL to the crate file."))),
(status = 200, description = "Successful Response (for `content-type: application/json`)", body = inline(UrlResponse)),
),
)]
pub async fn download_version(
app: AppState,
Expand All @@ -51,6 +61,11 @@ pub struct DownloadsQueryParams {
before_date: Option<NaiveDate>,
}

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct DownloadsResponse {
pub version_downloads: Vec<EncodableVersionDownload>,
}

/// Get the download counts for a crate version.
///
/// This includes the per-day downloads for the last 90 days.
Expand All @@ -59,13 +74,13 @@ pub struct DownloadsQueryParams {
path = "/api/v1/crates/{name}/{version}/downloads",
params(CrateVersionPath, DownloadsQueryParams),
tag = "versions",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(DownloadsResponse))),
)]
pub async fn get_version_downloads(
app: AppState,
path: CrateVersionPath,
params: DownloadsQueryParams,
) -> AppResult<ErasedJson> {
) -> AppResult<Json<DownloadsResponse>> {
let mut conn = app.db_read().await?;
let version = path.load_version(&mut conn).await?;

Expand All @@ -75,14 +90,14 @@ pub async fn get_version_downloads(

let cutoff_start_date = cutoff_end_date - Duration::days(89);

let downloads = VersionDownload::belonging_to(&version)
let version_downloads = VersionDownload::belonging_to(&version)
.filter(version_downloads::date.between(cutoff_start_date, cutoff_end_date))
.order(version_downloads::date)
.load(&mut conn)
.await?
.into_iter()
.map(VersionDownload::into)
.collect::<Vec<EncodableVersionDownload>>();
.collect();

Ok(json!({ "version_downloads": downloads }))
Ok(Json(DownloadsResponse { version_downloads }))
}
20 changes: 15 additions & 5 deletions src/controllers/version/readme.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
use crate::app::AppState;
use crate::controllers::version::CrateVersionPath;
use crate::util::{RequestUtils, redirect};
use axum::Json;
use axum::response::{IntoResponse, Response};
use axum_extra::json;
use http::request::Parts;

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct UrlResponse {
/// The URL to the readme file.
#[schema(example = "https://static.crates.io/readmes/serde/serde-1.0.0.html")]
pub url: String,
}

/// Get the readme of a crate version.
#[utoipa::path(
get,
path = "/api/v1/crates/{name}/{version}/readme",
params(CrateVersionPath),
tag = "versions",
responses((status = 200, description = "Successful Response")),
responses(
(status = 302, description = "Successful Response (default)", headers(("location" = String, description = "The URL to the readme file."))),
(status = 200, description = "Successful Response (for `content-type: application/json`)", body = inline(UrlResponse)),
),
)]
pub async fn get_version_readme(app: AppState, path: CrateVersionPath, req: Parts) -> Response {
let redirect_url = app.storage.readme_location(&path.name, &path.version);
let url = app.storage.readme_location(&path.name, &path.version);
if req.wants_json() {
json!({ "url": redirect_url }).into_response()
Json(UrlResponse { url }).into_response()
} else {
redirect(redirect_url)
redirect(url)
}
}
15 changes: 9 additions & 6 deletions src/controllers/version/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ use crate::util::errors::{AppResult, bad_request, custom};
use crate::views::EncodableVersion;
use crate::worker::jobs::{SyncToGitIndex, SyncToSparseIndex, UpdateDefaultVersion};
use axum::Json;
use axum_extra::json;
use axum_extra::response::ErasedJson;
use crates_io_worker::BackgroundJob;
use diesel::prelude::*;
use diesel_async::{AsyncPgConnection, RunQueryDsl};
Expand All @@ -29,6 +27,11 @@ pub struct VersionUpdateRequest {
version: VersionUpdate,
}

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct UpdateResponse {
pub version: EncodableVersion,
}

/// Update a crate version.
///
/// This endpoint allows updating the `yanked` state of a version, including a yank message.
Expand All @@ -41,14 +44,14 @@ pub struct VersionUpdateRequest {
("cookie" = []),
),
tag = "versions",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(UpdateResponse))),
)]
pub async fn update_version(
state: AppState,
path: CrateVersionPath,
req: Parts,
Json(update_request): Json<VersionUpdateRequest>,
) -> AppResult<ErasedJson> {
) -> AppResult<Json<UpdateResponse>> {
let mut conn = state.db_write().await?;
let (mut version, krate) = path.load_version_and_crate(&mut conn).await?;
validate_yank_update(&update_request.version, &version)?;
Expand All @@ -74,8 +77,8 @@ pub async fn update_version(
VersionOwnerAction::by_version(&mut conn, &version),
version.published_by(&mut conn),
)?;
let updated_version = EncodableVersion::from(version, &krate.name, published_by, actions);
Ok(json!({ "version": updated_version }))
let version = EncodableVersion::from(version, &krate.name, published_by, actions);
Ok(Json(UpdateResponse { version }))
}

fn validate_yank_update(update_data: &VersionUpdate, version: &Version) -> AppResult<()> {
Expand Down
120 changes: 118 additions & 2 deletions src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,33 @@ expression: response.json()
],
"type": "object"
},
"VersionDownload": {
"properties": {
"date": {
"description": "The date this download count is for.",
"example": "2019-12-13",
"type": "string"
},
"downloads": {
"description": "The number of downloads for this version on the given date.",
"example": 123,
"format": "int32",
"type": "integer"
},
"version": {
"description": "The ID of the version this download count is for.",
"example": 42,
"format": "int32",
"type": "integer"
}
},
"required": [
"version",
"downloads",
"date"
],
"type": "object"
},
"VersionLinks": {
"properties": {
"authors": {
Expand Down Expand Up @@ -1829,6 +1856,21 @@ expression: response.json()
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"version": {
"$ref": "#/components/schemas/Version"
}
},
"required": [
"version"
],
"type": "object"
}
}
},
"description": "Successful Response"
}
},
Expand Down Expand Up @@ -1964,7 +2006,35 @@ expression: response.json()
],
"responses": {
"200": {
"description": "Successful Response"
"content": {
"application/json": {
"schema": {
"properties": {
"url": {
"description": "The URL to the crate file.",
"example": "https://static.crates.io/crates/serde/serde-1.0.0.crate",
"type": "string"
}
},
"required": [
"url"
],
"type": "object"
}
}
},
"description": "Successful Response (for `content-type: application/json`)"
},
"302": {
"description": "Successful Response (default)",
"headers": {
"location": {
"description": "The URL to the crate file.",
"schema": {
"type": "string"
}
}
}
}
},
"summary": "Download a crate version.",
Expand Down Expand Up @@ -2011,6 +2081,24 @@ expression: response.json()
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"version_downloads": {
"items": {
"$ref": "#/components/schemas/VersionDownload"
},
"type": "array"
}
},
"required": [
"version_downloads"
],
"type": "object"
}
}
},
"description": "Successful Response"
}
},
Expand Down Expand Up @@ -2046,7 +2134,35 @@ expression: response.json()
],
"responses": {
"200": {
"description": "Successful Response"
"content": {
"application/json": {
"schema": {
"properties": {
"url": {
"description": "The URL to the readme file.",
"example": "https://static.crates.io/readmes/serde/serde-1.0.0.html",
"type": "string"
}
},
"required": [
"url"
],
"type": "object"
}
}
},
"description": "Successful Response (for `content-type: application/json`)"
},
"302": {
"description": "Successful Response (default)",
"headers": {
"location": {
"description": "The URL to the readme file.",
"schema": {
"type": "string"
}
}
}
}
},
"summary": "Get the readme of a crate version.",
Expand Down
11 changes: 10 additions & 1 deletion src/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,19 @@ impl EncodableDependency {
}
}

#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, utoipa::ToSchema)]
#[schema(as = VersionDownload)]
pub struct EncodableVersionDownload {
/// The ID of the version this download count is for.
#[schema(example = 42)]
pub version: i32,

/// The number of downloads for this version on the given date.
#[schema(example = 123)]
pub downloads: i32,

/// The date this download count is for.
#[schema(example = "2019-12-13")]
pub date: String,
}

Expand Down