From c0f88315a3fb828fa3087a3ff15495f468bbddb4 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 24 Jul 2025 07:06:52 +0200 Subject: [PATCH 1/7] database/models/action: Derive `Selectable` trait --- crates/crates_io_database/src/models/action.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/crates_io_database/src/models/action.rs b/crates/crates_io_database/src/models/action.rs index 893df2b418f..6f295b509b2 100644 --- a/crates/crates_io_database/src/models/action.rs +++ b/crates/crates_io_database/src/models/action.rs @@ -34,7 +34,7 @@ impl From for String { } } -#[derive(Debug, Clone, Copy, Queryable, Identifiable, Associations)] +#[derive(Debug, Clone, Copy, Queryable, Identifiable, Selectable, Associations)] #[diesel( table_name = version_owner_actions, check_for_backend(diesel::pg::Pg), From ded25379830edd89ae43150e1e462f769948025a Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 24 Jul 2025 06:54:39 +0200 Subject: [PATCH 2/7] Use `User::as_select()` to avoid column ordering issues --- crates/crates_io_database/src/models/action.rs | 2 ++ .../src/models/crate_owner_invitation.rs | 6 +++++- crates/crates_io_database/src/models/user.rs | 9 ++++++++- crates/crates_io_database/src/models/version.rs | 7 ++++++- src/bin/crates-admin/transfer_crates.rs | 2 ++ src/controllers/crate_owner_invitation.rs | 1 + src/controllers/krate/downloads.rs | 1 + src/controllers/session.rs | 1 + src/controllers/user/other.rs | 1 + 9 files changed, 27 insertions(+), 3 deletions(-) diff --git a/crates/crates_io_database/src/models/action.rs b/crates/crates_io_database/src/models/action.rs index 6f295b509b2..37cb0b0d8be 100644 --- a/crates/crates_io_database/src/models/action.rs +++ b/crates/crates_io_database/src/models/action.rs @@ -66,6 +66,7 @@ impl VersionOwnerAction { version_owner_actions::table .filter(version_id.eq(version.id)) .inner_join(users::table) + .select((VersionOwnerAction::as_select(), User::as_select())) .order(version_owner_actions::dsl::id) .load(conn) .boxed() @@ -77,6 +78,7 @@ impl VersionOwnerAction { ) -> QueryResult>> { Ok(Self::belonging_to(versions) .inner_join(users::table) + .select((VersionOwnerAction::as_select(), User::as_select())) .order(version_owner_actions::dsl::id) .load(conn) .await? diff --git a/crates/crates_io_database/src/models/crate_owner_invitation.rs b/crates/crates_io_database/src/models/crate_owner_invitation.rs index a2bb63ebf38..64792d2e3d1 100644 --- a/crates/crates_io_database/src/models/crate_owner_invitation.rs +++ b/crates/crates_io_database/src/models/crate_owner_invitation.rs @@ -103,7 +103,11 @@ impl CrateOwnerInvitation { } // Get the user and check if they have a verified email - let user: User = users::table.find(self.invited_user_id).first(conn).await?; + let user: User = users::table + .find(self.invited_user_id) + .select(User::as_select()) + .first(conn) + .await?; let verified_email = user.verified_email(conn).await?; if verified_email.is_none() { diff --git a/crates/crates_io_database/src/models/user.rs b/crates/crates_io_database/src/models/user.rs index f4b232abb6f..e6797c19c18 100644 --- a/crates/crates_io_database/src/models/user.rs +++ b/crates/crates_io_database/src/models/user.rs @@ -31,13 +31,18 @@ pub struct User { impl User { pub async fn find(conn: &mut AsyncPgConnection, id: i32) -> QueryResult { - users::table.find(id).first(conn).await + users::table + .find(id) + .select(User::as_select()) + .first(conn) + .await } pub async fn find_by_login(conn: &mut AsyncPgConnection, login: &str) -> QueryResult { users::table .filter(lower(users::gh_login).eq(login.to_lowercase())) .filter(users::gh_id.ne(-1)) + .select(User::as_select()) .order(users::gh_id.desc()) .first(conn) .await @@ -96,6 +101,7 @@ impl NewUser<'_> { pub async fn insert(&self, conn: &mut AsyncPgConnection) -> QueryResult { diesel::insert_into(users::table) .values(self) + .returning(User::as_returning()) .get_result(conn) .await } @@ -120,6 +126,7 @@ impl NewUser<'_> { users::gh_avatar.eq(excluded(users::gh_avatar)), users::gh_access_token.eq(excluded(users::gh_access_token)), )) + .returning(User::as_returning()) .get_result(conn) .await } diff --git a/crates/crates_io_database/src/models/version.rs b/crates/crates_io_database/src/models/version.rs index 477e8f51d77..88f4632144c 100644 --- a/crates/crates_io_database/src/models/version.rs +++ b/crates/crates_io_database/src/models/version.rs @@ -59,7 +59,12 @@ impl Version { /// Not for use when you have a group of versions you need the publishers for. pub async fn published_by(&self, conn: &mut AsyncPgConnection) -> QueryResult> { match self.published_by { - Some(pb) => users::table.find(pb).first(conn).await.optional(), + Some(pb) => users::table + .find(pb) + .select(User::as_select()) + .first(conn) + .await + .optional(), None => Ok(None), } } diff --git a/src/bin/crates-admin/transfer_crates.rs b/src/bin/crates-admin/transfer_crates.rs index d6e4dd1bb68..08423837fec 100644 --- a/src/bin/crates-admin/transfer_crates.rs +++ b/src/bin/crates-admin/transfer_crates.rs @@ -27,11 +27,13 @@ pub async fn run(opts: Opts) -> anyhow::Result<()> { async fn transfer(opts: Opts, conn: &mut AsyncPgConnection) -> anyhow::Result<()> { let from: User = users::table .filter(users::gh_login.eq(opts.from_user)) + .select(User::as_select()) .first(conn) .await?; let to: User = users::table .filter(users::gh_login.eq(opts.to_user)) + .select(User::as_select()) .first(conn) .await?; diff --git a/src/controllers/crate_owner_invitation.rs b/src/controllers/crate_owner_invitation.rs index 127e7cb24aa..a113eec82fc 100644 --- a/src/controllers/crate_owner_invitation.rs +++ b/src/controllers/crate_owner_invitation.rs @@ -266,6 +266,7 @@ async fn prepare_list( if !missing_users.is_empty() { let new_users: Vec = users::table .filter(users::id.eq_any(missing_users)) + .select(User::as_select()) .load(conn) .await?; for user in new_users.into_iter() { diff --git a/src/controllers/krate/downloads.rs b/src/controllers/krate/downloads.rs index a9d7ab7371c..c18735bd385 100644 --- a/src/controllers/krate/downloads.rs +++ b/src/controllers/krate/downloads.rs @@ -187,6 +187,7 @@ fn load_actions<'a>( } VersionOwnerAction::belonging_to(versions) .inner_join(users::table) + .select((VersionOwnerAction::as_select(), User::as_select())) .order(version_owner_actions::id) .load(conn) .boxed() diff --git a/src/controllers/session.rs b/src/controllers/session.rs index 7ca76afbb2b..8aaa4753c69 100644 --- a/src/controllers/session.rs +++ b/src/controllers/session.rs @@ -204,6 +204,7 @@ async fn create_or_update_user( async fn find_user_by_gh_id(conn: &mut AsyncPgConnection, gh_id: i32) -> QueryResult> { users::table .filter(users::gh_id.eq(gh_id)) + .select(User::as_select()) .first(conn) .await .optional() diff --git a/src/controllers/user/other.rs b/src/controllers/user/other.rs index de438bed6e6..34f179786ef 100644 --- a/src/controllers/user/other.rs +++ b/src/controllers/user/other.rs @@ -37,6 +37,7 @@ pub async fn find_user( let name = lower(&user_name); let user: User = users .filter(lower(gh_login).eq(name)) + .select(User::as_select()) .order(id.desc()) .first(&mut conn) .await?; From d7518ab680fd50ef3e28b7bc89883066fa566ada Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 24 Jul 2025 07:07:21 +0200 Subject: [PATCH 3/7] database/models/user: Reorder fields --- crates/crates_io_database/src/models/user.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/crates_io_database/src/models/user.rs b/crates/crates_io_database/src/models/user.rs index e6797c19c18..946c14301d1 100644 --- a/crates/crates_io_database/src/models/user.rs +++ b/crates/crates_io_database/src/models/user.rs @@ -16,13 +16,13 @@ use crates_io_diesel_helpers::lower; #[derive(Clone, Debug, Queryable, Identifiable, Selectable, Serialize)] pub struct User { pub id: i32, + pub name: Option, + pub gh_id: i32, + pub gh_login: String, + pub gh_avatar: Option, #[diesel(deserialize_as = String)] #[serde(skip)] pub gh_access_token: SecretString, - pub gh_login: String, - pub name: Option, - pub gh_avatar: Option, - pub gh_id: i32, pub account_lock_reason: Option, pub account_lock_until: Option>, pub is_admin: bool, From b5b2382673cb679a4b5265ed3b85eca5839b2392 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 24 Jul 2025 07:08:59 +0200 Subject: [PATCH 4/7] database/models/version: Remove obsolete code comment --- crates/crates_io_database/src/models/version.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/crates_io_database/src/models/version.rs b/crates/crates_io_database/src/models/version.rs index 88f4632144c..9c4116497b6 100644 --- a/crates/crates_io_database/src/models/version.rs +++ b/crates/crates_io_database/src/models/version.rs @@ -10,7 +10,6 @@ use serde::Deserialize; use crate::models::{Crate, TrustpubData, User}; use crate::schema::{readme_renderings, users, versions}; -// Queryable has a custom implementation below #[derive(Clone, Identifiable, Associations, Debug, Queryable, Selectable)] #[diesel(belongs_to(Crate), belongs_to(crate::models::download::Version, foreign_key=id))] pub struct Version { From fd1856a0641a3f5a5d5abad54f1096e1f55192e6 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 24 Jul 2025 07:33:20 +0200 Subject: [PATCH 5/7] database/models/download: Implement `Selectable` trait --- crates/crates_io_database/src/models/download.rs | 6 ++---- src/controllers/krate/downloads.rs | 1 + src/controllers/version/downloads.rs | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/crates_io_database/src/models/download.rs b/crates/crates_io_database/src/models/download.rs index 88359bacb5a..2f4f0efea71 100644 --- a/crates/crates_io_database/src/models/download.rs +++ b/crates/crates_io_database/src/models/download.rs @@ -4,7 +4,7 @@ use chrono::NaiveDate; use crates_io_diesel_helpers::SemverVersion; use diesel::prelude::*; -#[derive(Queryable, Identifiable, Associations, Debug, Clone, Copy)] +#[derive(Queryable, Identifiable, Selectable, Associations, Debug, Clone, Copy)] #[diesel( primary_key(version_id, date), belongs_to(FullVersion, foreign_key=version_id), @@ -12,10 +12,8 @@ use diesel::prelude::*; )] pub struct VersionDownload { pub version_id: i32, - pub downloads: i32, - pub counted: i32, pub date: NaiveDate, - pub processed: bool, + pub downloads: i32, } /// A subset of the columns of the `versions` table. diff --git a/src/controllers/krate/downloads.rs b/src/controllers/krate/downloads.rs index c18735bd385..9a4a66741ab 100644 --- a/src/controllers/krate/downloads.rs +++ b/src/controllers/krate/downloads.rs @@ -111,6 +111,7 @@ pub async fn get_crate_downloads( let (downloads, extra_downloads, versions_and_publishers, actions) = tokio::try_join!( VersionDownload::belonging_to(latest_five) .filter(version_downloads::date.gt(date(now - 90.days()))) + .select(VersionDownload::as_select()) .order(( version_downloads::date.asc(), version_downloads::version_id.desc(), diff --git a/src/controllers/version/downloads.rs b/src/controllers/version/downloads.rs index 0410bbd409a..66e84cddfbc 100644 --- a/src/controllers/version/downloads.rs +++ b/src/controllers/version/downloads.rs @@ -93,6 +93,7 @@ pub async fn get_version_downloads( let version_downloads = VersionDownload::belonging_to(&version) .filter(version_downloads::date.between(cutoff_start_date, cutoff_end_date)) + .select(VersionDownload::as_select()) .order(version_downloads::date) .load(&mut conn) .await? From ec1b0668ab379558e2155c529522c2d5606f311a Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 24 Jul 2025 07:40:30 +0200 Subject: [PATCH 6/7] database/models/email: Implement `Selectable` trait --- crates/crates_io_database/src/models/email.rs | 4 +--- src/controllers/user/email_verification.rs | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/crates_io_database/src/models/email.rs b/crates/crates_io_database/src/models/email.rs index c6fc64cf779..d3a96cca132 100644 --- a/crates/crates_io_database/src/models/email.rs +++ b/crates/crates_io_database/src/models/email.rs @@ -1,5 +1,4 @@ use bon::Builder; -use chrono::{DateTime, Utc}; use diesel::prelude::*; use diesel_async::{AsyncPgConnection, RunQueryDsl}; use secrecy::SecretString; @@ -7,7 +6,7 @@ use secrecy::SecretString; use crate::models::User; use crate::schema::emails; -#[derive(Debug, Queryable, Identifiable, Associations)] +#[derive(Debug, Queryable, Identifiable, Selectable, Associations)] #[diesel(belongs_to(User))] pub struct Email { pub id: i32, @@ -16,7 +15,6 @@ pub struct Email { pub verified: bool, #[diesel(deserialize_as = String, serialize_as = String)] pub token: SecretString, - pub token_generated_at: Option>, } #[derive(Debug, Insertable, AsChangeset, Builder)] diff --git a/src/controllers/user/email_verification.rs b/src/controllers/user/email_verification.rs index bab08ef3726..29479db6c84 100644 --- a/src/controllers/user/email_verification.rs +++ b/src/controllers/user/email_verification.rs @@ -74,6 +74,7 @@ pub async fn resend_email_verification( async move { let email: Email = diesel::update(Email::belonging_to(auth.user())) .set(emails::token.eq(sql("DEFAULT"))) + .returning(Email::as_returning()) .get_result(conn) .await .optional()? From bce2aa9b9f75202215518a9a31a88df6cc6ade95 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 24 Jul 2025 07:43:08 +0200 Subject: [PATCH 7/7] database/models/crate_owner_invitation: Implement `Selectable` trait --- .../crates_io_database/src/models/crate_owner_invitation.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/crates_io_database/src/models/crate_owner_invitation.rs b/crates/crates_io_database/src/models/crate_owner_invitation.rs index 64792d2e3d1..1b0773906d0 100644 --- a/crates/crates_io_database/src/models/crate_owner_invitation.rs +++ b/crates/crates_io_database/src/models/crate_owner_invitation.rs @@ -43,6 +43,7 @@ impl NewCrateOwnerInvitation { // already exists. This does not cause problems with expired invitation as those are // deleted before doing this INSERT. .on_conflict_do_nothing() + .returning(CrateOwnerInvitation::as_returning()) .get_result(conn) .await .optional()?; @@ -57,7 +58,7 @@ impl NewCrateOwnerInvitation { } /// The model representing a row in the `crate_owner_invitations` database table. -#[derive(Clone, Debug, Identifiable, Queryable)] +#[derive(Clone, Debug, Identifiable, Queryable, Selectable)] #[diesel(primary_key(invited_user_id, crate_id))] pub struct CrateOwnerInvitation { pub invited_user_id: i32, @@ -77,6 +78,7 @@ impl CrateOwnerInvitation { ) -> QueryResult { crate_owner_invitations::table .find((user_id, crate_id)) + .select(CrateOwnerInvitation::as_select()) .first::(conn) .await } @@ -84,6 +86,7 @@ impl CrateOwnerInvitation { pub async fn find_by_token(token: &str, conn: &mut AsyncPgConnection) -> QueryResult { crate_owner_invitations::table .filter(crate_owner_invitations::token.eq(token)) + .select(CrateOwnerInvitation::as_select()) .first::(conn) .await }