Skip to content

Extract CratePath and CrateVersionPath structs #10208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Dec 15, 2024
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
43 changes: 43 additions & 0 deletions src/controllers/krate.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
use crate::models::Crate;
use crate::util::errors::{crate_not_found, AppResult};
use axum::extract::{FromRequestParts, Path};
use crates_io_database::schema::crates;
use diesel::{OptionalExtension, QueryDsl};
use diesel_async::{AsyncPgConnection, RunQueryDsl};
use utoipa::IntoParams;

pub mod delete;
pub mod downloads;
pub mod follow;
Expand All @@ -6,3 +14,38 @@ pub mod owners;
pub mod publish;
pub mod search;
pub mod versions;

#[derive(Deserialize, FromRequestParts, IntoParams)]
#[into_params(parameter_in = Path)]
#[from_request(via(Path))]
pub struct CratePath {
/// Name of the crate
pub name: String,
}

impl CratePath {
pub async fn load_crate(&self, conn: &mut AsyncPgConnection) -> AppResult<Crate> {
load_crate(conn, &self.name).await
}

pub async fn load_crate_id(&self, conn: &mut AsyncPgConnection) -> AppResult<i32> {
load_crate_id(conn, &self.name).await
}
}

pub async fn load_crate(conn: &mut AsyncPgConnection, name: &str) -> AppResult<Crate> {
Crate::by_name(name)
.first(conn)
.await
.optional()?
.ok_or_else(|| crate_not_found(name))
}

