Skip to content

Commit 3706791

Browse files
Rustin170506Turbo87
authored andcommitted
Reuse the code to perform version yank update
Signed-off-by: Rustin170506 <[email protected]>
1 parent c19e7fb commit 3706791

File tree

2 files changed

+68
-104
lines changed

2 files changed

+68
-104
lines changed

src/controllers/version/metadata.rs

+63-40
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use axum::extract::Path;
88
use axum::Json;
99
use crates_io_worker::BackgroundJob;
10-
use diesel::{ExpressionMethods, RunQueryDsl};
10+
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
1111
use diesel_async::async_connection_wrapper::AsyncConnectionWrapper;
1212
use http::request::Parts;
1313
use http::StatusCode;
@@ -22,6 +22,7 @@ use crate::models::{
2222
insert_version_owner_action, Crate, Rights, Version, VersionAction, VersionOwnerAction,
2323
};
2424
use crate::rate_limiter::LimitedAction;
25+
use crate::schema::versions;
2526
use crate::tasks::spawn_blocking;
2627
use crate::util::diesel::Conn;
2728
use crate::util::errors::{bad_request, custom, version_not_found, AppResult};
@@ -142,14 +143,52 @@ fn apply_yank_update(
142143
) -> AppResult<()> {
143144
// Try to update the yank state first, to avoid unnecessary checks.
144145
update_version_yank_state(version, update_data)?;
146+
perform_version_yank_update(state, req, conn, version, krate)?;
145147

146-
// Add authentication check
148+
Ok(())
149+
}
150+
151+
fn update_version_yank_state(version: &mut Version, update_data: &VersionUpdate) -> AppResult<()> {
152+
match (update_data.yanked, &update_data.yank_message) {
153+
(Some(true), Some(message)) => {
154+
version.yanked = true;
155+
version.yank_message = Some(message.clone());
156+
}
157+
(Some(yanked), None) => {
158+
version.yanked = yanked;
159+
version.yank_message = None;
160+
}
161+
(Some(false), Some(_)) => {
162+
return Err(bad_request("Cannot set yank message when unyanking"));
163+
}
164+
(None, Some(message)) => {
165+
if version.yanked {
166+
version.yank_message = Some(message.clone());
167+
} else {
168+
return Err(bad_request(
169+
"Cannot update yank message for a version that is not yanked",
170+
));
171+
}
172+
}
173+
// If both yanked and yank_message are None, do nothing.
174+
// This function only cares about updating the yanked state and yank message.
175+
(None, None) => {}
176+
}
177+
Ok(())
178+
}
179+
180+
pub fn perform_version_yank_update(
181+
state: &AppState,
182+
req: &Parts,
183+
conn: &mut impl Conn,
184+
version: &Version,
185+
krate: &Crate,
186+
) -> AppResult<()> {
147187
let auth = AuthCheck::default()
148188
.with_endpoint_scope(EndpointScope::Yank)
149189
.for_crate(&krate.name)
150190
.check(req, conn)?;
151191

152-
// Add rate limiting check
153192
state
154193
.rate_limiter
155194
.check_rate_limit(auth.user_id(), LimitedAction::YankUnyank, conn)?;
@@ -158,68 +197,52 @@ fn apply_yank_update(
158197
let user = auth.user();
159198
let owners = krate.owners(conn)?;
160199

161-
// Check user rights
162200
if Handle::current().block_on(user.rights(state, &owners))? < Rights::Publish {
163201
if user.is_admin {
202+
let action = if version.yanked {
203+
"yanking"
204+
} else {
205+
"unyanking"
206+
};
164207
warn!(
165-
"Admin {} is updating {}@{}",
208+
"Admin {} is {action} {}@{}",
166209
user.gh_login, krate.name, version.num
167210
);
168211
} else {
169212
return Err(custom(
170213
StatusCode::FORBIDDEN,
171-
"must already be an owner to update version",
214+
"must already be an owner to yank or unyank",
172215
));
173216
}
174217
}
175218

176-
diesel::update(&*version)
219+
// Check if the yanked state or yank message has changed
220+
let (yanked, yank_message) = versions::table
221+
.find(version.id)
222+
.select((versions::yanked, versions::yank_message))
223+
.first::<(bool, Option<String>)>(conn)?;
224+
225+
if yanked == version.yanked && yank_message == version.yank_message {
226+
// No changes, return early
227+
return Ok(());
228+
}
229+
230+
diesel::update(version)
177231
.set((
178-
crate::schema::versions::yanked.eq(version.yanked),
179-
crate::schema::versions::yank_message.eq(&version.yank_message),
232+
versions::yanked.eq(version.yanked),
233+
versions::yank_message.eq(&version.yank_message),
180234
))
181235
.execute(conn)?;
182236

183-
// Add version owner action
184237
let action = if version.yanked {
185238
VersionAction::Yank
186239
} else {
187240
VersionAction::Unyank
188241
};
189242
insert_version_owner_action(conn, version.id, user.id, api_token_id, action)?;
190243

191-
// Enqueue jobs
192244
jobs::enqueue_sync_to_index(&krate.name, conn)?;
193245
UpdateDefaultVersion::new(krate.id).enqueue(conn)?;
194246

195247
Ok(())
196248
}
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/controllers/version/yank.rs

+5-64
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,15 @@
11
//! Endpoints for yanking and unyanking specific versions of crates
22
3+
use super::metadata::perform_version_yank_update;
34
use super::version_and_crate;
45
use crate::app::AppState;
5-
use crate::auth::AuthCheck;
66
use crate::controllers::helpers::ok_true;
7-
use crate::models::token::EndpointScope;
8-
use crate::models::Rights;
9-
use crate::models::{insert_version_owner_action, VersionAction};
10-
use crate::rate_limiter::LimitedAction;
11-
use crate::schema::versions;
127
use crate::tasks::spawn_blocking;
13-
use crate::util::errors::{custom, version_not_found, AppResult};
14-
use crate::worker::jobs;
15-
use crate::worker::jobs::UpdateDefaultVersion;
8+
use crate::util::errors::{version_not_found, AppResult};
169
use axum::extract::Path;
1710
use axum::response::Response;
18-
use crates_io_worker::BackgroundJob;
19-
use diesel::prelude::*;
2011
use diesel_async::async_connection_wrapper::AsyncConnectionWrapper;
2112
use http::request::Parts;
22-
use http::StatusCode;
23-
use tokio::runtime::Handle;
2413

2514
/// Handles the `DELETE /crates/:crate_id/:version/yank` route.
2615
/// This does not delete a crate version, it makes the crate
@@ -66,57 +55,9 @@ async fn modify_yank(
6655
let conn = state.db_write().await?;
6756
spawn_blocking(move || {
6857
let conn: &mut AsyncConnectionWrapper<_> = &mut conn.into();
69-
70-
let auth = AuthCheck::default()
71-
.with_endpoint_scope(EndpointScope::Yank)
72-
.for_crate(&crate_name)
73-
.check(&req, conn)?;
74-
75-
state
76-
.rate_limiter
77-
.check_rate_limit(auth.user_id(), LimitedAction::YankUnyank, conn)?;
78-
79-
let (version, krate) = version_and_crate(conn, &crate_name, &version)?;
80-
let api_token_id = auth.api_token_id();
81-
let user = auth.user();
82-
let owners = krate.owners(conn)?;
83-
84-
if Handle::current().block_on(user.rights(&state, &owners))? < Rights::Publish {
85-
if user.is_admin {
86-
let action = if yanked { "yanking" } else { "unyanking" };
87-
warn!(
88-
"Admin {} is {action} {}@{}",
89-
user.gh_login, krate.name, version.num
90-
);
91-
} else {
92-
return Err(custom(
93-
StatusCode::FORBIDDEN,
94-
"must already be an owner to yank or unyank",
95-
));
96-
}
97-
}
98-
99-
if version.yanked == yanked {
100-
// The crate is already in the state requested, nothing to do
101-
return ok_true();
102-
}
103-
104-
diesel::update(&version)
105-
.set(versions::yanked.eq(yanked))
106-
.execute(conn)?;
107-
108-
let action = if yanked {
109-
VersionAction::Yank
110-
} else {
111-
VersionAction::Unyank
112-
};
113-
114-
insert_version_owner_action(conn, version.id, user.id, api_token_id, action)?;
115-
116-
jobs::enqueue_sync_to_index(&krate.name, conn)?;
117-
118-
UpdateDefaultVersion::new(krate.id).enqueue(conn)?;
119-
58+
let (mut version, krate) = version_and_crate(conn, &crate_name, &version)?;
59+
version.yanked = yanked;
60+
perform_version_yank_update(&state, &req, conn, &version, &krate)?;
12061
ok_true()
12162
})
12263
.await

0 commit comments

Comments
 (0)