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
27 changes: 21 additions & 6 deletions src/controllers/site_metadata.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
use crate::app::AppState;
use axum::Json;
use axum::response::IntoResponse;
use axum_extra::json;

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct MetadataResponse<'a> {
/// The SHA1 of the currently deployed commit.
#[schema(example = "0aebe2cdfacae1229b93853b1c58f9352195f081")]
pub deployed_sha: &'a str,

/// The SHA1 of the currently deployed commit.
#[schema(example = "0aebe2cdfacae1229b93853b1c58f9352195f081")]
pub commit: &'a str,

/// Whether the crates.io service is in read-only mode.
pub read_only: bool,
}

/// Get crates.io metadata.
///
Expand All @@ -10,17 +24,18 @@ use axum_extra::json;
get,
path = "/api/v1/site_metadata",
tag = "other",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(MetadataResponse<'_>))),
)]
pub async fn get_site_metadata(state: AppState) -> impl IntoResponse {
let read_only = state.config.db.are_all_read_only();

let deployed_sha =
dotenvy::var("HEROKU_SLUG_COMMIT").unwrap_or_else(|_| String::from("unknown"));

json!({
"deployed_sha": &deployed_sha[..],
"commit": &deployed_sha[..],
"read_only": read_only,
Json(MetadataResponse {
deployed_sha: &deployed_sha,
commit: &deployed_sha,
read_only,
})
.into_response()
}
71 changes: 46 additions & 25 deletions src/controllers/summary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,41 @@ use crate::schema::{
};
use crate::util::errors::AppResult;
use crate::views::{EncodableCategory, EncodableCrate, EncodableKeyword};
use axum_extra::json;
use axum_extra::response::ErasedJson;
use axum::Json;
use diesel::prelude::*;
use diesel_async::{AsyncPgConnection, RunQueryDsl};
use futures_util::FutureExt;
use std::future::Future;

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct SummaryResponse {
/// The total number of downloads across all crates.
#[schema(example = 123_456_789)]
num_downloads: i64,

/// The total number of crates on crates.io.
#[schema(example = 123_456)]
num_crates: i64,

/// The 10 most recently created crates.
new_crates: Vec<EncodableCrate>,

/// The 10 crates with the highest total number of downloads.
most_downloaded: Vec<EncodableCrate>,

/// The 10 crates with the highest number of downloads within the last 90 days.
most_recently_downloaded: Vec<EncodableCrate>,

/// The 10 most recently updated crates.
just_updated: Vec<EncodableCrate>,

/// The 10 most popular keywords.
popular_keywords: Vec<EncodableKeyword>,

/// The 10 most popular categories.
popular_categories: Vec<EncodableCategory>,
}

/// Get front page data.
///
/// This endpoint returns a summary of the most important data for the front
Expand All @@ -20,9 +48,9 @@ use std::future::Future;
get,
path = "/api/v1/summary",
tag = "other",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(SummaryResponse))),
)]
pub async fn get_summary(state: AppState) -> AppResult<ErasedJson> {
pub async fn get_summary(state: AppState) -> AppResult<Json<SummaryResponse>> {
let mut conn = state.db_read().await?;

let config = &state.config;
Expand All @@ -37,10 +65,10 @@ pub async fn get_summary(state: AppState) -> AppResult<ErasedJson> {
popular_categories,
popular_keywords,
) = tokio::try_join!(
crates::table.count().get_result::<i64>(&mut conn).boxed(),
crates::table.count().get_result(&mut conn).boxed(),
metadata::table
.select(metadata::total_downloads)
.get_result::<i64>(&mut conn)
.get_result(&mut conn)
.boxed(),
crates::table
.inner_join(crate_downloads::table)
Expand Down Expand Up @@ -100,25 +128,18 @@ pub async fn get_summary(state: AppState) -> AppResult<ErasedJson> {
encode_crates(&mut conn, just_updated),
)?;

let popular_categories = popular_categories
.into_iter()
.map(Category::into)
.collect::<Vec<EncodableCategory>>();

let popular_keywords = popular_keywords
.into_iter()
.map(Keyword::into)
.collect::<Vec<EncodableKeyword>>();

Ok(json!({
"num_downloads": num_downloads,
"num_crates": num_crates,
"new_crates": new_crates,
"most_downloaded": most_downloaded,
"most_recently_downloaded": most_recently_downloaded,
"just_updated": just_updated,
"popular_keywords": popular_keywords,
"popular_categories": popular_categories,
let popular_categories = popular_categories.into_iter().map(Category::into).collect();
let popular_keywords = popular_keywords.into_iter().map(Keyword::into).collect();

Ok(Json(SummaryResponse {
num_downloads,
num_crates,
new_crates,
most_downloaded,
most_recently_downloaded,
just_updated,
popular_keywords,
popular_categories,
}))
}

Expand Down
101 changes: 101 additions & 0 deletions src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3661,6 +3661,34 @@ expression: response.json()
"operationId": "get_site_metadata",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"commit": {
"description": "The SHA1 of the currently deployed commit.",
"example": "0aebe2cdfacae1229b93853b1c58f9352195f081",
"type": "string"
},
"deployed_sha": {
"description": "The SHA1 of the currently deployed commit.",
"example": "0aebe2cdfacae1229b93853b1c58f9352195f081",
"type": "string"
},
"read_only": {
"description": "Whether the crates.io service is in read-only mode.",
"type": "boolean"
}
},
"required": [
"deployed_sha",
"commit",
"read_only"
],
"type": "object"
}
}
},
"description": "Successful Response"
}
},
Expand All @@ -3676,6 +3704,79 @@ expression: response.json()
"operationId": "get_summary",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"just_updated": {
"description": "The 10 most recently updated crates.",
"items": {
"$ref": "#/components/schemas/Crate"
},
"type": "array"
},
"most_downloaded": {
"description": "The 10 crates with the highest total number of downloads.",
"items": {
"$ref": "#/components/schemas/Crate"
},
"type": "array"
},
"most_recently_downloaded": {
"description": "The 10 crates with the highest number of downloads within the last 90 days.",
"items": {
"$ref": "#/components/schemas/Crate"
},
"type": "array"
},
"new_crates": {
"description": "The 10 most recently created crates.",
"items": {
"$ref": "#/components/schemas/Crate"
},
"type": "array"
},
"num_crates": {
"description": "The total number of crates on crates.io.",
"example": 123456,
"format": "int64",
"type": "integer"
},
"num_downloads": {
"description": "The total number of downloads across all crates.",
"example": 123456789,
"format": "int64",
"type": "integer"
},
"popular_categories": {
"description": "The 10 most popular categories.",
"items": {
"$ref": "#/components/schemas/Category"
},
"type": "array"
},
"popular_keywords": {
"description": "The 10 most popular keywords.",
"items": {
"$ref": "#/components/schemas/Keyword"
},
"type": "array"
}
},
"required": [
"num_downloads",
"num_crates",
"new_crates",
"most_downloaded",
"most_recently_downloaded",
"just_updated",
"popular_keywords",
"popular_categories"
],
"type": "object"
}
}
},
"description": "Successful Response"
}
},
Expand Down