From 82aa90f3640ef1d31909e1b52b16a034f15a8922 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 10:23:54 +0100 Subject: [PATCH 01/43] utoipa: Add annotations to `/crates/new` endpoints --- src/controllers/krate/metadata.rs | 12 +++++++- src/controllers/krate/publish.rs | 10 ++++++- src/router.rs | 13 +++------ ..._io__openapi__tests__openapi_snapshot.snap | 28 +++++++++++++++++++ 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/controllers/krate/metadata.rs b/src/controllers/krate/metadata.rs index 8be893ba88..0aabfe6009 100644 --- a/src/controllers/krate/metadata.rs +++ b/src/controllers/krate/metadata.rs @@ -26,7 +26,17 @@ use http::request::Parts; use std::cmp::Reverse; use std::str::FromStr; -/// Handles the `GET /crates/new` special case. +/// Get crate metadata (for the `new` crate). +/// +/// This endpoint works around a small limitation in `axum` and is delegating +/// to the `GET /api/v1/crates/{name}` endpoint internally. +#[utoipa::path( + get, + path = "/api/v1/crates/new", + operation_id = "crates_show_new", + tag = "crates", + responses((status = 200, description = "Successful Response")), +)] pub async fn show_new(app: AppState, req: Parts) -> AppResult { show(app, Path("new".to_string()), req).await } diff --git a/src/controllers/krate/publish.rs b/src/controllers/krate/publish.rs index fe938360d2..82a30e6ff0 100644 --- a/src/controllers/krate/publish.rs +++ b/src/controllers/krate/publish.rs @@ -49,9 +49,17 @@ const MISSING_RIGHTS_ERROR_MESSAGE: &str = "this crate exists but you don't seem const MAX_DESCRIPTION_LENGTH: usize = 1000; -/// Handles the `PUT /crates/new` route. +/// Publish a new crate/version. +/// /// Used by `cargo publish` to publish a new crate or to publish a new version of an /// existing crate. +#[utoipa::path( + put, + path = "/api/v1/crates/new", + operation_id = "publish", + tag = "publish", + responses((status = 200, description = "Successful Response")), +)] pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult> { let stream = body.into_data_stream(); let stream = stream.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)); diff --git a/src/router.rs b/src/router.rs index 1da715e710..05da516723 100644 --- a/src/router.rs +++ b/src/router.rs @@ -13,18 +13,13 @@ use crate::Env; pub fn build_axum_router(state: AppState) -> Router<()> { let (router, openapi) = BaseOpenApi::router() - .routes(routes!( - // Route used by both `cargo search` and the frontend - krate::search::search - )) + // Route used by both `cargo search` and the frontend + .routes(routes!(krate::search::search)) + // Routes used by `cargo` + .routes(routes!(krate::publish::publish, krate::metadata::show_new)) .split_for_parts(); let mut router = router - // Routes used by `cargo` - .route( - "/api/v1/crates/new", - put(krate::publish::publish).get(krate::metadata::show_new), - ) .route( "/api/v1/crates/:crate_id/owners", get(krate::owners::owners) diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index bb539c3965..f6afb17fb0 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -34,6 +34,34 @@ snapshot_kind: text "crates" ] } + }, + "/api/v1/crates/new": { + "get": { + "description": "This endpoint works around a small limitation in `axum` and is delegating\nto the `GET /api/v1/crates/{name}` endpoint internally.", + "operationId": "crates_show_new", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Get crate metadata (for the `new` crate).", + "tags": [ + "crates" + ] + }, + "put": { + "description": "Used by `cargo publish` to publish a new crate or to publish a new version of an\nexisting crate.", + "operationId": "publish", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Publish a new crate/version.", + "tags": [ + "publish" + ] + } } }, "servers": [ From 08c6e4bdbc2694c1932f7935a923afbebd93c35b Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 10:36:55 +0100 Subject: [PATCH 02/43] utoipa: Add annotations to `/crates/{name}/owners` endpoints --- src/controllers/krate/owners.rs | 27 +++++++++++-- src/router.rs | 11 +++--- ..._io__openapi__tests__openapi_snapshot.snap | 38 +++++++++++++++++++ 3 files changed, 67 insertions(+), 9 deletions(-) diff --git a/src/controllers/krate/owners.rs b/src/controllers/krate/owners.rs index 05a3451a5a..bf50d05a94 100644 --- a/src/controllers/krate/owners.rs +++ b/src/controllers/krate/owners.rs @@ -17,7 +17,14 @@ use http::request::Parts; use http::StatusCode; use secrecy::{ExposeSecret, SecretString}; -/// Handles the `GET /crates/:crate_id/owners` route. +/// List crate owners. +#[utoipa::path( + get, + path = "/api/v1/crates/{name}/owners", + operation_id = "list_owners", + tag = "owners", + responses((status = 200, description = "Successful Response")), +)] pub async fn owners(state: AppState, Path(crate_name): Path) -> AppResult { let mut conn = state.db_read().await?; @@ -74,7 +81,14 @@ pub async fn owner_user(state: AppState, Path(crate_name): Path) -> AppR Ok(json!({ "users": owners })) } -/// Handles the `PUT /crates/:crate_id/owners` route. +/// Add crate owners. +#[utoipa::path( + put, + path = "/api/v1/crates/{name}/owners", + operation_id = "add_owners", + tag = "owners", + responses((status = 200, description = "Successful Response")), +)] pub async fn add_owners( app: AppState, Path(crate_name): Path, @@ -84,7 +98,14 @@ pub async fn add_owners( modify_owners(app, crate_name, parts, body, true).await } -/// Handles the `DELETE /crates/:crate_id/owners` route. +/// Remove crate owners. +#[utoipa::path( + delete, + path = "/api/v1/crates/{name}/owners", + operation_id = "delete_owners", + tag = "owners", + responses((status = 200, description = "Successful Response")), +)] pub async fn remove_owners( app: AppState, Path(crate_name): Path, diff --git a/src/router.rs b/src/router.rs index 05da516723..cffc590c2b 100644 --- a/src/router.rs +++ b/src/router.rs @@ -17,15 +17,14 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(krate::search::search)) // Routes used by `cargo` .routes(routes!(krate::publish::publish, krate::metadata::show_new)) + .routes(routes!( + krate::owners::owners, + krate::owners::add_owners, + krate::owners::remove_owners + )) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/owners", - get(krate::owners::owners) - .put(krate::owners::add_owners) - .delete(krate::owners::remove_owners), - ) .route( "/api/v1/crates/:crate_id/:version/yank", delete(version::yank::yank), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index f6afb17fb0..0c046fb233 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -62,6 +62,44 @@ snapshot_kind: text "publish" ] } + }, + "/api/v1/crates/{name}/owners": { + "delete": { + "operationId": "delete_owners", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Remove crate owners.", + "tags": [ + "owners" + ] + }, + "get": { + "operationId": "list_owners", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "List crate owners.", + "tags": [ + "owners" + ] + }, + "put": { + "operationId": "add_owners", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Add crate owners.", + "tags": [ + "owners" + ] + } } }, "servers": [ From 4acb47fccc6a1dfc075f68b02f33f0991b797169 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 10:41:21 +0100 Subject: [PATCH 03/43] utoipa: Add annotations to `/crates/{name}/{version}/yank` endpoints --- src/controllers/version/yank.rs | 13 +++++++++++-- src/router.rs | 5 +---- ...ates_io__openapi__tests__openapi_snapshot.snap | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/controllers/version/yank.rs b/src/controllers/version/yank.rs index c9e18f6507..8498c58e62 100644 --- a/src/controllers/version/yank.rs +++ b/src/controllers/version/yank.rs @@ -10,15 +10,24 @@ use axum::extract::Path; use axum::response::Response; use http::request::Parts; -/// Handles the `DELETE /crates/:crate_id/:version/yank` route. +/// Yank a crate version. +/// /// This does not delete a crate version, it makes the crate /// version accessible only to crates that already have a /// `Cargo.lock` containing this version. /// /// Notes: -/// Crate deletion is not implemented to avoid breaking builds, +/// +/// Version deletion is not implemented to avoid breaking builds, /// and the goal of yanking a crate is to prevent crates /// beginning to depend on the yanked crate version. +#[utoipa::path( + delete, + path = "/api/v1/crates/{name}/{version}/yank", + operation_id = "yank_version", + tag = "versions", + responses((status = 200, description = "Successful Response")), +)] pub async fn yank( app: AppState, Path((crate_name, version)): Path<(String, String)>, diff --git a/src/router.rs b/src/router.rs index cffc590c2b..efcb2d8b41 100644 --- a/src/router.rs +++ b/src/router.rs @@ -22,13 +22,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { krate::owners::add_owners, krate::owners::remove_owners )) + .routes(routes!(version::yank::yank)) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/:version/yank", - delete(version::yank::yank), - ) .route( "/api/v1/crates/:crate_id/:version/unyank", put(version::yank::unyank), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 0c046fb233..9c99af26ee 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -100,6 +100,21 @@ snapshot_kind: text "owners" ] } + }, + "/api/v1/crates/{name}/{version}/yank": { + "delete": { + "description": "This does not delete a crate version, it makes the crate\nversion accessible only to crates that already have a\n`Cargo.lock` containing this version.\n\nNotes:\n\nVersion deletion is not implemented to avoid breaking builds,\nand the goal of yanking a crate is to prevent crates\nbeginning to depend on the yanked crate version.", + "operationId": "yank_version", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Yank a crate version.", + "tags": [ + "versions" + ] + } } }, "servers": [ From fc5f2ce2a9c0600f3439b4f208c9cfc4eb2ba680 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 10:42:30 +0100 Subject: [PATCH 04/43] utoipa: Add annotations to `/crates/{name}/{version}/unyank` endpoints --- src/controllers/version/yank.rs | 9 ++++++++- src/router.rs | 5 +---- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/controllers/version/yank.rs b/src/controllers/version/yank.rs index 8498c58e62..06b8cac5cb 100644 --- a/src/controllers/version/yank.rs +++ b/src/controllers/version/yank.rs @@ -36,7 +36,14 @@ pub async fn yank( modify_yank(crate_name, version, app, req, true).await } -/// Handles the `PUT /crates/:crate_id/:version/unyank` route. +/// Unyank a crate version. +#[utoipa::path( + put, + path = "/api/v1/crates/{name}/{version}/unyank", + operation_id = "unyank_version", + tag = "versions", + responses((status = 200, description = "Successful Response")), +)] pub async fn unyank( app: AppState, Path((crate_name, version)): Path<(String, String)>, diff --git a/src/router.rs b/src/router.rs index efcb2d8b41..fe9abb6570 100644 --- a/src/router.rs +++ b/src/router.rs @@ -23,13 +23,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { krate::owners::remove_owners )) .routes(routes!(version::yank::yank)) + .routes(routes!(version::yank::unyank)) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/:version/unyank", - put(version::yank::unyank), - ) .route( "/api/v1/crates/:crate_id/:version/download", get(version::downloads::download), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 9c99af26ee..4a01b2c697 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -101,6 +101,20 @@ snapshot_kind: text ] } }, + "/api/v1/crates/{name}/{version}/unyank": { + "put": { + "operationId": "unyank_version", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Unyank a crate version.", + "tags": [ + "versions" + ] + } + }, "/api/v1/crates/{name}/{version}/yank": { "delete": { "description": "This does not delete a crate version, it makes the crate\nversion accessible only to crates that already have a\n`Cargo.lock` containing this version.\n\nNotes:\n\nVersion deletion is not implemented to avoid breaking builds,\nand the goal of yanking a crate is to prevent crates\nbeginning to depend on the yanked crate version.", From 9000960009bcfd89221c233a7cb4a96f8e7a9bb7 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 10:44:15 +0100 Subject: [PATCH 05/43] utoipa: Add annotations to `/crates/{name}/{version}/download` endpoints --- src/controllers/version/downloads.rs | 10 +++++++++- src/router.rs | 5 +---- ...ates_io__openapi__tests__openapi_snapshot.snap | 15 +++++++++++++++ src/tests/blocked_routes.rs | 2 +- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/controllers/version/downloads.rs b/src/controllers/version/downloads.rs index 28390e2b90..80a302c9f3 100644 --- a/src/controllers/version/downloads.rs +++ b/src/controllers/version/downloads.rs @@ -18,8 +18,16 @@ use diesel::prelude::*; use diesel_async::RunQueryDsl; use http::request::Parts; -/// Handles the `GET /crates/:crate_id/:version/download` route. +/// Download a crate version. +/// /// This returns a URL to the location where the crate is stored. +#[utoipa::path( + get, + path = "/api/v1/crates/{name}/{version}/download", + operation_id = "download_version", + tag = "versions", + responses((status = 200, description = "Successful Response")), +)] pub async fn download( app: AppState, Path((crate_name, version)): Path<(String, String)>, diff --git a/src/router.rs b/src/router.rs index fe9abb6570..3abafa285b 100644 --- a/src/router.rs +++ b/src/router.rs @@ -24,13 +24,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { )) .routes(routes!(version::yank::yank)) .routes(routes!(version::yank::unyank)) + .routes(routes!(version::downloads::download)) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/:version/download", - get(version::downloads::download), - ) // Routes used by the frontend .route( "/api/v1/crates/:crate_id", diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 4a01b2c697..0e39e46060 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -101,6 +101,21 @@ snapshot_kind: text ] } }, + "/api/v1/crates/{name}/{version}/download": { + "get": { + "description": "This returns a URL to the location where the crate is stored.", + "operationId": "download_version", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Download a crate version.", + "tags": [ + "versions" + ] + } + }, "/api/v1/crates/{name}/{version}/unyank": { "put": { "operationId": "unyank_version", diff --git a/src/tests/blocked_routes.rs b/src/tests/blocked_routes.rs index 7c1e31efdd..bca35c9360 100644 --- a/src/tests/blocked_routes.rs +++ b/src/tests/blocked_routes.rs @@ -32,7 +32,7 @@ async fn test_blocked_download_route() { config.blocked_routes.clear(); config .blocked_routes - .insert("/api/v1/crates/:crate_id/:version/download".into()); + .insert("/api/v1/crates/:name/:version/download".into()); }) .with_user() .await; From ea9b50e07f8b7c848a2f7815a136197c1dc377c0 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 10:50:00 +0100 Subject: [PATCH 06/43] utoipa: Add annotations to `/crates/{name}` endpoints --- src/controllers/krate.rs | 4 +-- src/controllers/krate/delete.rs | 12 ++++++++- src/controllers/krate/metadata.rs | 9 ++++++- src/router.rs | 7 ++--- ..._io__openapi__tests__openapi_snapshot.snap | 27 +++++++++++++++++++ 5 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/controllers/krate.rs b/src/controllers/krate.rs index 93ee59d479..ca1cd37cdd 100644 --- a/src/controllers/krate.rs +++ b/src/controllers/krate.rs @@ -1,4 +1,4 @@ -mod delete; +pub mod delete; pub mod downloads; pub mod follow; pub mod metadata; @@ -6,5 +6,3 @@ pub mod owners; pub mod publish; pub mod search; pub mod versions; - -pub use delete::delete; diff --git a/src/controllers/krate/delete.rs b/src/controllers/krate/delete.rs index 55bd9db4b3..b08e464206 100644 --- a/src/controllers/krate/delete.rs +++ b/src/controllers/krate/delete.rs @@ -18,12 +18,22 @@ use http::StatusCode; const DOWNLOADS_PER_MONTH_LIMIT: u64 = 100; const AVAILABLE_AFTER: TimeDelta = TimeDelta::hours(24); -/// Deletes a crate from the database, index and storage. +/// Delete a crate. +/// +/// The crate is immediately deleted from the database, and with a small delay +/// from the git and sparse index, and the crate file storage. /// /// The crate can only be deleted by the owner of the crate, and only if the /// crate has been published for less than 72 hours, or if the crate has a /// single owner, has been downloaded less than 100 times for each month it has /// been published, and is not depended upon by any other crate on crates.io. +#[utoipa::path( + delete, + path = "/api/v1/crates/{name}", + operation_id = "delete_crate", + tag = "crates", + responses((status = 200, description = "Successful Response")), +)] pub async fn delete( Path(name): Path, parts: Parts, diff --git a/src/controllers/krate/metadata.rs b/src/controllers/krate/metadata.rs index 0aabfe6009..9f86e4f583 100644 --- a/src/controllers/krate/metadata.rs +++ b/src/controllers/krate/metadata.rs @@ -41,7 +41,14 @@ pub async fn show_new(app: AppState, req: Parts) -> AppResult { show(app, Path("new".to_string()), req).await } -/// Handles the `GET /crates/:crate_id` route. +/// Get crate metadata. +#[utoipa::path( + get, + path = "/api/v1/crates/{name}", + operation_id = "get_crate", + tag = "crates", + responses((status = 200, description = "Successful Response")), +)] pub async fn show(app: AppState, Path(name): Path, req: Parts) -> AppResult { let mut conn = app.db_read().await?; diff --git a/src/router.rs b/src/router.rs index 3abafa285b..826fc47273 100644 --- a/src/router.rs +++ b/src/router.rs @@ -25,14 +25,11 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(version::yank::yank)) .routes(routes!(version::yank::unyank)) .routes(routes!(version::downloads::download)) + // Routes used by the frontend + .routes(routes!(krate::metadata::show, krate::delete::delete)) .split_for_parts(); let mut router = router - // Routes used by the frontend - .route( - "/api/v1/crates/:crate_id", - get(krate::metadata::show).delete(krate::delete), - ) .route( "/api/v1/crates/:crate_id/:version", get(version::metadata::show).patch(version::metadata::update), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 0e39e46060..99ecdf12cc 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -63,6 +63,33 @@ snapshot_kind: text ] } }, + "/api/v1/crates/{name}": { + "delete": { + "description": "The crate is immediately deleted from the database, and with a small delay\nfrom the git and sparse index, and the crate file storage.\n\nThe crate can only be deleted by the owner of the crate, and only if the\ncrate has been published for less than 72 hours, or if the crate has a\nsingle owner, has been downloaded less than 100 times for each month it has\nbeen published, and is not depended upon by any other crate on crates.io.", + "operationId": "delete_crate", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Delete a crate.", + "tags": [ + "crates" + ] + }, + "get": { + "operationId": "get_crate", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Get crate metadata.", + "tags": [ + "crates" + ] + } + }, "/api/v1/crates/{name}/owners": { "delete": { "operationId": "delete_owners", From 9d0a123342bbc8dcf8b6df1926c1a6bbf4610dc0 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 10:56:42 +0100 Subject: [PATCH 07/43] utoipa: Add annotations to `/crates/{name}/{version}` endpoints --- src/controllers/version/metadata.rs | 23 +++++++++++----- src/router.rs | 5 +--- ..._io__openapi__tests__openapi_snapshot.snap | 27 +++++++++++++++++++ 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/controllers/version/metadata.rs b/src/controllers/version/metadata.rs index 61130b80bf..76b87687ff 100644 --- a/src/controllers/version/metadata.rs +++ b/src/controllers/version/metadata.rs @@ -82,10 +82,14 @@ pub async fn authors() -> ErasedJson { }) } -/// Handles the `GET /crates/:crate/:version` route. -/// -/// The frontend doesn't appear to hit this endpoint, but our tests do, and it seems to be a useful -/// API route to have. +/// Get crate version metadata. +#[utoipa::path( + get, + path = "/api/v1/crates/{name}/{version}", + operation_id = "get_version", + tag = "versions", + responses((status = 200, description = "Successful Response")), +)] pub async fn show( state: AppState, Path((crate_name, version)): Path<(String, String)>, @@ -103,9 +107,16 @@ pub async fn show( Ok(json!({ "version": version })) } -/// Handles the `PATCH /crates/:crate/:version` route. +/// Update a crate version. /// -/// This endpoint allows updating the yanked state of a version, including a yank message. +/// This endpoint allows updating the `yanked` state of a version, including a yank message. +#[utoipa::path( + patch, + path = "/api/v1/crates/{name}/{version}", + operation_id = "update_version", + tag = "versions", + responses((status = 200, description = "Successful Response")), +)] pub async fn update( state: AppState, Path((crate_name, version)): Path<(String, String)>, diff --git a/src/router.rs b/src/router.rs index 826fc47273..ab42daa138 100644 --- a/src/router.rs +++ b/src/router.rs @@ -27,13 +27,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(version::downloads::download)) // Routes used by the frontend .routes(routes!(krate::metadata::show, krate::delete::delete)) + .routes(routes!(version::metadata::show, version::metadata::update)) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/:version", - get(version::metadata::show).patch(version::metadata::update), - ) .route( "/api/v1/crates/:crate_id/:version/readme", get(krate::metadata::readme), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 99ecdf12cc..628103ed84 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -128,6 +128,33 @@ snapshot_kind: text ] } }, + "/api/v1/crates/{name}/{version}": { + "get": { + "operationId": "get_version", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Get crate version metadata.", + "tags": [ + "versions" + ] + }, + "patch": { + "description": "This endpoint allows updating the `yanked` state of a version, including a yank message.", + "operationId": "update_version", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Update a crate version.", + "tags": [ + "versions" + ] + } + }, "/api/v1/crates/{name}/{version}/download": { "get": { "description": "This returns a URL to the location where the crate is stored.", From f79f4d52a38109186e5d9e13ec8ea129d03dba59 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 10:58:51 +0100 Subject: [PATCH 08/43] utoipa: Add annotations to `/crates/{name}/{version}/readme` endpoints --- src/controllers/krate/metadata.rs | 9 ++++++++- src/router.rs | 5 +---- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/controllers/krate/metadata.rs b/src/controllers/krate/metadata.rs index 9f86e4f583..ee38beb769 100644 --- a/src/controllers/krate/metadata.rs +++ b/src/controllers/krate/metadata.rs @@ -244,7 +244,14 @@ impl FromStr for ShowIncludeMode { } } -/// Handles the `GET /crates/:crate_id/:version/readme` route. +/// Get the readme of a crate version. +#[utoipa::path( + get, + path = "/api/v1/crates/{name}/{version}/readme", + operation_id = "get_version_readme", + tag = "versions", + responses((status = 200, description = "Successful Response")), +)] pub async fn readme( app: AppState, Path((crate_name, version)): Path<(String, String)>, diff --git a/src/router.rs b/src/router.rs index ab42daa138..fa136a2022 100644 --- a/src/router.rs +++ b/src/router.rs @@ -28,13 +28,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { // Routes used by the frontend .routes(routes!(krate::metadata::show, krate::delete::delete)) .routes(routes!(version::metadata::show, version::metadata::update)) + .routes(routes!(krate::metadata::readme)) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/:version/readme", - get(krate::metadata::readme), - ) .route( "/api/v1/crates/:crate_id/:version/dependencies", get(version::metadata::dependencies), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 628103ed84..182fdad19e 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -170,6 +170,20 @@ snapshot_kind: text ] } }, + "/api/v1/crates/{name}/{version}/readme": { + "get": { + "operationId": "get_version_readme", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Get the readme of a crate version.", + "tags": [ + "versions" + ] + } + }, "/api/v1/crates/{name}/{version}/unyank": { "put": { "operationId": "unyank_version", From 5eb53e37a8b530bac93f487068651823dcd4a22b Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 11:01:12 +0100 Subject: [PATCH 09/43] utoipa: Add annotations to `/crates/{name}/{version}/dependencies` endpoints --- src/controllers/version/metadata.rs | 11 +++++++++-- src/router.rs | 5 +---- ...ates_io__openapi__tests__openapi_snapshot.snap | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/controllers/version/metadata.rs b/src/controllers/version/metadata.rs index 76b87687ff..2b2a2fd146 100644 --- a/src/controllers/version/metadata.rs +++ b/src/controllers/version/metadata.rs @@ -40,13 +40,20 @@ pub struct VersionUpdateRequest { version: VersionUpdate, } -/// Handles the `GET /crates/:crate_id/:version/dependencies` route. +/// Get crate version dependencies. /// -/// This information can be obtained directly from the index. +/// This information can also be obtained directly from the index. /// /// In addition to returning cached data from the index, this returns /// fields for `id`, `version_id`, and `downloads` (which appears to always /// be 0) +#[utoipa::path( + get, + path = "/api/v1/crates/{name}/{version}/dependencies", + operation_id = "get_version_dependencies", + tag = "versions", + responses((status = 200, description = "Successful Response")), +)] pub async fn dependencies( state: AppState, Path((crate_name, version)): Path<(String, String)>, diff --git a/src/router.rs b/src/router.rs index fa136a2022..cc9dd4655f 100644 --- a/src/router.rs +++ b/src/router.rs @@ -29,13 +29,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(krate::metadata::show, krate::delete::delete)) .routes(routes!(version::metadata::show, version::metadata::update)) .routes(routes!(krate::metadata::readme)) + .routes(routes!(version::metadata::dependencies)) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/:version/dependencies", - get(version::metadata::dependencies), - ) .route( "/api/v1/crates/:crate_id/:version/downloads", get(version::downloads::downloads), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 182fdad19e..a56b21b474 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -155,6 +155,21 @@ snapshot_kind: text ] } }, + "/api/v1/crates/{name}/{version}/dependencies": { + "get": { + "description": "This information can also be obtained directly from the index.\n\nIn addition to returning cached data from the index, this returns\nfields for `id`, `version_id`, and `downloads` (which appears to always\nbe 0)", + "operationId": "get_version_dependencies", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Get crate version dependencies.", + "tags": [ + "versions" + ] + } + }, "/api/v1/crates/{name}/{version}/download": { "get": { "description": "This returns a URL to the location where the crate is stored.", From 96ff1663a856c878e24829f75303b8afc60ac150 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 11:03:09 +0100 Subject: [PATCH 10/43] utoipa: Add annotations to `/crates/{name}/{version}/downloads` endpoints --- src/controllers/version/downloads.rs | 11 ++++++++++- src/router.rs | 5 +---- ...ates_io__openapi__tests__openapi_snapshot.snap | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/controllers/version/downloads.rs b/src/controllers/version/downloads.rs index 80a302c9f3..92841325a5 100644 --- a/src/controllers/version/downloads.rs +++ b/src/controllers/version/downloads.rs @@ -42,7 +42,16 @@ pub async fn download( } } -/// Handles the `GET /crates/:crate_id/:version/downloads` route. +/// Get the download counts for a crate version. +/// +/// This includes the per-day downloads for the last 90 days. +#[utoipa::path( + get, + path = "/api/v1/crates/{name}/{version}/downloads", + operation_id = "get_version_downloads", + tag = "versions", + responses((status = 200, description = "Successful Response")), +)] pub async fn downloads( app: AppState, Path((crate_name, version)): Path<(String, String)>, diff --git a/src/router.rs b/src/router.rs index cc9dd4655f..45fdaf6a0d 100644 --- a/src/router.rs +++ b/src/router.rs @@ -30,13 +30,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(version::metadata::show, version::metadata::update)) .routes(routes!(krate::metadata::readme)) .routes(routes!(version::metadata::dependencies)) + .routes(routes!(version::downloads::downloads)) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/:version/downloads", - get(version::downloads::downloads), - ) .route( "/api/v1/crates/:crate_id/:version/authors", get(version::metadata::authors), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index a56b21b474..fe757a8172 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -185,6 +185,21 @@ snapshot_kind: text ] } }, + "/api/v1/crates/{name}/{version}/downloads": { + "get": { + "description": "This includes the per-day downloads for the last 90 days.", + "operationId": "get_version_downloads", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Get the download counts for a crate version.", + "tags": [ + "versions" + ] + } + }, "/api/v1/crates/{name}/{version}/readme": { "get": { "operationId": "get_version_readme", From f19c6f471bba72a64c5809dbbfa7a8683ba3aaf4 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 16:49:00 +0100 Subject: [PATCH 11/43] utoipa: Add annotations to `/crates/{name}/{version}/authors` endpoints --- src/controllers/version/metadata.rs | 16 ++++++++++++---- src/router.rs | 6 ++---- ...tes_io__openapi__tests__openapi_snapshot.snap | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/controllers/version/metadata.rs b/src/controllers/version/metadata.rs index 2b2a2fd146..01f15fa788 100644 --- a/src/controllers/version/metadata.rs +++ b/src/controllers/version/metadata.rs @@ -78,11 +78,19 @@ pub async fn dependencies( Ok(json!({ "dependencies": deps })) } -/// Handles the `GET /crates/:crate_id/:version/authors` route. +/// Get crate version authors. +/// +/// This endpoint was deprecated by [RFC #3052](https://github.com/rust-lang/rfcs/pull/3052) +/// and returns an empty list for backwards compatibility reasons. +#[utoipa::path( + get, + path = "/api/v1/crates/{name}/{version}/authors", + operation_id = "get_version_authors", + tag = "versions", + responses((status = 200, description = "Successful Response")), +)] +#[deprecated] pub async fn authors() -> ErasedJson { - // Currently we return the empty list. - // Because the API is not used anymore after RFC https://github.com/rust-lang/rfcs/pull/3052. - json!({ "users": [], "meta": { "names": [] }, diff --git a/src/router.rs b/src/router.rs index 45fdaf6a0d..7608f36978 100644 --- a/src/router.rs +++ b/src/router.rs @@ -11,6 +11,7 @@ use crate::openapi::BaseOpenApi; use crate::util::errors::not_found; use crate::Env; +#[allow(deprecated)] pub fn build_axum_router(state: AppState) -> Router<()> { let (router, openapi) = BaseOpenApi::router() // Route used by both `cargo search` and the frontend @@ -31,13 +32,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(krate::metadata::readme)) .routes(routes!(version::metadata::dependencies)) .routes(routes!(version::downloads::downloads)) + .routes(routes!(version::metadata::authors)) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/:version/authors", - get(version::metadata::authors), - ) .route( "/api/v1/crates/:crate_id/downloads", get(krate::downloads::downloads), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index fe757a8172..f1c488fd4d 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -155,6 +155,22 @@ snapshot_kind: text ] } }, + "/api/v1/crates/{name}/{version}/authors": { + "get": { + "deprecated": true, + "description": "This endpoint was deprecated by [RFC #3052](https://github.com/rust-lang/rfcs/pull/3052)\nand returns an empty list for backwards compatibility reasons.", + "operationId": "get_version_authors", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Get crate version authors.", + "tags": [ + "versions" + ] + } + }, "/api/v1/crates/{name}/{version}/dependencies": { "get": { "description": "This information can also be obtained directly from the index.\n\nIn addition to returning cached data from the index, this returns\nfields for `id`, `version_id`, and `downloads` (which appears to always\nbe 0)", From 24090fd50048e0d7bdba0256e59f539194499d65 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 16:52:13 +0100 Subject: [PATCH 12/43] utoipa: Add annotations to `/crates/{name}/downloads` endpoints --- src/controllers/krate/downloads.rs | 13 ++++++++++++- src/router.rs | 5 +---- ...ates_io__openapi__tests__openapi_snapshot.snap | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/controllers/krate/downloads.rs b/src/controllers/krate/downloads.rs index 3ff2dbba87..3d731aa058 100644 --- a/src/controllers/krate/downloads.rs +++ b/src/controllers/krate/downloads.rs @@ -16,7 +16,18 @@ use diesel::prelude::*; use diesel_async::RunQueryDsl; use std::cmp; -/// Handles the `GET /crates/:crate_id/downloads` route. +/// Get the download counts for a crate. +/// +/// This includes the per-day downloads for the last 90 days and for the +/// latest 5 versions plus the sum of the rest. +#[utoipa::path( + get, + path = "/api/v1/crates/{name}/downloads", + operation_id = "get_crate_downloads", + tag = "crates", + responses((status = 200, description = "Successful Response")), +)] + pub async fn downloads(state: AppState, Path(crate_name): Path) -> AppResult { let mut conn = state.db_read().await?; diff --git a/src/router.rs b/src/router.rs index 7608f36978..5a52c931cf 100644 --- a/src/router.rs +++ b/src/router.rs @@ -33,13 +33,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(version::metadata::dependencies)) .routes(routes!(version::downloads::downloads)) .routes(routes!(version::metadata::authors)) + .routes(routes!(krate::downloads::downloads)) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/downloads", - get(krate::downloads::downloads), - ) .route( "/api/v1/crates/:crate_id/versions", get(krate::versions::versions), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index f1c488fd4d..9da83f927d 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -90,6 +90,21 @@ snapshot_kind: text ] } }, + "/api/v1/crates/{name}/downloads": { + "get": { + "description": "This includes the per-day downloads for the last 90 days and for the\nlatest 5 versions plus the sum of the rest.", + "operationId": "get_crate_downloads", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Get the download counts for a crate.", + "tags": [ + "crates" + ] + } + }, "/api/v1/crates/{name}/owners": { "delete": { "operationId": "delete_owners", From 5c1d82db572b02fbdbaa8d1c799fc503ab516835 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 16:54:45 +0100 Subject: [PATCH 13/43] utoipa: Add annotations to `/crates/{name}/versions` endpoints --- src/controllers/krate/versions.rs | 9 ++++++++- src/router.rs | 5 +---- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/controllers/krate/versions.rs b/src/controllers/krate/versions.rs index 4376101612..5dd370a241 100644 --- a/src/controllers/krate/versions.rs +++ b/src/controllers/krate/versions.rs @@ -20,7 +20,14 @@ use crate::util::errors::{bad_request, crate_not_found, AppResult, BoxedAppError use crate::util::RequestUtils; use crate::views::EncodableVersion; -/// Handles the `GET /crates/:crate_id/versions` route. +/// List all versions of a crate. +#[utoipa::path( + get, + path = "/api/v1/crates/{name}/versions", + operation_id = "list_crate_versions", + tag = "versions", + responses((status = 200, description = "Successful Response")), +)] pub async fn versions( state: AppState, Path(crate_name): Path, diff --git a/src/router.rs b/src/router.rs index 5a52c931cf..5dc61cbf6c 100644 --- a/src/router.rs +++ b/src/router.rs @@ -34,13 +34,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(version::downloads::downloads)) .routes(routes!(version::metadata::authors)) .routes(routes!(krate::downloads::downloads)) + .routes(routes!(krate::versions::versions)) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/versions", - get(krate::versions::versions), - ) .route( "/api/v1/crates/:crate_id/follow", put(krate::follow::follow).delete(krate::follow::unfollow), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 9da83f927d..51c49744cb 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -143,6 +143,20 @@ snapshot_kind: text ] } }, + "/api/v1/crates/{name}/versions": { + "get": { + "operationId": "list_crate_versions", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "List all versions of a crate.", + "tags": [ + "versions" + ] + } + }, "/api/v1/crates/{name}/{version}": { "get": { "operationId": "get_version", From c9d9c29b5f0c9d5fa67e8ceb8abcb2dc917fb22c Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 16:57:31 +0100 Subject: [PATCH 14/43] utoipa: Add annotations to `/crates/{name}/follow` endpoints --- src/controllers/krate/follow.rs | 18 +++++++++++-- src/router.rs | 5 +--- ..._io__openapi__tests__openapi_snapshot.snap | 26 +++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/controllers/krate/follow.rs b/src/controllers/krate/follow.rs index d000a10c53..0c081544e1 100644 --- a/src/controllers/krate/follow.rs +++ b/src/controllers/krate/follow.rs @@ -29,7 +29,14 @@ async fn follow_target( Ok(Follow { user_id, crate_id }) } -/// Handles the `PUT /crates/:crate_id/follow` route. +/// Follow a crate. +#[utoipa::path( + put, + path = "/api/v1/crates/{name}/follow", + operation_id = "follow_crate", + tag = "crates", + responses((status = 200, description = "Successful Response")), +)] pub async fn follow( app: AppState, Path(crate_name): Path, @@ -47,7 +54,14 @@ pub async fn follow( ok_true() } -/// Handles the `DELETE /crates/:crate_id/follow` route. +/// Unfollow a crate. +#[utoipa::path( + delete, + path = "/api/v1/crates/{name}/follow", + operation_id = "unfollow_crate", + tag = "crates", + responses((status = 200, description = "Successful Response")), +)] pub async fn unfollow( app: AppState, Path(crate_name): Path, diff --git a/src/router.rs b/src/router.rs index 5dc61cbf6c..c3159670d3 100644 --- a/src/router.rs +++ b/src/router.rs @@ -35,13 +35,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(version::metadata::authors)) .routes(routes!(krate::downloads::downloads)) .routes(routes!(krate::versions::versions)) + .routes(routes!(krate::follow::follow, krate::follow::unfollow)) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/follow", - put(krate::follow::follow).delete(krate::follow::unfollow), - ) .route( "/api/v1/crates/:crate_id/following", get(krate::follow::following), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 51c49744cb..f4b5ec07de 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -105,6 +105,32 @@ snapshot_kind: text ] } }, + "/api/v1/crates/{name}/follow": { + "delete": { + "operationId": "unfollow_crate", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Unfollow a crate.", + "tags": [ + "crates" + ] + }, + "put": { + "operationId": "follow_crate", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Follow a crate.", + "tags": [ + "crates" + ] + } + }, "/api/v1/crates/{name}/owners": { "delete": { "operationId": "delete_owners", From b740a465452c4ff4ad48484af170687e6e40ee40 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:00:46 +0100 Subject: [PATCH 15/43] utoipa: Add annotations to `/crates/{name}/following` endpoints --- src/controllers/krate/follow.rs | 9 ++++++++- src/router.rs | 5 +---- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/controllers/krate/follow.rs b/src/controllers/krate/follow.rs index 0c081544e1..c0e2dc2985 100644 --- a/src/controllers/krate/follow.rs +++ b/src/controllers/krate/follow.rs @@ -75,7 +75,14 @@ pub async fn unfollow( ok_true() } -/// Handles the `GET /crates/:crate_id/following` route. +/// Check if a crate is followed. +#[utoipa::path( + get, + path = "/api/v1/crates/{name}/following", + operation_id = "get_following_crate", + tag = "crates", + responses((status = 200, description = "Successful Response")), +)] pub async fn following( app: AppState, Path(crate_name): Path, diff --git a/src/router.rs b/src/router.rs index c3159670d3..3ab4900ad2 100644 --- a/src/router.rs +++ b/src/router.rs @@ -36,13 +36,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(krate::downloads::downloads)) .routes(routes!(krate::versions::versions)) .routes(routes!(krate::follow::follow, krate::follow::unfollow)) + .routes(routes!(krate::follow::following)) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/following", - get(krate::follow::following), - ) .route( "/api/v1/crates/:crate_id/owner_team", get(krate::owners::owner_team), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index f4b5ec07de..b3282175ba 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -131,6 +131,20 @@ snapshot_kind: text ] } }, + "/api/v1/crates/{name}/following": { + "get": { + "operationId": "get_following_crate", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Check if a crate is followed.", + "tags": [ + "crates" + ] + } + }, "/api/v1/crates/{name}/owners": { "delete": { "operationId": "delete_owners", From 5fa4b1feb77509c1cc470a6324134440bf8ae61b Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:05:38 +0100 Subject: [PATCH 16/43] utoipa: Add annotations to `/crates/{name}/owner_team` endpoints --- src/controllers/krate/owners.rs | 9 ++++++++- src/router.rs | 5 +---- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/controllers/krate/owners.rs b/src/controllers/krate/owners.rs index bf50d05a94..1ecc07423a 100644 --- a/src/controllers/krate/owners.rs +++ b/src/controllers/krate/owners.rs @@ -44,7 +44,14 @@ pub async fn owners(state: AppState, Path(crate_name): Path) -> AppResul Ok(json!({ "users": owners })) } -/// Handles the `GET /crates/:crate_id/owner_team` route. +/// List team owners of a crate. +#[utoipa::path( + get, + path = "/api/v1/crates/{name}/owner_team", + operation_id = "get_team_owners", + tag = "owners", + responses((status = 200, description = "Successful Response")), +)] pub async fn owner_team(state: AppState, Path(crate_name): Path) -> AppResult { let mut conn = state.db_read().await?; let krate: Crate = Crate::by_name(&crate_name) diff --git a/src/router.rs b/src/router.rs index 3ab4900ad2..6c802bd901 100644 --- a/src/router.rs +++ b/src/router.rs @@ -37,13 +37,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(krate::versions::versions)) .routes(routes!(krate::follow::follow, krate::follow::unfollow)) .routes(routes!(krate::follow::following)) + .routes(routes!(krate::owners::owner_team)) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/owner_team", - get(krate::owners::owner_team), - ) .route( "/api/v1/crates/:crate_id/owner_user", get(krate::owners::owner_user), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index b3282175ba..24762a08a1 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -145,6 +145,20 @@ snapshot_kind: text ] } }, + "/api/v1/crates/{name}/owner_team": { + "get": { + "operationId": "get_team_owners", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "List team owners of a crate.", + "tags": [ + "owners" + ] + } + }, "/api/v1/crates/{name}/owners": { "delete": { "operationId": "delete_owners", From b8f8fef2219cfa3baa8550d714528ee63adf7217 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:07:15 +0100 Subject: [PATCH 17/43] utoipa: Add annotations to `/crates/{name}/owner_user` endpoints --- src/controllers/krate/owners.rs | 9 ++++++++- src/router.rs | 5 +---- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/controllers/krate/owners.rs b/src/controllers/krate/owners.rs index 1ecc07423a..6fdf2584f3 100644 --- a/src/controllers/krate/owners.rs +++ b/src/controllers/krate/owners.rs @@ -69,7 +69,14 @@ pub async fn owner_team(state: AppState, Path(crate_name): Path) -> AppR Ok(json!({ "teams": owners })) } -/// Handles the `GET /crates/:crate_id/owner_user` route. +/// List user owners of a crate. +#[utoipa::path( + get, + path = "/api/v1/crates/{name}/owner_user", + operation_id = "get_user_owners", + tag = "owners", + responses((status = 200, description = "Successful Response")), +)] pub async fn owner_user(state: AppState, Path(crate_name): Path) -> AppResult { let mut conn = state.db_read().await?; diff --git a/src/router.rs b/src/router.rs index 6c802bd901..d95ccab478 100644 --- a/src/router.rs +++ b/src/router.rs @@ -38,13 +38,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(krate::follow::follow, krate::follow::unfollow)) .routes(routes!(krate::follow::following)) .routes(routes!(krate::owners::owner_team)) + .routes(routes!(krate::owners::owner_user)) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/owner_user", - get(krate::owners::owner_user), - ) .route( "/api/v1/crates/:crate_id/reverse_dependencies", get(krate::metadata::reverse_dependencies), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 24762a08a1..06f55bd8e5 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -159,6 +159,20 @@ snapshot_kind: text ] } }, + "/api/v1/crates/{name}/owner_user": { + "get": { + "operationId": "get_user_owners", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "List user owners of a crate.", + "tags": [ + "owners" + ] + } + }, "/api/v1/crates/{name}/owners": { "delete": { "operationId": "delete_owners", From 5777f08afbcc4187831dfc02d3fb0ff06b8babea Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:08:59 +0100 Subject: [PATCH 18/43] utoipa: Add annotations to `/crates/{name}/reverse_dependencies` endpoints --- src/controllers/krate/metadata.rs | 9 ++++++++- src/router.rs | 5 +---- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/controllers/krate/metadata.rs b/src/controllers/krate/metadata.rs index ee38beb769..f0befa72aa 100644 --- a/src/controllers/krate/metadata.rs +++ b/src/controllers/krate/metadata.rs @@ -265,7 +265,14 @@ pub async fn readme( } } -/// Handles the `GET /crates/:crate_id/reverse_dependencies` route. +/// List reverse dependencies of a crate. +#[utoipa::path( + get, + path = "/api/v1/crates/{name}/reverse_dependencies", + operation_id = "list_reverse_dependencies", + tag = "crates", + responses((status = 200, description = "Successful Response")), +)] pub async fn reverse_dependencies( app: AppState, Path(name): Path, diff --git a/src/router.rs b/src/router.rs index d95ccab478..c87dd252c5 100644 --- a/src/router.rs +++ b/src/router.rs @@ -39,13 +39,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(krate::follow::following)) .routes(routes!(krate::owners::owner_team)) .routes(routes!(krate::owners::owner_user)) + .routes(routes!(krate::metadata::reverse_dependencies)) .split_for_parts(); let mut router = router - .route( - "/api/v1/crates/:crate_id/reverse_dependencies", - get(krate::metadata::reverse_dependencies), - ) .route("/api/v1/keywords", get(keyword::index)) .route("/api/v1/keywords/:keyword_id", get(keyword::show)) .route("/api/v1/categories", get(category::index)) diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 06f55bd8e5..00d35b5f50 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -211,6 +211,20 @@ snapshot_kind: text ] } }, + "/api/v1/crates/{name}/reverse_dependencies": { + "get": { + "operationId": "list_reverse_dependencies", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "List reverse dependencies of a crate.", + "tags": [ + "crates" + ] + } + }, "/api/v1/crates/{name}/versions": { "get": { "operationId": "list_crate_versions", From 8cf41e09bcde9c1291ff4f4a83839cb1de9b5bf7 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:10:08 +0100 Subject: [PATCH 19/43] utoipa: Add annotations to `/keywords` endpoints --- src/controllers/keyword.rs | 9 ++++++++- src/router.rs | 2 +- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/controllers/keyword.rs b/src/controllers/keyword.rs index 6843b72461..7ec5c36bd9 100644 --- a/src/controllers/keyword.rs +++ b/src/controllers/keyword.rs @@ -15,7 +15,14 @@ pub struct IndexQuery { sort: Option, } -/// Handles the `GET /keywords` route. +/// List all keywords. +#[utoipa::path( + get, + path = "/api/v1/keywords", + operation_id = "list_keywords", + tag = "keywords", + responses((status = 200, description = "Successful Response")), +)] pub async fn index(state: AppState, qp: Query, req: Parts) -> AppResult { use crate::schema::keywords; diff --git a/src/router.rs b/src/router.rs index c87dd252c5..686c334520 100644 --- a/src/router.rs +++ b/src/router.rs @@ -40,10 +40,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(krate::owners::owner_team)) .routes(routes!(krate::owners::owner_user)) .routes(routes!(krate::metadata::reverse_dependencies)) + .routes(routes!(keyword::index)) .split_for_parts(); let mut router = router - .route("/api/v1/keywords", get(keyword::index)) .route("/api/v1/keywords/:keyword_id", get(keyword::show)) .route("/api/v1/categories", get(category::index)) .route("/api/v1/categories/:category_id", get(category::show)) diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 00d35b5f50..0c14a45fe4 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -369,6 +369,20 @@ snapshot_kind: text "versions" ] } + }, + "/api/v1/keywords": { + "get": { + "operationId": "list_keywords", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "List all keywords.", + "tags": [ + "keywords" + ] + } } }, "servers": [ From 51150531cd9097427b975130411113ff6a0021a4 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:12:33 +0100 Subject: [PATCH 20/43] utoipa: Add annotations to `/keywords/{keyword}` endpoints --- src/controllers/keyword.rs | 9 ++++++++- src/router.rs | 2 +- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/controllers/keyword.rs b/src/controllers/keyword.rs index 7ec5c36bd9..c7c4efbfab 100644 --- a/src/controllers/keyword.rs +++ b/src/controllers/keyword.rs @@ -49,7 +49,14 @@ pub async fn index(state: AppState, qp: Query, req: Parts) -> AppRes })) } -/// Handles the `GET /keywords/:keyword_id` route. +/// Get keyword metadata. +#[utoipa::path( + get, + path = "/api/v1/keywords/{keyword}", + operation_id = "get_keyword", + tag = "keywords", + responses((status = 200, description = "Successful Response")), +)] pub async fn show(Path(name): Path, state: AppState) -> AppResult { let mut conn = state.db_read().await?; let kw = Keyword::find_by_keyword(&mut conn, &name).await?; diff --git a/src/router.rs b/src/router.rs index 686c334520..8d614d82c2 100644 --- a/src/router.rs +++ b/src/router.rs @@ -41,10 +41,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(krate::owners::owner_user)) .routes(routes!(krate::metadata::reverse_dependencies)) .routes(routes!(keyword::index)) + .routes(routes!(keyword::show)) .split_for_parts(); let mut router = router - .route("/api/v1/keywords/:keyword_id", get(keyword::show)) .route("/api/v1/categories", get(category::index)) .route("/api/v1/categories/:category_id", get(category::show)) .route("/api/v1/category_slugs", get(category::slugs)) diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 0c14a45fe4..879af7b63d 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -383,6 +383,20 @@ snapshot_kind: text "keywords" ] } + }, + "/api/v1/keywords/{keyword}": { + "get": { + "operationId": "get_keyword", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Get keyword metadata.", + "tags": [ + "keywords" + ] + } } }, "servers": [ From cb7c6869b7e9211dc2f91b5fc5fa69f2f2686931 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:19:25 +0100 Subject: [PATCH 21/43] utoipa: Add annotations to `/categories` endpoints --- src/controllers/category.rs | 9 ++++++++- src/router.rs | 2 +- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/controllers/category.rs b/src/controllers/category.rs index 6384fcf880..dc79127579 100644 --- a/src/controllers/category.rs +++ b/src/controllers/category.rs @@ -12,7 +12,14 @@ use diesel::QueryDsl; use diesel_async::RunQueryDsl; use http::request::Parts; -/// Handles the `GET /categories` route. +/// List all categories. +#[utoipa::path( + get, + path = "/api/v1/categories", + operation_id = "list_categories", + tag = "categories", + responses((status = 200, description = "Successful Response")), +)] pub async fn index(app: AppState, req: Parts) -> AppResult { // FIXME: There are 69 categories, 47 top level. This isn't going to // grow by an OoM. We need a limit for /summary, but we don't need diff --git a/src/router.rs b/src/router.rs index 8d614d82c2..46c3d9799f 100644 --- a/src/router.rs +++ b/src/router.rs @@ -42,10 +42,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(krate::metadata::reverse_dependencies)) .routes(routes!(keyword::index)) .routes(routes!(keyword::show)) + .routes(routes!(category::index)) .split_for_parts(); let mut router = router - .route("/api/v1/categories", get(category::index)) .route("/api/v1/categories/:category_id", get(category::show)) .route("/api/v1/category_slugs", get(category::slugs)) .route( diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 879af7b63d..0fa44a02e2 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -20,6 +20,20 @@ snapshot_kind: text }, "openapi": "3.1.0", "paths": { + "/api/v1/categories": { + "get": { + "operationId": "list_categories", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "List all categories.", + "tags": [ + "categories" + ] + } + }, "/api/v1/crates": { "get": { "description": "Called in a variety of scenarios in the front end, including:\n- Alphabetical listing of crates\n- List of crates under a specific owner\n- Listing a user's followed crates", From ec22ad15194547d06d63f73cd04361ba80bc779c Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:20:55 +0100 Subject: [PATCH 22/43] utoipa: Add annotations to `/categories/{category}` endpoints --- src/controllers/category.rs | 9 ++++++++- src/router.rs | 2 +- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/controllers/category.rs b/src/controllers/category.rs index dc79127579..c87030fa35 100644 --- a/src/controllers/category.rs +++ b/src/controllers/category.rs @@ -48,7 +48,14 @@ pub async fn index(app: AppState, req: Parts) -> AppResult { })) } -/// Handles the `GET /categories/:category_id` route. +/// Get category metadata. +#[utoipa::path( + get, + path = "/api/v1/categories/{category}", + operation_id = "get_category", + tag = "categories", + responses((status = 200, description = "Successful Response")), +)] pub async fn show(state: AppState, Path(slug): Path) -> AppResult { let mut conn = state.db_read().await?; diff --git a/src/router.rs b/src/router.rs index 46c3d9799f..1ebe7028e5 100644 --- a/src/router.rs +++ b/src/router.rs @@ -43,10 +43,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(keyword::index)) .routes(routes!(keyword::show)) .routes(routes!(category::index)) + .routes(routes!(category::show)) .split_for_parts(); let mut router = router - .route("/api/v1/categories/:category_id", get(category::show)) .route("/api/v1/category_slugs", get(category::slugs)) .route( "/api/v1/users/:user_id", diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 0fa44a02e2..8a2323916a 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -34,6 +34,20 @@ snapshot_kind: text ] } }, + "/api/v1/categories/{category}": { + "get": { + "operationId": "get_category", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Get category metadata.", + "tags": [ + "categories" + ] + } + }, "/api/v1/crates": { "get": { "description": "Called in a variety of scenarios in the front end, including:\n- Alphabetical listing of crates\n- List of crates under a specific owner\n- Listing a user's followed crates", From 42f64e42b39ee7325359d2d282973dab320f419a Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:22:15 +0100 Subject: [PATCH 23/43] utoipa: Add annotations to `/category_slugs` endpoints --- src/controllers/category.rs | 9 ++++++++- src/router.rs | 2 +- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/controllers/category.rs b/src/controllers/category.rs index c87030fa35..b7ea29d18b 100644 --- a/src/controllers/category.rs +++ b/src/controllers/category.rs @@ -88,7 +88,14 @@ pub async fn show(state: AppState, Path(slug): Path) -> AppResult AppResult { let mut conn = state.db_read().await?; diff --git a/src/router.rs b/src/router.rs index 1ebe7028e5..f7516355c7 100644 --- a/src/router.rs +++ b/src/router.rs @@ -44,10 +44,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(keyword::show)) .routes(routes!(category::index)) .routes(routes!(category::show)) + .routes(routes!(category::slugs)) .split_for_parts(); let mut router = router - .route("/api/v1/category_slugs", get(category::slugs)) .route( "/api/v1/users/:user_id", get(user::other::show).put(update_user), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 8a2323916a..259108b771 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -48,6 +48,20 @@ snapshot_kind: text ] } }, + "/api/v1/category_slugs": { + "get": { + "operationId": "list_category_slugs", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "List all available category slugs.", + "tags": [ + "categories" + ] + } + }, "/api/v1/crates": { "get": { "description": "Called in a variety of scenarios in the front end, including:\n- Alphabetical listing of crates\n- List of crates under a specific owner\n- Listing a user's followed crates", From ccba727653970ee68bd4083c2d7b6a4a056fea7a Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:28:36 +0100 Subject: [PATCH 24/43] utoipa: Add annotations to `/users/{user}` endpoints --- src/controllers/user/other.rs | 9 ++++++- src/controllers/user/update.rs | 13 ++++++++- src/router.rs | 6 +---- ..._io__openapi__tests__openapi_snapshot.snap | 27 +++++++++++++++++++ 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/controllers/user/other.rs b/src/controllers/user/other.rs index 8ba6459abb..d653c6414f 100644 --- a/src/controllers/user/other.rs +++ b/src/controllers/user/other.rs @@ -12,7 +12,14 @@ use crate::sql::lower; use crate::util::errors::AppResult; use crate::views::EncodablePublicUser; -/// Handles the `GET /users/:user_id` route. +/// Find user by login. +#[utoipa::path( + get, + path = "/api/v1/users/{user}", + operation_id = "get_user", + tag = "users", + responses((status = 200, description = "Successful Response")), +)] pub async fn show(state: AppState, Path(user_name): Path) -> AppResult { let mut conn = state.db_read_prefer_primary().await?; diff --git a/src/controllers/user/update.rs b/src/controllers/user/update.rs index 9c14a308e2..06e934ee7c 100644 --- a/src/controllers/user/update.rs +++ b/src/controllers/user/update.rs @@ -24,7 +24,18 @@ pub struct User { publish_notifications: Option, } -/// Handles the `PUT /users/:user_id` route. +/// Update user settings. +/// +/// This endpoint allows users to update their email address and publish notifications settings. +/// +/// The `id` parameter needs to match the ID of the currently authenticated user. +#[utoipa::path( + put, + path = "/api/v1/users/{user}", + operation_id = "update_user", + tag = "users", + responses((status = 200, description = "Successful Response")), +)] pub async fn update_user( state: AppState, Path(param_user_id): Path, diff --git a/src/router.rs b/src/router.rs index f7516355c7..639d81a0cd 100644 --- a/src/router.rs +++ b/src/router.rs @@ -5,7 +5,6 @@ use http::{Method, StatusCode}; use utoipa_axum::routes; use crate::app::AppState; -use crate::controllers::user::update_user; use crate::controllers::*; use crate::openapi::BaseOpenApi; use crate::util::errors::not_found; @@ -45,13 +44,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(category::index)) .routes(routes!(category::show)) .routes(routes!(category::slugs)) + .routes(routes!(user::other::show, user::update::update_user)) .split_for_parts(); let mut router = router - .route( - "/api/v1/users/:user_id", - get(user::other::show).put(update_user), - ) .route("/api/v1/users/:user_id/stats", get(user::other::stats)) .route("/api/v1/teams/:team_id", get(team::show_team)) .route("/api/v1/me", get(user::me::me)) diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 259108b771..84d8b58052 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -439,6 +439,33 @@ snapshot_kind: text "keywords" ] } + }, + "/api/v1/users/{user}": { + "get": { + "operationId": "get_user", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Find user by login.", + "tags": [ + "users" + ] + }, + "put": { + "description": "This endpoint allows users to update their email address and publish notifications settings.\n\nThe `id` parameter needs to match the ID of the currently authenticated user.", + "operationId": "update_user", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Update user settings.", + "tags": [ + "users" + ] + } } }, "servers": [ From 949634b8f94301a87cd21e9b1eeba44cd307f079 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:35:53 +0100 Subject: [PATCH 25/43] utoipa: Add annotations to `/users/{id}/stats` endpoints --- src/controllers/user/other.rs | 12 +++++++++++- src/router.rs | 2 +- ...ates_io__openapi__tests__openapi_snapshot.snap | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/controllers/user/other.rs b/src/controllers/user/other.rs index d653c6414f..05602a415b 100644 --- a/src/controllers/user/other.rs +++ b/src/controllers/user/other.rs @@ -35,7 +35,17 @@ pub async fn show(state: AppState, Path(user_name): Path) -> AppResult) -> AppResult { let mut conn = state.db_read_prefer_primary().await?; diff --git a/src/router.rs b/src/router.rs index 639d81a0cd..ac8a157c66 100644 --- a/src/router.rs +++ b/src/router.rs @@ -45,10 +45,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(category::show)) .routes(routes!(category::slugs)) .routes(routes!(user::other::show, user::update::update_user)) + .routes(routes!(user::other::stats)) .split_for_parts(); let mut router = router - .route("/api/v1/users/:user_id/stats", get(user::other::stats)) .route("/api/v1/teams/:team_id", get(team::show_team)) .route("/api/v1/me", get(user::me::me)) .route("/api/v1/me/updates", get(user::me::updates)) diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 84d8b58052..5cf744c1fe 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -440,6 +440,21 @@ snapshot_kind: text ] } }, + "/api/v1/users/{id}/stats": { + "get": { + "description": "This currently only returns the total number of downloads for crates owned\nby the user.", + "operationId": "get_user_stats", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Get user stats.", + "tags": [ + "users" + ] + } + }, "/api/v1/users/{user}": { "get": { "operationId": "get_user", From 3767cef3cfc8d1a1c39aec58d6da35ef6cd5fdbd Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:37:16 +0100 Subject: [PATCH 26/43] utoipa: Add annotations to `/teams/{team}` endpoints --- src/controllers/team.rs | 9 ++++++++- src/router.rs | 2 +- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/controllers/team.rs b/src/controllers/team.rs index b1263ae463..ccfc482e3b 100644 --- a/src/controllers/team.rs +++ b/src/controllers/team.rs @@ -8,7 +8,14 @@ use axum_extra::response::ErasedJson; use diesel::prelude::*; use diesel_async::RunQueryDsl; -/// Handles the `GET /teams/:team_id` route. +/// Find team by login. +#[utoipa::path( + get, + path = "/api/v1/teams/{team}", + operation_id = "get_team", + tag = "teams", + responses((status = 200, description = "Successful Response")), +)] pub async fn show_team(state: AppState, Path(name): Path) -> AppResult { use crate::schema::teams::dsl::{login, teams}; diff --git a/src/router.rs b/src/router.rs index ac8a157c66..3d14274b40 100644 --- a/src/router.rs +++ b/src/router.rs @@ -46,10 +46,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(category::slugs)) .routes(routes!(user::other::show, user::update::update_user)) .routes(routes!(user::other::stats)) + .routes(routes!(team::show_team)) .split_for_parts(); let mut router = router - .route("/api/v1/teams/:team_id", get(team::show_team)) .route("/api/v1/me", get(user::me::me)) .route("/api/v1/me/updates", get(user::me::updates)) .route("/api/v1/me/tokens", get(token::list).put(token::new)) diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 5cf744c1fe..97b7bcaad4 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -440,6 +440,20 @@ snapshot_kind: text ] } }, + "/api/v1/teams/{team}": { + "get": { + "operationId": "get_team", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Find team by login.", + "tags": [ + "teams" + ] + } + }, "/api/v1/users/{id}/stats": { "get": { "description": "This currently only returns the total number of downloads for crates owned\nby the user.", From 67d13f70d9ccf9548b5aab4c77b261451bc95bad Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:39:10 +0100 Subject: [PATCH 27/43] utoipa: Add annotations to `/me` endpoints --- src/controllers/user/me.rs | 9 ++++++++- src/router.rs | 2 +- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/controllers/user/me.rs b/src/controllers/user/me.rs index 210db02e4c..00699c7778 100644 --- a/src/controllers/user/me.rs +++ b/src/controllers/user/me.rs @@ -19,7 +19,14 @@ use crate::util::errors::{bad_request, AppResult}; use crate::util::BytesRequest; use crate::views::{EncodableMe, EncodablePrivateUser, EncodableVersion, OwnedCrate}; -/// Handles the `GET /me` route. +/// Get the currently authenticated user. +#[utoipa::path( + get, + path = "/api/v1/me", + operation_id = "get_authenticated_user", + tag = "users", + responses((status = 200, description = "Successful Response")), +)] pub async fn me(app: AppState, req: Parts) -> AppResult> { let mut conn = app.db_read_prefer_primary().await?; let user_id = AuthCheck::only_cookie() diff --git a/src/router.rs b/src/router.rs index 3d14274b40..a80751ddf8 100644 --- a/src/router.rs +++ b/src/router.rs @@ -47,10 +47,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(user::other::show, user::update::update_user)) .routes(routes!(user::other::stats)) .routes(routes!(team::show_team)) + .routes(routes!(user::me::me)) .split_for_parts(); let mut router = router - .route("/api/v1/me", get(user::me::me)) .route("/api/v1/me/updates", get(user::me::updates)) .route("/api/v1/me/tokens", get(token::list).put(token::new)) .route( diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 97b7bcaad4..37dee131e3 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -440,6 +440,20 @@ snapshot_kind: text ] } }, + "/api/v1/me": { + "get": { + "operationId": "get_authenticated_user", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Get the currently authenticated user.", + "tags": [ + "users" + ] + } + }, "/api/v1/teams/{team}": { "get": { "operationId": "get_team", From dd4f19227ed2b764c669055765db3aecf9983985 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:42:02 +0100 Subject: [PATCH 28/43] utoipa: Add annotations to `/me/updates` endpoints --- src/controllers/user/me.rs | 9 ++++++++- src/router.rs | 2 +- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/controllers/user/me.rs b/src/controllers/user/me.rs index 00699c7778..761158b6b9 100644 --- a/src/controllers/user/me.rs +++ b/src/controllers/user/me.rs @@ -69,7 +69,14 @@ pub async fn me(app: AppState, req: Parts) -> AppResult> { })) } -/// Handles the `GET /me/updates` route. +/// List versions of crates that the authenticated user follows. +#[utoipa::path( + get, + path = "/api/v1/me/updates", + operation_id = "get_authenticated_user_updates", + tag = "versions", + responses((status = 200, description = "Successful Response")), +)] pub async fn updates(app: AppState, req: Parts) -> AppResult { let mut conn = app.db_read_prefer_primary().await?; let auth = AuthCheck::only_cookie().check(&req, &mut conn).await?; diff --git a/src/router.rs b/src/router.rs index a80751ddf8..a14fa8dbed 100644 --- a/src/router.rs +++ b/src/router.rs @@ -48,10 +48,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(user::other::stats)) .routes(routes!(team::show_team)) .routes(routes!(user::me::me)) + .routes(routes!(user::me::updates)) .split_for_parts(); let mut router = router - .route("/api/v1/me/updates", get(user::me::updates)) .route("/api/v1/me/tokens", get(token::list).put(token::new)) .route( "/api/v1/me/tokens/:id", diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 37dee131e3..7ef37b45aa 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -454,6 +454,20 @@ snapshot_kind: text ] } }, + "/api/v1/me/updates": { + "get": { + "operationId": "get_authenticated_user_updates", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "List versions of crates that the authenticated user follows.", + "tags": [ + "versions" + ] + } + }, "/api/v1/teams/{team}": { "get": { "operationId": "get_team", From c5987ae5c23e8a57e5f70ab452b4b94c5f26793f Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:45:33 +0100 Subject: [PATCH 29/43] utoipa: Add annotations to `/me/tokens` endpoints --- src/controllers/token.rs | 18 +++++++++++-- src/router.rs | 2 +- ..._io__openapi__tests__openapi_snapshot.snap | 26 +++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/controllers/token.rs b/src/controllers/token.rs index 8f6a4d2c30..a6994d8103 100644 --- a/src/controllers/token.rs +++ b/src/controllers/token.rs @@ -35,7 +35,14 @@ impl GetParams { } } -/// Handles the `GET /me/tokens` route. +/// List all API tokens of the authenticated user. +#[utoipa::path( + get, + path = "/api/v1/me/tokens", + operation_id = "list_api_tokens", + tag = "api_tokens", + responses((status = 200, description = "Successful Response")), +)] pub async fn list( app: AppState, Query(params): Query, @@ -76,7 +83,14 @@ pub struct NewApiTokenRequest { api_token: NewApiToken, } -/// Handles the `PUT /me/tokens` route. +/// Create a new API token. +#[utoipa::path( + put, + path = "/api/v1/me/tokens", + operation_id = "create_api_token", + tag = "api_tokens", + responses((status = 200, description = "Successful Response")), +)] pub async fn new( app: AppState, parts: Parts, diff --git a/src/router.rs b/src/router.rs index a14fa8dbed..99825681c8 100644 --- a/src/router.rs +++ b/src/router.rs @@ -49,10 +49,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(team::show_team)) .routes(routes!(user::me::me)) .routes(routes!(user::me::updates)) + .routes(routes!(token::list, token::new)) .split_for_parts(); let mut router = router - .route("/api/v1/me/tokens", get(token::list).put(token::new)) .route( "/api/v1/me/tokens/:id", get(token::show).delete(token::revoke), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 7ef37b45aa..6ac8fd3d50 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -454,6 +454,32 @@ snapshot_kind: text ] } }, + "/api/v1/me/tokens": { + "get": { + "operationId": "list_api_tokens", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "List all API tokens of the authenticated user.", + "tags": [ + "api_tokens" + ] + }, + "put": { + "operationId": "create_api_token", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Create a new API token.", + "tags": [ + "api_tokens" + ] + } + }, "/api/v1/me/updates": { "get": { "operationId": "get_authenticated_user_updates", From 2730095a1ac4acd33da92fc983a64e77791ba8f3 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:53:39 +0100 Subject: [PATCH 30/43] utoipa: Add annotations to `/me/tokens/{id}` endpoints --- src/controllers/token.rs | 18 +++++++++++-- src/router.rs | 5 +--- ..._io__openapi__tests__openapi_snapshot.snap | 26 +++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/controllers/token.rs b/src/controllers/token.rs index a6994d8103..86a3cbaa07 100644 --- a/src/controllers/token.rs +++ b/src/controllers/token.rs @@ -179,7 +179,14 @@ pub async fn new( Ok(json!({ "api_token": api_token })) } -/// Handles the `GET /me/tokens/:id` route. +/// Find API token by id. +#[utoipa::path( + get, + path = "/api/v1/me/tokens/{id}", + operation_id = "get_api_token", + tag = "api_tokens", + responses((status = 200, description = "Successful Response")), +)] pub async fn show(app: AppState, Path(id): Path, req: Parts) -> AppResult { let mut conn = app.db_write().await?; let auth = AuthCheck::default().check(&req, &mut conn).await?; @@ -193,7 +200,14 @@ pub async fn show(app: AppState, Path(id): Path, req: Parts) -> AppResult, req: Parts) -> AppResult { let mut conn = app.db_write().await?; let auth = AuthCheck::default().check(&req, &mut conn).await?; diff --git a/src/router.rs b/src/router.rs index 99825681c8..00067debd7 100644 --- a/src/router.rs +++ b/src/router.rs @@ -50,13 +50,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(user::me::me)) .routes(routes!(user::me::updates)) .routes(routes!(token::list, token::new)) + .routes(routes!(token::show, token::revoke)) .split_for_parts(); let mut router = router - .route( - "/api/v1/me/tokens/:id", - get(token::show).delete(token::revoke), - ) .route("/api/v1/tokens/current", delete(token::revoke_current)) .route( "/api/v1/me/crate_owner_invitations", diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 6ac8fd3d50..6031bd2af7 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -480,6 +480,32 @@ snapshot_kind: text ] } }, + "/api/v1/me/tokens/{id}": { + "delete": { + "operationId": "revoke_api_token", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Revoke API token.", + "tags": [ + "api_tokens" + ] + }, + "get": { + "operationId": "get_api_token", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Find API token by id.", + "tags": [ + "api_tokens" + ] + } + }, "/api/v1/me/updates": { "get": { "operationId": "get_authenticated_user_updates", From 868d2dae548d84bd5e05b22f76c441ead2c2442b Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:54:12 +0100 Subject: [PATCH 31/43] utoipa: Add annotations to `/tokens/current` endpoints --- src/controllers/token.rs | 12 +++++++++++- src/router.rs | 2 +- ...ates_io__openapi__tests__openapi_snapshot.snap | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/controllers/token.rs b/src/controllers/token.rs index 86a3cbaa07..14f0a06626 100644 --- a/src/controllers/token.rs +++ b/src/controllers/token.rs @@ -220,7 +220,17 @@ pub async fn revoke(app: AppState, Path(id): Path, req: Parts) -> AppResult Ok(json!({})) } -/// Handles the `DELETE /tokens/current` route. +/// Revoke the current API token. +/// +/// This endpoint revokes the API token that is used to authenticate +/// the request. +#[utoipa::path( + delete, + path = "/api/v1/tokens/current", + operation_id = "revoke_current_api_token", + tag = "api_tokens", + responses((status = 200, description = "Successful Response")), +)] pub async fn revoke_current(app: AppState, req: Parts) -> AppResult { let mut conn = app.db_write().await?; let auth = AuthCheck::default().check(&req, &mut conn).await?; diff --git a/src/router.rs b/src/router.rs index 00067debd7..dcaa9b2e09 100644 --- a/src/router.rs +++ b/src/router.rs @@ -51,10 +51,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(user::me::updates)) .routes(routes!(token::list, token::new)) .routes(routes!(token::show, token::revoke)) + .routes(routes!(token::revoke_current)) .split_for_parts(); let mut router = router - .route("/api/v1/tokens/current", delete(token::revoke_current)) .route( "/api/v1/me/crate_owner_invitations", get(crate_owner_invitation::list), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 6031bd2af7..69a168cb81 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -534,6 +534,21 @@ snapshot_kind: text ] } }, + "/api/v1/tokens/current": { + "delete": { + "description": "This endpoint revokes the API token that is used to authenticate\nthe request.", + "operationId": "revoke_current_api_token", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Revoke the current API token.", + "tags": [ + "api_tokens" + ] + } + }, "/api/v1/users/{id}/stats": { "get": { "description": "This currently only returns the total number of downloads for crates owned\nby the user.", From 6dafec27daa636ce5f12afc294cdf0e6ae591ccb Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 17:56:04 +0100 Subject: [PATCH 32/43] utoipa: Add annotations to `/me/crate_owner_invitations` endpoints --- src/controllers/crate_owner_invitation.rs | 9 ++++++++- src/router.rs | 5 +---- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/controllers/crate_owner_invitation.rs b/src/controllers/crate_owner_invitation.rs index b9b7383faf..96fdab464c 100644 --- a/src/controllers/crate_owner_invitation.rs +++ b/src/controllers/crate_owner_invitation.rs @@ -23,7 +23,14 @@ use http::request::Parts; use indexmap::IndexMap; use std::collections::{HashMap, HashSet}; -/// Handles the `GET /api/v1/me/crate_owner_invitations` route. +/// List all crate owner invitations for the authenticated user. +#[utoipa::path( + get, + path = "/api/v1/me/crate_owner_invitations", + operation_id = "list_crate_owner_invitations", + tag = "owners", + responses((status = 200, description = "Successful Response")), +)] pub async fn list(app: AppState, req: Parts) -> AppResult { let mut conn = app.db_read().await?; let auth = AuthCheck::only_cookie().check(&req, &mut conn).await?; diff --git a/src/router.rs b/src/router.rs index dcaa9b2e09..093e490e71 100644 --- a/src/router.rs +++ b/src/router.rs @@ -52,13 +52,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(token::list, token::new)) .routes(routes!(token::show, token::revoke)) .routes(routes!(token::revoke_current)) + .routes(routes!(crate_owner_invitation::list)) .split_for_parts(); let mut router = router - .route( - "/api/v1/me/crate_owner_invitations", - get(crate_owner_invitation::list), - ) .route( "/api/v1/me/crate_owner_invitations/:crate_id", put(crate_owner_invitation::handle_invite), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 69a168cb81..8766eccebb 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -454,6 +454,20 @@ snapshot_kind: text ] } }, + "/api/v1/me/crate_owner_invitations": { + "get": { + "operationId": "list_crate_owner_invitations", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "List all crate owner invitations for the authenticated user.", + "tags": [ + "owners" + ] + } + }, "/api/v1/me/tokens": { "get": { "operationId": "list_api_tokens", From b68719b02fef13de073c6c2dbdc4a3b8af67f91d Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 18:03:45 +0100 Subject: [PATCH 33/43] utoipa: Add annotations to `/me/crate_owner_invitations/{crate_id}` endpoints --- src/controllers/crate_owner_invitation.rs | 9 ++++++++- src/router.rs | 5 +---- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/controllers/crate_owner_invitation.rs b/src/controllers/crate_owner_invitation.rs index 96fdab464c..34368a6deb 100644 --- a/src/controllers/crate_owner_invitation.rs +++ b/src/controllers/crate_owner_invitation.rs @@ -272,7 +272,14 @@ struct OwnerInvitation { crate_owner_invite: InvitationResponse, } -/// Handles the `PUT /api/v1/me/crate_owner_invitations/:crate_id` route. +/// Accept or decline a crate owner invitation. +#[utoipa::path( + put, + path = "/api/v1/me/crate_owner_invitations/{crate_id}", + operation_id = "handle_crate_owner_invitation", + tag = "owners", + responses((status = 200, description = "Successful Response")), +)] pub async fn handle_invite(state: AppState, req: BytesRequest) -> AppResult { let (parts, body) = req.0.into_parts(); diff --git a/src/router.rs b/src/router.rs index 093e490e71..db6df5ea2b 100644 --- a/src/router.rs +++ b/src/router.rs @@ -53,13 +53,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(token::show, token::revoke)) .routes(routes!(token::revoke_current)) .routes(routes!(crate_owner_invitation::list)) + .routes(routes!(crate_owner_invitation::handle_invite)) .split_for_parts(); let mut router = router - .route( - "/api/v1/me/crate_owner_invitations/:crate_id", - put(crate_owner_invitation::handle_invite), - ) .route( "/api/v1/me/crate_owner_invitations/accept/:token", put(crate_owner_invitation::handle_invite_with_token), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 8766eccebb..8806104a64 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -468,6 +468,20 @@ snapshot_kind: text ] } }, + "/api/v1/me/crate_owner_invitations/{crate_id}": { + "put": { + "operationId": "handle_crate_owner_invitation", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Accept or decline a crate owner invitation.", + "tags": [ + "owners" + ] + } + }, "/api/v1/me/tokens": { "get": { "operationId": "list_api_tokens", From 29ca13c96bb89828e7032436fbde161d8eb24378 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 18:05:15 +0100 Subject: [PATCH 34/43] utoipa: Add annotations to `/me/crate_owner_invitations/accept/{token}` endpoints --- src/controllers/crate_owner_invitation.rs | 9 ++++++++- src/router.rs | 5 +---- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/controllers/crate_owner_invitation.rs b/src/controllers/crate_owner_invitation.rs index 34368a6deb..8d8653c2ac 100644 --- a/src/controllers/crate_owner_invitation.rs +++ b/src/controllers/crate_owner_invitation.rs @@ -307,7 +307,14 @@ pub async fn handle_invite(state: AppState, req: BytesRequest) -> AppResult, diff --git a/src/router.rs b/src/router.rs index db6df5ea2b..dc8681f7c6 100644 --- a/src/router.rs +++ b/src/router.rs @@ -54,13 +54,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(token::revoke_current)) .routes(routes!(crate_owner_invitation::list)) .routes(routes!(crate_owner_invitation::handle_invite)) + .routes(routes!(crate_owner_invitation::handle_invite_with_token)) .split_for_parts(); let mut router = router - .route( - "/api/v1/me/crate_owner_invitations/accept/:token", - put(crate_owner_invitation::handle_invite_with_token), - ) .route( "/api/v1/me/email_notifications", put(user::me::update_email_notifications), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 8806104a64..76602548a7 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -468,6 +468,20 @@ snapshot_kind: text ] } }, + "/api/v1/me/crate_owner_invitations/accept/{token}": { + "put": { + "operationId": "accept_crate_owner_invitation_with_token", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Accept a crate owner invitation with a token.", + "tags": [ + "owners" + ] + } + }, "/api/v1/me/crate_owner_invitations/{crate_id}": { "put": { "operationId": "handle_crate_owner_invitation", From 834624676e1a3c322106a40b735d0e8d4c1bbe0c Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 18:13:57 +0100 Subject: [PATCH 35/43] utoipa: Add annotations to `/me/email_notifications` endpoints --- src/controllers/user/me.rs | 13 ++++++++++++- src/router.rs | 5 +---- ...tes_io__openapi__tests__openapi_snapshot.snap | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/controllers/user/me.rs b/src/controllers/user/me.rs index 761158b6b9..e501be8366 100644 --- a/src/controllers/user/me.rs +++ b/src/controllers/user/me.rs @@ -133,7 +133,18 @@ pub async fn confirm_user_email(state: AppState, Path(token): Path) -> A ok_true() } -/// Handles `PUT /me/email_notifications` route +/// Update email notification settings for the authenticated user. +/// +/// This endpoint was implemented for an experimental feature that was never +/// fully implemented. It is now deprecated and will be removed in the future. +#[utoipa::path( + put, + path = "/api/v1/me/email_notifications", + operation_id = "update_email_notifications", + tag = "users", + responses((status = 200, description = "Successful Response")), +)] +#[deprecated] pub async fn update_email_notifications(app: AppState, req: BytesRequest) -> AppResult { use diesel::pg::upsert::excluded; diff --git a/src/router.rs b/src/router.rs index dc8681f7c6..4ec4d980ac 100644 --- a/src/router.rs +++ b/src/router.rs @@ -55,13 +55,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(crate_owner_invitation::list)) .routes(routes!(crate_owner_invitation::handle_invite)) .routes(routes!(crate_owner_invitation::handle_invite_with_token)) + .routes(routes!(user::me::update_email_notifications)) .split_for_parts(); let mut router = router - .route( - "/api/v1/me/email_notifications", - put(user::me::update_email_notifications), - ) .route("/api/v1/summary", get(summary::summary)) .route( "/api/v1/confirm/:email_token", diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 76602548a7..ec9906bb37 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -496,6 +496,22 @@ snapshot_kind: text ] } }, + "/api/v1/me/email_notifications": { + "put": { + "deprecated": true, + "description": "This endpoint was implemented for an experimental feature that was never\nfully implemented. It is now deprecated and will be removed in the future.", + "operationId": "update_email_notifications", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Update email notification settings for the authenticated user.", + "tags": [ + "users" + ] + } + }, "/api/v1/me/tokens": { "get": { "operationId": "list_api_tokens", From d0bb30ba1cd6550bb9103a9b564ddce8ee2baae2 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 18:16:56 +0100 Subject: [PATCH 36/43] utoipa: Add annotations to `/summary` endpoints --- src/controllers/summary.rs | 12 +++++++++++- src/router.rs | 2 +- ...ates_io__openapi__tests__openapi_snapshot.snap | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/controllers/summary.rs b/src/controllers/summary.rs index 452cb7d5c8..c803d89242 100644 --- a/src/controllers/summary.rs +++ b/src/controllers/summary.rs @@ -12,7 +12,17 @@ use diesel_async::{AsyncPgConnection, RunQueryDsl}; use futures_util::FutureExt; use std::future::Future; -/// Handles the `GET /summary` route. +/// Get front page data. +/// +/// This endpoint returns a summary of the most important data for the front +/// page of crates.io. +#[utoipa::path( + get, + path = "/api/v1/summary", + operation_id = "get_summary", + tag = "other", + responses((status = 200, description = "Successful Response")), +)] pub async fn summary(state: AppState) -> AppResult { let mut conn = state.db_read().await?; diff --git a/src/router.rs b/src/router.rs index 4ec4d980ac..1810811aa8 100644 --- a/src/router.rs +++ b/src/router.rs @@ -56,10 +56,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(crate_owner_invitation::handle_invite)) .routes(routes!(crate_owner_invitation::handle_invite_with_token)) .routes(routes!(user::me::update_email_notifications)) + .routes(routes!(summary::summary)) .split_for_parts(); let mut router = router - .route("/api/v1/summary", get(summary::summary)) .route( "/api/v1/confirm/:email_token", put(user::me::confirm_user_email), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index ec9906bb37..4fa3bd87e7 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -578,6 +578,21 @@ snapshot_kind: text ] } }, + "/api/v1/summary": { + "get": { + "description": "This endpoint returns a summary of the most important data for the front\npage of crates.io.", + "operationId": "get_summary", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Get front page data.", + "tags": [ + "other" + ] + } + }, "/api/v1/teams/{team}": { "get": { "operationId": "get_team", From b2a388812043e3e19adbf19af7234b78a889569c Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 18:18:44 +0100 Subject: [PATCH 37/43] utoipa: Add annotations to `/confirm/{email_token}` endpoints --- src/controllers/user/me.rs | 9 ++++++++- src/router.rs | 5 +---- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/controllers/user/me.rs b/src/controllers/user/me.rs index e501be8366..a4c0d3bf00 100644 --- a/src/controllers/user/me.rs +++ b/src/controllers/user/me.rs @@ -115,7 +115,14 @@ pub async fn updates(app: AppState, req: Parts) -> AppResult { })) } -/// Handles the `PUT /confirm/:email_token` route +/// Marks the email belonging to the given token as verified. +#[utoipa::path( + put, + path = "/api/v1/confirm/{email_token}", + operation_id = "confirm_user_email", + tag = "users", + responses((status = 200, description = "Successful Response")), +)] pub async fn confirm_user_email(state: AppState, Path(token): Path) -> AppResult { use diesel::update; diff --git a/src/router.rs b/src/router.rs index 1810811aa8..8919c2de61 100644 --- a/src/router.rs +++ b/src/router.rs @@ -57,13 +57,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(crate_owner_invitation::handle_invite_with_token)) .routes(routes!(user::me::update_email_notifications)) .routes(routes!(summary::summary)) + .routes(routes!(user::me::confirm_user_email)) .split_for_parts(); let mut router = router - .route( - "/api/v1/confirm/:email_token", - put(user::me::confirm_user_email), - ) .route( "/api/v1/users/:user_id/resend", put(user::regenerate_token_and_send), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 4fa3bd87e7..5c70b558ae 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -62,6 +62,20 @@ snapshot_kind: text ] } }, + "/api/v1/confirm/{email_token}": { + "put": { + "operationId": "confirm_user_email", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Marks the email belonging to the given token as verified.", + "tags": [ + "users" + ] + } + }, "/api/v1/crates": { "get": { "description": "Called in a variety of scenarios in the front end, including:\n- Alphabetical listing of crates\n- List of crates under a specific owner\n- Listing a user's followed crates", From 4a7d68ebee95de63b9b8da49c1c5e289bf4bdf0b Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 18:25:35 +0100 Subject: [PATCH 38/43] utoipa: Add annotations to `/users/{id}/resend` endpoints --- src/controllers/user.rs | 2 +- src/controllers/user/resend.rs | 9 ++++++++- src/router.rs | 7 ++----- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/controllers/user.rs b/src/controllers/user.rs index 4226f0f3a0..250a658be1 100644 --- a/src/controllers/user.rs +++ b/src/controllers/user.rs @@ -1,6 +1,6 @@ pub mod me; pub mod other; -mod resend; +pub mod resend; pub mod session; pub mod update; diff --git a/src/controllers/user/resend.rs b/src/controllers/user/resend.rs index d73f47697e..3a743848a9 100644 --- a/src/controllers/user/resend.rs +++ b/src/controllers/user/resend.rs @@ -14,7 +14,14 @@ use diesel_async::scoped_futures::ScopedFutureExt; use diesel_async::{AsyncConnection, RunQueryDsl}; use http::request::Parts; -/// Handles `PUT /user/:user_id/resend` route +/// Regenerate and send an email verification token. +#[utoipa::path( + put, + path = "/api/v1/users/{id}/resend", + operation_id = "resend_email_verification", + tag = "users", + responses((status = 200, description = "Successful Response")), +)] pub async fn regenerate_token_and_send( state: AppState, Path(param_user_id): Path, diff --git a/src/router.rs b/src/router.rs index 8919c2de61..e77fa5396f 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,5 +1,5 @@ use axum::response::IntoResponse; -use axum::routing::{delete, get, post, put}; +use axum::routing::{delete, get, post}; use axum::{Json, Router}; use http::{Method, StatusCode}; use utoipa_axum::routes; @@ -58,13 +58,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(user::me::update_email_notifications)) .routes(routes!(summary::summary)) .routes(routes!(user::me::confirm_user_email)) + .routes(routes!(user::resend::regenerate_token_and_send)) .split_for_parts(); let mut router = router - .route( - "/api/v1/users/:user_id/resend", - put(user::regenerate_token_and_send), - ) .route( "/api/v1/site_metadata", get(site_metadata::show_deployed_sha), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 5c70b558ae..41874ef745 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -636,6 +636,20 @@ snapshot_kind: text ] } }, + "/api/v1/users/{id}/resend": { + "put": { + "operationId": "resend_email_verification", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Regenerate and send an email verification token.", + "tags": [ + "users" + ] + } + }, "/api/v1/users/{id}/stats": { "get": { "description": "This currently only returns the total number of downloads for crates owned\nby the user.", From d7b2d4e706910128b2ca41c39bf2aa286fdfd95f Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 18:28:53 +0100 Subject: [PATCH 39/43] utoipa: Add annotations to `/site_metadata` endpoints --- src/controllers/site_metadata.rs | 13 ++++++++++--- src/router.rs | 5 +---- ...ates_io__openapi__tests__openapi_snapshot.snap | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/controllers/site_metadata.rs b/src/controllers/site_metadata.rs index 910055458e..7d4515f2d5 100644 --- a/src/controllers/site_metadata.rs +++ b/src/controllers/site_metadata.rs @@ -2,10 +2,17 @@ use crate::app::AppState; use axum::response::IntoResponse; use axum_extra::json; -/// Returns the JSON representation of the current deployed commit sha. +/// Get crates.io metadata. /// -/// The sha is contained within the `HEROKU_SLUG_COMMIT` environment variable. -/// If `HEROKU_SLUG_COMMIT` is not set, returns `"unknown"`. +/// Returns the current deployed commit SHA1 (or `unknown`), and whether the +/// system is in read-only mode. +#[utoipa::path( + get, + path = "/api/v1/site_metadata", + operation_id = "get_site_metadata", + tag = "other", + responses((status = 200, description = "Successful Response")), +)] pub async fn show_deployed_sha(state: AppState) -> impl IntoResponse { let read_only = state.config.db.are_all_read_only(); diff --git a/src/router.rs b/src/router.rs index e77fa5396f..fc36622c9c 100644 --- a/src/router.rs +++ b/src/router.rs @@ -59,13 +59,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(summary::summary)) .routes(routes!(user::me::confirm_user_email)) .routes(routes!(user::resend::regenerate_token_and_send)) + .routes(routes!(site_metadata::show_deployed_sha)) .split_for_parts(); let mut router = router - .route( - "/api/v1/site_metadata", - get(site_metadata::show_deployed_sha), - ) // Session management .route("/api/private/session/begin", get(user::session::begin)) .route( diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 41874ef745..3bbacf0570 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -592,6 +592,21 @@ snapshot_kind: text ] } }, + "/api/v1/site_metadata": { + "get": { + "description": "Returns the current deployed commit SHA1 (or `unknown`), and whether the\nsystem is in read-only mode.", + "operationId": "get_site_metadata", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Get crates.io metadata.", + "tags": [ + "other" + ] + } + }, "/api/v1/summary": { "get": { "description": "This endpoint returns a summary of the most important data for the front\npage of crates.io.", From b03579c1a602addc27f44ebd86f452593116db82 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 18:32:33 +0100 Subject: [PATCH 40/43] utoipa: Add annotations to `/private/session/begin` endpoints --- src/controllers/user/session.rs | 9 ++++++++- src/router.rs | 4 ++-- ...ates_io__openapi__tests__openapi_snapshot.snap | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/controllers/user/session.rs b/src/controllers/user/session.rs index e555573a1e..1f6e0ffbd3 100644 --- a/src/controllers/user/session.rs +++ b/src/controllers/user/session.rs @@ -19,7 +19,7 @@ use crate::util::errors::{bad_request, server_error, AppResult}; use crate::views::EncodableMe; use crates_io_github::GithubUser; -/// Handles the `GET /api/private/session/begin` route. +/// Begin authentication flow. /// /// This route will return an authorization URL for the GitHub OAuth flow including the crates.io /// `client_id` and a randomly generated `state` secret. @@ -34,6 +34,13 @@ use crates_io_github::GithubUser; /// "url": "https://github.com/login/oauth/authorize?client_id=...&state=...&scope=read%3Aorg" /// } /// ``` +#[utoipa::path( + get, + path = "/api/private/session/begin", + operation_id = "begin_session", + tag = "session", + responses((status = 200, description = "Successful Response")), +)] pub async fn begin(app: AppState, session: SessionExtension) -> ErasedJson { let (url, state) = app .github_oauth diff --git a/src/router.rs b/src/router.rs index fc36622c9c..ee20722f49 100644 --- a/src/router.rs +++ b/src/router.rs @@ -60,11 +60,11 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(user::me::confirm_user_email)) .routes(routes!(user::resend::regenerate_token_and_send)) .routes(routes!(site_metadata::show_deployed_sha)) + // Session management + .routes(routes!(user::session::begin)) .split_for_parts(); let mut router = router - // Session management - .route("/api/private/session/begin", get(user::session::begin)) .route( "/api/private/session/authorize", get(user::session::authorize), diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 3bbacf0570..5df7302aa8 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -20,6 +20,21 @@ snapshot_kind: text }, "openapi": "3.1.0", "paths": { + "/api/private/session/begin": { + "get": { + "description": "This route will return an authorization URL for the GitHub OAuth flow including the crates.io\n`client_id` and a randomly generated `state` secret.\n\nsee \n\n## Response Body Example\n\n```json\n{\n \"state\": \"b84a63c4ea3fcb4ac84\",\n \"url\": \"https://github.com/login/oauth/authorize?client_id=...&state=...&scope=read%3Aorg\"\n}\n```", + "operationId": "begin_session", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Begin authentication flow.", + "tags": [ + "session" + ] + } + }, "/api/v1/categories": { "get": { "operationId": "list_categories", From 4c0ebe9eb792d2e220f28de5abaacd63baca68cd Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 18:35:09 +0100 Subject: [PATCH 41/43] utoipa: Add annotations to `/private/session/authorize` endpoints --- src/controllers/user/session.rs | 10 ++++++++-- src/router.rs | 5 +---- ...ates_io__openapi__tests__openapi_snapshot.snap | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/controllers/user/session.rs b/src/controllers/user/session.rs index 1f6e0ffbd3..3bd20c633c 100644 --- a/src/controllers/user/session.rs +++ b/src/controllers/user/session.rs @@ -61,7 +61,7 @@ pub struct AuthorizeQuery { state: CsrfToken, } -/// Handles the `GET /api/private/session/authorize` route. +/// Complete authentication flow. /// /// This route is called from the GitHub API OAuth flow after the user accepted or rejected /// the data access permissions. It will check the `state` parameter and then call the GitHub API @@ -79,7 +79,6 @@ pub struct AuthorizeQuery { /// /// ```json /// { -/// "api_token": "b84a63c4ea3fcb4ac84", /// "user": { /// "email": "foo@bar.org", /// "name": "Foo Bar", @@ -89,6 +88,13 @@ pub struct AuthorizeQuery { /// } /// } /// ``` +#[utoipa::path( + get, + path = "/api/private/session/authorize", + operation_id = "authorize_session", + tag = "session", + responses((status = 200, description = "Successful Response")), +)] pub async fn authorize( query: AuthorizeQuery, app: AppState, diff --git a/src/router.rs b/src/router.rs index ee20722f49..ccc164cb0f 100644 --- a/src/router.rs +++ b/src/router.rs @@ -62,13 +62,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(site_metadata::show_deployed_sha)) // Session management .routes(routes!(user::session::begin)) + .routes(routes!(user::session::authorize)) .split_for_parts(); let mut router = router - .route( - "/api/private/session/authorize", - get(user::session::authorize), - ) .route("/api/private/session", delete(user::session::logout)) // Metrics .route("/api/private/metrics/:kind", get(metrics::prometheus)) diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 5df7302aa8..bafc5be149 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -20,6 +20,21 @@ snapshot_kind: text }, "openapi": "3.1.0", "paths": { + "/api/private/session/authorize": { + "get": { + "description": "This route is called from the GitHub API OAuth flow after the user accepted or rejected\nthe data access permissions. It will check the `state` parameter and then call the GitHub API\nto exchange the temporary `code` for an API token. The API token is returned together with\nthe corresponding user information.\n\nsee \n\n## Query Parameters\n\n- `code` – temporary code received from the GitHub API **(Required)**\n- `state` – state parameter received from the GitHub API **(Required)**\n\n## Response Body Example\n\n```json\n{\n \"user\": {\n \"email\": \"foo@bar.org\",\n \"name\": \"Foo Bar\",\n \"login\": \"foobar\",\n \"avatar\": \"https://avatars.githubusercontent.com/u/1234\",\n \"url\": null\n }\n}\n```", + "operationId": "authorize_session", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "Complete authentication flow.", + "tags": [ + "session" + ] + } + }, "/api/private/session/begin": { "get": { "description": "This route will return an authorization URL for the GitHub OAuth flow including the crates.io\n`client_id` and a randomly generated `state` secret.\n\nsee \n\n## Response Body Example\n\n```json\n{\n \"state\": \"b84a63c4ea3fcb4ac84\",\n \"url\": \"https://github.com/login/oauth/authorize?client_id=...&state=...&scope=read%3Aorg\"\n}\n```", From 3fec3e82bbfee904595cc854f7aef95ed66ecf8c Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 18:36:22 +0100 Subject: [PATCH 42/43] utoipa: Add annotations to `/private/session` endpoints --- src/controllers/user/session.rs | 9 ++++++++- src/router.rs | 2 +- ...rates_io__openapi__tests__openapi_snapshot.snap | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/controllers/user/session.rs b/src/controllers/user/session.rs index 3bd20c633c..84566e701a 100644 --- a/src/controllers/user/session.rs +++ b/src/controllers/user/session.rs @@ -171,7 +171,14 @@ async fn find_user_by_gh_id(conn: &mut AsyncPgConnection, gh_id: i32) -> QueryRe .optional() } -/// Handles the `DELETE /api/private/session` route. +/// End the current session. +#[utoipa::path( + delete, + path = "/api/private/session", + operation_id = "end_session", + tag = "session", + responses((status = 200, description = "Successful Response")), +)] pub async fn logout(session: SessionExtension) -> Json { session.remove("user_id"); Json(true) diff --git a/src/router.rs b/src/router.rs index ccc164cb0f..9559e3ea0f 100644 --- a/src/router.rs +++ b/src/router.rs @@ -63,10 +63,10 @@ pub fn build_axum_router(state: AppState) -> Router<()> { // Session management .routes(routes!(user::session::begin)) .routes(routes!(user::session::authorize)) + .routes(routes!(user::session::logout)) .split_for_parts(); let mut router = router - .route("/api/private/session", delete(user::session::logout)) // Metrics .route("/api/private/metrics/:kind", get(metrics::prometheus)) // Crate ownership invitations management in the frontend diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index bafc5be149..607b324c69 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -20,6 +20,20 @@ snapshot_kind: text }, "openapi": "3.1.0", "paths": { + "/api/private/session": { + "delete": { + "operationId": "end_session", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "End the current session.", + "tags": [ + "session" + ] + } + }, "/api/private/session/authorize": { "get": { "description": "This route is called from the GitHub API OAuth flow after the user accepted or rejected\nthe data access permissions. It will check the `state` parameter and then call the GitHub API\nto exchange the temporary `code` for an API token. The API token is returned together with\nthe corresponding user information.\n\nsee \n\n## Query Parameters\n\n- `code` – temporary code received from the GitHub API **(Required)**\n- `state` – state parameter received from the GitHub API **(Required)**\n\n## Response Body Example\n\n```json\n{\n \"user\": {\n \"email\": \"foo@bar.org\",\n \"name\": \"Foo Bar\",\n \"login\": \"foobar\",\n \"avatar\": \"https://avatars.githubusercontent.com/u/1234\",\n \"url\": null\n }\n}\n```", From dd6973958b0d6949532b97b83adc5b9c47104bdb Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 13 Dec 2024 18:39:39 +0100 Subject: [PATCH 43/43] utoipa: Add annotations to `/private/crate_owner_invitations` endpoints --- src/controllers/crate_owner_invitation.rs | 11 +++++++++-- src/router.rs | 8 ++------ ...tes_io__openapi__tests__openapi_snapshot.snap | 16 +++++++++++++++- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/controllers/crate_owner_invitation.rs b/src/controllers/crate_owner_invitation.rs index 8d8653c2ac..4bfd68d52b 100644 --- a/src/controllers/crate_owner_invitation.rs +++ b/src/controllers/crate_owner_invitation.rs @@ -27,7 +27,7 @@ use std::collections::{HashMap, HashSet}; #[utoipa::path( get, path = "/api/v1/me/crate_owner_invitations", - operation_id = "list_crate_owner_invitations", + operation_id = "list_crate_owner_invitations_for_user", tag = "owners", responses((status = 200, description = "Successful Response")), )] @@ -68,7 +68,14 @@ pub async fn list(app: AppState, req: Parts) -> AppResult { })) } -/// Handles the `GET /api/private/crate_owner_invitations` route. +/// List all crate owner invitations for a crate or user. +#[utoipa::path( + get, + path = "/api/private/crate_owner_invitations", + operation_id = "list_crate_owner_invitations", + tag = "owners", + responses((status = 200, description = "Successful Response")), +)] pub async fn private_list(app: AppState, req: Parts) -> AppResult> { let mut conn = app.db_read().await?; let auth = AuthCheck::only_cookie().check(&req, &mut conn).await?; diff --git a/src/router.rs b/src/router.rs index 9559e3ea0f..24758d9240 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,5 +1,5 @@ use axum::response::IntoResponse; -use axum::routing::{delete, get, post}; +use axum::routing::{get, post}; use axum::{Json, Router}; use http::{Method, StatusCode}; use utoipa_axum::routes; @@ -53,6 +53,7 @@ pub fn build_axum_router(state: AppState) -> Router<()> { .routes(routes!(token::show, token::revoke)) .routes(routes!(token::revoke_current)) .routes(routes!(crate_owner_invitation::list)) + .routes(routes!(crate_owner_invitation::private_list)) .routes(routes!(crate_owner_invitation::handle_invite)) .routes(routes!(crate_owner_invitation::handle_invite_with_token)) .routes(routes!(user::me::update_email_notifications)) @@ -69,11 +70,6 @@ pub fn build_axum_router(state: AppState) -> Router<()> { let mut router = router // Metrics .route("/api/private/metrics/:kind", get(metrics::prometheus)) - // Crate ownership invitations management in the frontend - .route( - "/api/private/crate_owner_invitations", - get(crate_owner_invitation::private_list), - ) // Alerts from GitHub scanning for exposed API tokens .route( "/api/github/secret-scanning/verify", diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 607b324c69..325db33001 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -20,6 +20,20 @@ snapshot_kind: text }, "openapi": "3.1.0", "paths": { + "/api/private/crate_owner_invitations": { + "get": { + "operationId": "list_crate_owner_invitations", + "responses": { + "200": { + "description": "Successful Response" + } + }, + "summary": "List all crate owner invitations for a crate or user.", + "tags": [ + "owners" + ] + } + }, "/api/private/session": { "delete": { "operationId": "end_session", @@ -514,7 +528,7 @@ snapshot_kind: text }, "/api/v1/me/crate_owner_invitations": { "get": { - "operationId": "list_crate_owner_invitations", + "operationId": "list_crate_owner_invitations_for_user", "responses": { "200": { "description": "Successful Response"