Skip to content

Commit 624a705

Browse files
0xPoeTurbo87
authored andcommitted
Add PATCH /crates/:crate/:version route
Signed-off-by: Rustin170506 <[email protected]>
1 parent 3678583 commit 624a705

File tree

2 files changed

+142
-3
lines changed

2 files changed

+142
-3
lines changed

src/controllers/version/metadata.rs

Lines changed: 141 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,36 @@
66
77
use axum::extract::Path;
88
use axum::Json;
9+
use crates_io_worker::BackgroundJob;
10+
use diesel::{ExpressionMethods, RunQueryDsl};
911
use diesel_async::async_connection_wrapper::AsyncConnectionWrapper;
12+
use http::request::Parts;
13+
use http::StatusCode;
14+
use serde::Deserialize;
1015
use serde_json::Value;
16+
use tokio::runtime::Handle;
1117

1218
use crate::app::AppState;
13-
use crate::models::VersionOwnerAction;
19+
use crate::auth::AuthCheck;
20+
use crate::models::token::EndpointScope;
21+
use crate::models::{
22+
insert_version_owner_action, Crate, Rights, Version, VersionAction, VersionOwnerAction,
23+
};
24+
use crate::rate_limiter::LimitedAction;
1425
use crate::tasks::spawn_blocking;
15-
use crate::util::errors::{version_not_found, AppResult};
26+
use crate::util::diesel::Conn;
27+
use crate::util::errors::{bad_request, custom, version_not_found, AppResult};
1628
use crate::views::{EncodableDependency, EncodableVersion};
29+
use crate::worker::jobs::{self, UpdateDefaultVersion};
1730

1831
use super::version_and_crate;
1932

33+
#[derive(Deserialize)]
34+
pub struct VersionUpdate {
35+
yanked: Option<bool>,
36+
yank_message: Option<String>,
37+
}
38+
2039
/// Handles the `GET /crates/:crate_id/:version/dependencies` route.
2140
///
2241
/// This information can be obtained directly from the index.
@@ -84,3 +103,123 @@ pub async fn show(
84103
})
85104
.await
86105
}
106+
107+
/// Handles the `PATCH /crates/:crate/:version` route.
108+
///
109+
/// This endpoint allows updating the yanked state of a version, including a yank message.
110+
pub async fn update(
111+
state: AppState,
112+
Path((crate_name, version)): Path<(String, String)>,
113+
req: Parts,
114+
Json(update_data): Json<VersionUpdate>,
115+
) -> AppResult<Json<Value>> {
116+
if semver::Version::parse(&version).is_err() {
117+
return Err(version_not_found(&crate_name, &version));
118+
}
119+
120+
let conn = state.db_write().await?;
121+
spawn_blocking(move || {
122+
let conn: &mut AsyncConnectionWrapper<_> = &mut conn.into();
123+
let (mut version, krate) = version_and_crate(conn, &crate_name, &version)?;
124+
125+
apply_yank_update(&state, &req, conn, &mut version, &krate, &update_data)?;
126+
127+
let published_by = version.published_by(conn);
128+
let actions = VersionOwnerAction::by_version(conn, &version)?;
129+
let updated_version = EncodableVersion::from(version, &krate.name, published_by, actions);
130+
Ok(Json(json!({ "version": updated_version })))
131+
})
132+
.await
133+
}
134+
135+
fn apply_yank_update(
136+
state: &AppState,
137+
req: &Parts,
138+
conn: &mut impl Conn,
139+
version: &mut Version,
140+
krate: &Crate,
141+
update_data: &VersionUpdate,
142+
) -> AppResult<()> {
143+
// Try to update the yank state first, to avoid unnecessary checks.
144+
update_version_yank_state(version, update_data)?;
145+
146+
// Add authentication check
147+
let auth = AuthCheck::default()
148+
.with_endpoint_scope(EndpointScope::Yank)
149+
.for_crate(&krate.name)
150+
.check(req, conn)?;
151+
152+
// Add rate limiting check
153+
state
154+
.rate_limiter
155+
.check_rate_limit(auth.user_id(), LimitedAction::YankUnyank, conn)?;
156+
157+
let api_token_id = auth.api_token_id();
158+
let user = auth.user();
159+
let owners = krate.owners(conn)?;
160+
161+
// Check user rights
162+
if Handle::current().block_on(user.rights(state, &owners))? < Rights::Publish {
163+
if user.is_admin {
164+
warn!(
165+
"Admin {} is updating {}@{}",
166+
user.gh_login, krate.name, version.num
167+
);
168+
} else {
169+
return Err(custom(
170+
StatusCode::FORBIDDEN,
171+
"must already be an owner to update version",
172+
));
173+
}
174+
}
175+
176+
diesel::update(&*version)
177+
.set((
178+
crate::schema::versions::yanked.eq(version.yanked),
179+
crate::schema::versions::yank_message.eq(&version.yank_message),
180+
))
181+
.execute(conn)?;
182+
183+
// Add version owner action
184+
let action = if version.yanked {
185+
VersionAction::Yank
186+
} else {
187+
VersionAction::Unyank
188+
};
189+
insert_version_owner_action(conn, version.id, user.id, api_token_id, action)?;
190+
191+
// Enqueue jobs
192+
jobs::enqueue_sync_to_index(&krate.name, conn)?;
193+
UpdateDefaultVersion::new(krate.id).enqueue(conn)?;
194+
195+
Ok(())
196+
}
197+
198+
fn update_version_yank_state(version: &mut Version, update_data: &VersionUpdate) -> AppResult<()> {
199+
match (update_data.yanked, &update_data.yank_message) {
200+
(Some(true), Some(message)) => {
201+
version.yanked = true;
202+
version.yank_message = Some(message.clone());
203+
}
204+
(Some(yanked), None) => {
205+
version.yanked = yanked;
206+
version.yank_message = None;
207+
}
208+
(Some(false), Some(_)) => {
209+
return Err(bad_request("Cannot set yank message when unyanking"));
210+
}
211+
(None, Some(message)) => {
212+
if version.yanked {
213+
version.yank_message = Some(message.clone());
214+
} else {
215+
return Err(bad_request(
216+
"Cannot update yank message for a version that is not yanked",
217+
));
218+
}
219+
}
220+
// If both yanked and yank_message are None, do nothing.
221+
// This function only cares about updating the yanked state and yank message.
222+
(None, None) => {}
223+
}
224+
Ok(())
225+
}

src/router.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub fn build_axum_router(state: AppState) -> Router<()> {
4545
.route("/api/v1/crates/:crate_id", get(krate::metadata::show))
4646
.route(
4747
"/api/v1/crates/:crate_id/:version",
48-
get(version::metadata::show),
48+
get(version::metadata::show).patch(version::metadata::update),
4949
)
5050
.route(
5151
"/api/v1/crates/:crate_id/:version/readme",

0 commit comments

Comments
 (0)