pub async fn load_crate_id(conn: &mut AsyncPgConnection, name: &str) -> AppResult<i32> {
Crate::by_name(name)
.select(crates::id)
.first(conn)
.await
.optional()?
.ok_or_else(|| crate_not_found(name))
}
22 changes: 7 additions & 15 deletions src/controllers/krate/delete.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::app::AppState;
use crate::auth::AuthCheck;
use crate::models::{Crate, NewDeletedCrate, Rights};
use crate::controllers::krate::CratePath;
use crate::models::{NewDeletedCrate, Rights};
use crate::schema::{crate_downloads, crates, dependencies};
use crate::util::errors::{crate_not_found, custom, AppResult, BoxedAppError};
use crate::util::errors::{custom, AppResult, BoxedAppError};
use crate::worker::jobs;
use axum::extract::Path;
use bigdecimal::ToPrimitive;
use chrono::{TimeDelta, Utc};
use crates_io_database::schema::deleted_crates;
Expand All @@ -30,22 +30,18 @@ const AVAILABLE_AFTER: TimeDelta = TimeDelta::hours(24);
#[utoipa::path(
delete,
path = "/api/v1/crates/{name}",
params(CratePath),
tag = "crates",
responses((status = 200, description = "Successful Response")),
)]
pub async fn delete_crate(
Path(name): Path<String>,
parts: Parts,
app: AppState,
) -> AppResult<StatusCode> {
pub async fn delete_crate(path: CratePath, parts: Parts, app: AppState) -> AppResult<StatusCode> {
let mut conn = app.db_write().await?;

// Check that the user is authenticated
let auth = AuthCheck::only_cookie().check(&parts, &mut conn).await?;

// Check that the crate exists
let krate = find_crate(&mut conn, &name).await?;
let krate = krate.ok_or_else(|| crate_not_found(&name))?;
let krate = path.load_crate(&mut conn).await?;

// Check that the user is an owner of the crate (team owners are not allowed to delete crates)
let user = auth.user();
Expand Down Expand Up @@ -106,7 +102,7 @@ pub async fn delete_crate(

let git_index_job = jobs::SyncToGitIndex::new(&krate.name);
let sparse_index_job = jobs::SyncToSparseIndex::new(&krate.name);
let delete_from_storage_job = jobs::DeleteCrateFromStorage::new(name);
let delete_from_storage_job = jobs::DeleteCrateFromStorage::new(path.name);

tokio::try_join!(
git_index_job.enqueue(conn),
Expand All @@ -123,10 +119,6 @@ pub async fn delete_crate(
Ok(StatusCode::NO_CONTENT)
}

async fn find_crate(conn: &mut AsyncPgConnection, name: &str) -> QueryResult<Option<Crate>> {
Crate::by_name(name).first(conn).await.optional()
}

async fn get_crate_downloads(conn: &mut AsyncPgConnection, crate_id: i32) -> QueryResult<u64> {
let downloads = crate_downloads::table
.find(crate_id)
Expand Down
21 changes: 7 additions & 14 deletions src/controllers/krate/downloads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
//! download counts are located in `version::downloads`.

use crate::app::AppState;
use crate::models::{Crate, Version, VersionDownload};
use crate::schema::{crates, version_downloads, versions};
use crate::controllers::krate::CratePath;
use crate::models::{Version, VersionDownload};
use crate::schema::{version_downloads, versions};
use crate::sql::to_char;
use crate::util::errors::{crate_not_found, AppResult};
use crate::util::errors::AppResult;
use crate::views::EncodableVersionDownload;
use axum::extract::Path;
use axum_extra::json;
use axum_extra::response::ErasedJson;
use diesel::prelude::*;
Expand All @@ -23,25 +23,18 @@ use std::cmp;
#[utoipa::path(
get,
path = "/api/v1/crates/{name}/downloads",
params(CratePath),
tag = "crates",
responses((status = 200, description = "Successful Response")),
)]

pub async fn get_crate_downloads(
state: AppState,
Path(crate_name): Path<String>,
) -> AppResult<ErasedJson> {
pub async fn get_crate_downloads(state: AppState, path: CratePath) -> AppResult<ErasedJson> {
let mut conn = state.db_read().await?;

use diesel::dsl::*;
use diesel::sql_types::BigInt;

let crate_id: i32 = Crate::by_name(&crate_name)
.select(crates::id)
.first(&mut conn)
.await
.optional()?
.ok_or_else(|| crate_not_found(&crate_name))?;
let crate_id: i32 = path.load_crate_id(&mut conn).await?;

let mut versions: Vec<Version> = versions::table
.filter(versions::crate_id.eq(crate_id))
Expand Down
25 changes: 10 additions & 15 deletions src/controllers/krate/follow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
use crate::app::AppState;
use crate::auth::AuthCheck;
use crate::controllers::helpers::ok_true;
use crate::controllers::krate::CratePath;
use crate::models::{Crate, Follow};
use crate::schema::*;
use crate::util::errors::{crate_not_found, AppResult};
use axum::extract::Path;
use axum::response::Response;
use axum_extra::json;
use axum_extra::response::ErasedJson;
Expand All @@ -33,17 +33,14 @@ async fn follow_target(
#[utoipa::path(
put,
path = "/api/v1/crates/{name}/follow",
params(CratePath),
tag = "crates",
responses((status = 200, description = "Successful Response")),
)]
pub async fn follow_crate(
app: AppState,
Path(crate_name): Path<String>,
req: Parts,
) -> AppResult<Response> {
pub async fn follow_crate(app: AppState, path: CratePath, req: Parts) -> AppResult<Response> {
let mut conn = app.db_write().await?;
let user_id = AuthCheck::default().check(&req, &mut conn).await?.user_id();
let follow = follow_target(&crate_name, &mut conn, user_id).await?;
let follow = follow_target(&path.name, &mut conn, user_id).await?;
diesel::insert_into(follows::table)
.values(&follow)
.on_conflict_do_nothing()
Expand All @@ -57,17 +54,14 @@ pub async fn follow_crate(
#[utoipa::path(
delete,
path = "/api/v1/crates/{name}/follow",
params(CratePath),
tag = "crates",
responses((status = 200, description = "Successful Response")),
)]
pub async fn unfollow_crate(
app: AppState,
Path(crate_name): Path<String>,
req: Parts,
) -> AppResult<Response> {
pub async fn unfollow_crate(app: AppState, path: CratePath, req: Parts) -> AppResult<Response> {
let mut conn = app.db_write().await?;
let user_id = AuthCheck::default().check(&req, &mut conn).await?.user_id();
let follow = follow_target(&crate_name, &mut conn, user_id).await?;
let follow = follow_target(&path.name, &mut conn, user_id).await?;
diesel::delete(&follow).execute(&mut conn).await?;

ok_true()
Expand All @@ -77,12 +71,13 @@ pub async fn unfollow_crate(
#[utoipa::path(
get,
path = "/api/v1/crates/{name}/following",
params(CratePath),
tag = "crates",
responses((status = 200, description = "Successful Response")),
)]
pub async fn get_following_crate(
app: AppState,
Path(crate_name): Path<String>,
path: CratePath,
req: Parts,
) -> AppResult<ErasedJson> {
use diesel::dsl::exists;
Expand All @@ -93,7 +88,7 @@ pub async fn get_following_crate(
.await?
.user_id();

let follow = follow_target(&crate_name, &mut conn, user_id).await?;
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)
.await?;
Expand Down
35 changes: 14 additions & 21 deletions src/controllers/krate/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use crate::app::AppState;
use crate::controllers::helpers::pagination::PaginationOptions;
use crate::controllers::krate::CratePath;
use crate::controllers::version::CrateVersionPath;
use crate::models::{
Category, Crate, CrateCategory, CrateKeyword, CrateName, Keyword, RecentCrateDownloads, User,
Version, VersionOwnerAction,
Expand All @@ -16,7 +18,6 @@ use crate::util::{redirect, RequestUtils};
use crate::views::{
EncodableCategory, EncodableCrate, EncodableDependency, EncodableKeyword, EncodableVersion,
};
use axum::extract::Path;
use axum::response::{IntoResponse, Response};
use axum_extra::json;
use axum_extra::response::ErasedJson;
Expand All @@ -37,21 +38,19 @@ use std::str::FromStr;
responses((status = 200, description = "Successful Response")),
)]
pub async fn find_new_crate(app: AppState, req: Parts) -> AppResult<ErasedJson> {
find_crate(app, Path("new".to_string()), req).await
let name = "new".to_string();
find_crate(app, CratePath { name }, req).await
}

/// Get crate metadata.
#[utoipa::path(
get,
path = "/api/v1/crates/{name}",
params(CratePath),
tag = "crates",
responses((status = 200, description = "Successful Response")),
)]
pub async fn find_crate(
app: AppState,
Path(name): Path<String>,
req: Parts,
) -> AppResult<ErasedJson> {
pub async fn find_crate(app: AppState, path: CratePath, req: Parts) -> AppResult<ErasedJson> {
let mut conn = app.db_read().await?;

let include = req
Expand All @@ -62,7 +61,7 @@ pub async fn find_crate(
.unwrap_or_default();

let (krate, downloads, default_version, yanked): (Crate, i64, Option<String>, Option<bool>) =
Crate::by_name(&name)
Crate::by_name(&path.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 @@ -75,7 +74,7 @@ pub async fn find_crate(
.first(&mut conn)
.await
.optional()?
.ok_or_else(|| crate_not_found(&name))?;
.ok_or_else(|| crate_not_found(&path.name))?;

let versions_publishers_and_audit_actions = if include.versions {
let mut versions_and_publishers: Vec<(Version, Option<User>)> =
Expand Down Expand Up @@ -250,15 +249,12 @@ impl FromStr for ShowIncludeMode {
#[utoipa::path(
get,
path = "/api/v1/crates/{name}/{version}/readme",
params(CrateVersionPath),
tag = "versions",
responses((status = 200, description = "Successful Response")),
)]
pub async fn get_version_readme(
app: AppState,
Path((crate_name, version)): Path<(String, String)>,
req: Parts,
) -> Response {
let redirect_url = app.storage.readme_location(&crate_name, &version);
pub async fn get_version_readme(app: AppState, path: CrateVersionPath, req: Parts) -> Response {
let redirect_url = app.storage.readme_location(&path.name, &path.version);
if req.wants_json() {
json!({ "url": redirect_url }).into_response()
} else {
Expand All @@ -270,23 +266,20 @@ pub async fn get_version_readme(
#[utoipa::path(
get,
path = "/api/v1/crates/{name}/reverse_dependencies",
params(CratePath),
tag = "crates",
responses((status = 200, description = "Successful Response")),
)]
pub async fn list_reverse_dependencies(
app: AppState,
Path(name): Path<String>,
path: CratePath,
req: Parts,
) -> AppResult<ErasedJson> {
let mut conn = app.db_read().await?;

let pagination_options = PaginationOptions::builder().gather(&req)?;

let krate: Crate = Crate::by_name(&name)
.first(&mut conn)
.await
.optional()?
.ok_or_else(|| crate_not_found(&name))?;
let krate = path.load_crate(&mut conn).await?;

let (rev_deps, total) = krate
.reverse_dependencies(&mut conn, pagination_options)
Expand Down
Loading