From 0036471e9f5bbd4b4c7a770332199cf5c2ac7015 Mon Sep 17 00:00:00 2001 From: Timshel Date: Mon, 22 Jun 2026 16:23:42 +0200 Subject: [PATCH] Refactor Sends table --- .../2026-06-18-120000_add_sends_emails/up.sql | 38 +++++++++++++++ .../2026-06-18-120000_add_sends_emails/up.sql | 7 +++ .../down.sql | 2 - .../2026-06-18-120000_add_sends_emails/up.sql | 41 +++++++++++++++- src/api/core/sends.rs | 6 +-- src/api/notifications.rs | 3 +- src/api/push.rs | 4 +- src/db/models/send.rs | 47 +++++-------------- src/db/schema.rs | 6 +-- 9 files changed, 105 insertions(+), 49 deletions(-) diff --git a/migrations/mysql/2026-06-18-120000_add_sends_emails/up.sql b/migrations/mysql/2026-06-18-120000_add_sends_emails/up.sql index ae69e9d2..299c9093 100644 --- a/migrations/mysql/2026-06-18-120000_add_sends_emails/up.sql +++ b/migrations/mysql/2026-06-18-120000_add_sends_emails/up.sql @@ -11,3 +11,41 @@ CREATE TABLE sends_otp ( PRIMARY KEY(send_uuid, email) ); + + +DELETE FROM sends where user_uuid IS NULL; +UPDATE sends SET hide_email = false WHERE hide_email IS NULL; + +SELECT if ( + EXISTS( + SELECT CONSTRAINT_NAME FROM information_schema.table_constraints + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'sends' + AND CONSTRAINT_TYPE = 'FOREIGN KEY' + AND CONSTRAINT_NAME = 'sends_ibfk_2' + ) + ,'ALTER TABLE sends DROP FOREIGN KEY `sends_ibfk_2`' + ,'SELECT "info: FK sends_ibfk_2 does not exist."' +) INTO @drop_stmt; +PREPARE drop_stmt FROM @drop_stmt; +EXECUTE drop_stmt; + +SELECT if ( + EXISTS( + SELECT CONSTRAINT_NAME FROM information_schema.table_constraints + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'sends' + AND CONSTRAINT_TYPE = 'FOREIGN KEY' + AND CONSTRAINT_NAME = '2' + ) + ,'ALTER TABLE sends DROP FOREIGN KEY `2`' + ,'SELECT "info: FK sends 2 does not exist."' +) INTO @drop_stmt; +PREPARE drop_stmt FROM @drop_stmt; +EXECUTE drop_stmt; + +DEALLOCATE PREPARE drop_stmt; + +ALTER TABLE sends DROP COLUMN organization_uuid; +ALTER TABLE sends MODIFY user_uuid CHAR(36) NOT NULL; +ALTER TABLE sends MODIFY hide_email BOOLEAN NOT NULL; diff --git a/migrations/postgresql/2026-06-18-120000_add_sends_emails/up.sql b/migrations/postgresql/2026-06-18-120000_add_sends_emails/up.sql index bdbc4b95..9f7a0921 100644 --- a/migrations/postgresql/2026-06-18-120000_add_sends_emails/up.sql +++ b/migrations/postgresql/2026-06-18-120000_add_sends_emails/up.sql @@ -11,3 +11,10 @@ CREATE TABLE sends_otp ( PRIMARY KEY(send_uuid, email) ); + + +DELETE FROM sends where user_uuid IS NULL; +UPDATE sends SET hide_email = false WHERE hide_email IS NULL; +ALTER TABLE sends DROP COLUMN organization_uuid; +ALTER TABLE sends ALTER COLUMN user_uuid SET NOT NULL; +ALTER TABLE sends ALTER COLUMN hide_email SET NOT NULL; diff --git a/migrations/sqlite/2026-06-18-120000_add_sends_emails/down.sql b/migrations/sqlite/2026-06-18-120000_add_sends_emails/down.sql index 2cfff832..e69de29b 100644 --- a/migrations/sqlite/2026-06-18-120000_add_sends_emails/down.sql +++ b/migrations/sqlite/2026-06-18-120000_add_sends_emails/down.sql @@ -1,2 +0,0 @@ -ALTER TABLE sends DROP COLUMN emails; -DROP TABLE sends_otp; diff --git a/migrations/sqlite/2026-06-18-120000_add_sends_emails/up.sql b/migrations/sqlite/2026-06-18-120000_add_sends_emails/up.sql index 86ab7a45..28b6daab 100644 --- a/migrations/sqlite/2026-06-18-120000_add_sends_emails/up.sql +++ b/migrations/sqlite/2026-06-18-120000_add_sends_emails/up.sql @@ -1,4 +1,43 @@ -ALTER TABLE sends ADD COLUMN emails TEXT; +ALTER TABLE sends RENAME TO sends_old; + +CREATE TABLE sends ( + uuid TEXT NOT NULL PRIMARY KEY, + user_uuid TEXT NOT NULL REFERENCES users (uuid), + + name TEXT NOT NULL, + notes TEXT, + + atype INTEGER NOT NULL, + data TEXT NOT NULL, + akey TEXT NOT NULL, + password_hash BLOB, + password_salt BLOB, + password_iter INTEGER, + emails TEXT, + + max_access_count INTEGER, + access_count INTEGER NOT NULL, + + creation_date DATETIME NOT NULL, + revision_date DATETIME NOT NULL, + expiration_date DATETIME, + deletion_date DATETIME NOT NULL, + + disabled BOOLEAN NOT NULL, + hide_email BOOLEAN NOT NULL +); + +INSERT INTO sends( + uuid, user_uuid, name, notes, atype, data, akey, password_hash, password_salt, password_iter, + max_access_count, access_count, creation_date, revision_date, expiration_date, deletion_date, + disabled, hide_email +) SELECT uuid, user_uuid, name, notes, atype, data, akey, password_hash, password_salt, password_iter, + max_access_count, access_count, creation_date, revision_date, expiration_date, deletion_date, + disabled, + CASE WHEN hide_email IS NOT NULL THEN hide_email ELSE false END + FROM sends_old WHERE user_uuid IS NOT NULL; + +DROP TABLE sends_old; CREATE TABLE sends_otp ( send_uuid TEXT NOT NULL REFERENCES sends(uuid) ON DELETE CASCADE ON UPDATE CASCADE, diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs index cafc29df..6ffbeb96 100644 --- a/src/api/core/sends.rs +++ b/src/api/core/sends.rs @@ -172,7 +172,7 @@ fn create_send(data: SendData, user_id: UserId) -> ApiResult { data.expiration_date.map(|d| d.naive_utc()), data.deletion_date.naive_utc(), data.disabled, - data.hide_email, + data.hide_email.unwrap_or(false), ); send.set_password(data.password.as_deref()); @@ -665,7 +665,7 @@ pub async fn update_send_from_data( nt: &Notify<'_>, ut: UpdateType, ) -> EmptyResult { - if send.user_uuid.as_ref() != Some(&headers.user.uuid) { + if send.user_uuid != headers.user.uuid { err!("Send is not owned by user") } @@ -700,7 +700,7 @@ pub async fn update_send_from_data( _ => None, }; send.expiration_date = data.expiration_date.map(|d| d.naive_utc()); - send.hide_email = data.hide_email; + send.hide_email = data.hide_email.unwrap_or(false); send.disabled = data.disabled; send.emails = data.emails.map(|e| e.to_lowercase()); diff --git a/src/api/notifications.rs b/src/api/notifications.rs index 80067433..3c582197 100644 --- a/src/api/notifications.rs +++ b/src/api/notifications.rs @@ -465,12 +465,11 @@ impl WebSocketUsers { if *NOTIFICATIONS_DISABLED { return; } - let user_id = convert_option(send.user_uuid.as_deref()); let data = create_update( vec![ ("Id".into(), send.uuid.to_string().into()), - ("UserId".into(), user_id), + ("UserId".into(), send.user_uuid.to_string().into()), ("RevisionDate".into(), serialize_date(send.revision_date)), ], ut, diff --git a/src/api/push.rs b/src/api/push.rs index e87a0985..ac27d85e 100644 --- a/src/api/push.rs +++ b/src/api/push.rs @@ -244,9 +244,7 @@ pub async fn push_folder_update(ut: UpdateType, folder: &Folder, device: &Device } pub async fn push_send_update(ut: UpdateType, send: &Send, device: &Device, conn: &DbConn) { - if let Some(s) = &send.user_uuid - && Device::check_user_has_push_device(s, conn).await - { + if Device::check_user_has_push_device(&send.user_uuid, conn).await { tokio::task::spawn(send_to_push_relay(json!({ "userId": send.user_uuid, "organizationId": null, diff --git a/src/db/models/send.rs b/src/db/models/send.rs index 8072ff0d..26c5a2ab 100644 --- a/src/db/models/send.rs +++ b/src/db/models/send.rs @@ -20,7 +20,7 @@ use crate::{ util::{LowerCase, NumberOrString, format_date}, }; -use super::{OrganizationId, User, UserId}; +use super::{User, UserId}; #[derive(Identifiable, Queryable, Insertable, AsChangeset, Selectable)] #[diesel(table_name = sends)] @@ -29,8 +29,7 @@ use super::{OrganizationId, User, UserId}; pub struct Send { pub uuid: SendId, - pub user_uuid: Option, - pub organization_uuid: Option, + pub user_uuid: UserId, pub name: String, pub notes: Option, @@ -52,7 +51,7 @@ pub struct Send { pub deletion_date: NaiveDateTime, pub disabled: bool, - pub hide_email: Option, + pub hide_email: bool, } #[derive(Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)] @@ -85,14 +84,13 @@ impl Send { expiration_date: Option, deletion_date: NaiveDateTime, disabled: bool, - hide_email: Option, + hide_email: bool, ) -> Self { let now = Utc::now().naive_utc(); Self { uuid: SendId::from(crate::util::get_uuid()), - user_uuid: Some(user_uuid), - organization_uuid: None, + user_uuid, name, notes, atype, @@ -142,19 +140,13 @@ impl Send { } pub async fn creator_identifier(&self, conn: &DbConn) -> Option { - if let Some(hide_email) = self.hide_email - && hide_email + if !self.hide_email + && let Some(user) = User::find_by_uuid(&self.user_uuid, conn).await { - return None; - } - - if let Some(user_uuid) = &self.user_uuid - && let Some(user) = User::find_by_uuid(user_uuid, conn).await - { - return Some(user.email); + Some(user.email) + } else { + None } - - None } pub fn to_json(&self) -> Value { @@ -181,7 +173,7 @@ impl Send { "password": self.password_hash.as_deref().map(|h| BASE64URL_NOPAD.encode(h)), "authType": if self.password_hash.is_some() { SendAuthType::Password } else if self.emails.is_some() { SendAuthType::Email } else { SendAuthType::None } as i32, "disabled": self.disabled, - "hideEmail": self.hide_email.unwrap_or(false), + "hideEmail": self.hide_email, "emails": self.emails, "revisionDate": format_date(&self.revision_date), @@ -263,14 +255,8 @@ impl Send { } pub async fn update_users_revision(&self, conn: &DbConn) -> Vec { - let mut user_uuids = Vec::new(); - if let Some(user_uuid) = &self.user_uuid { - User::update_uuid_revision(user_uuid, conn).await; - user_uuids.push(user_uuid.clone()); - } else { - // Belongs to Organization, not implemented - } - user_uuids + User::update_uuid_revision(&self.user_uuid, conn).await; + vec![self.user_uuid.clone()] } pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult { @@ -332,13 +318,6 @@ impl Send { Some(total) } - pub async fn find_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> Vec { - conn.run(move |conn| { - sends::table.filter(sends::organization_uuid.eq(org_uuid)).load::(conn).expect("Error loading sends") - }) - .await - } - pub async fn find_by_past_deletion_date(conn: &DbConn) -> Vec { let now = Utc::now().naive_utc(); conn.run(move |conn| { diff --git a/src/db/schema.rs b/src/db/schema.rs index 7d9bb4d9..2f0ea0dc 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -132,8 +132,7 @@ table! { table! { sends (uuid) { uuid -> Text, - user_uuid -> Nullable, - organization_uuid -> Nullable, + user_uuid -> Text, name -> Text, notes -> Nullable, atype -> Integer, @@ -150,7 +149,7 @@ table! { expiration_date -> Nullable, deletion_date -> Timestamp, disabled -> Bool, - hide_email -> Nullable, + hide_email -> Bool, } } @@ -376,7 +375,6 @@ joinable!(folders -> users (user_uuid)); joinable!(folders_ciphers -> ciphers (cipher_uuid)); joinable!(folders_ciphers -> folders (folder_uuid)); joinable!(org_policies -> organizations (org_uuid)); -joinable!(sends -> organizations (organization_uuid)); joinable!(sends -> users (user_uuid)); joinable!(sends_otp -> sends (send_uuid)); joinable!(twofactor -> users (user_uuid));