From 175d647e47fbd9abec4134c708199ba8aa1ec682 Mon Sep 17 00:00:00 2001 From: Jeremy Lin Date: Wed, 26 Aug 2020 01:27:38 -0700 Subject: [PATCH] Delete associated favorites when deleting a cipher or user This prevents foreign key constraint violations. --- src/db/models/cipher.rs | 58 +++++++-------------------- src/db/models/favorite.rs | 83 +++++++++++++++++++++++++++++++++++++++ src/db/models/mod.rs | 16 ++++---- src/db/models/user.rs | 3 +- 4 files changed, 108 insertions(+), 52 deletions(-) create mode 100644 src/db/models/favorite.rs diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index 4e223a6b..cadb4134 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -2,7 +2,15 @@ use chrono::{NaiveDateTime, Utc}; use serde_json::Value; use super::{ - Attachment, CollectionCipher, FolderCipher, Organization, User, UserOrgStatus, UserOrgType, UserOrganization, + Attachment, + CollectionCipher, + Favorite, + FolderCipher, + Organization, + User, + UserOrgStatus, + UserOrgType, + UserOrganization, }; db_object! { @@ -213,6 +221,7 @@ impl Cipher { FolderCipher::delete_all_by_cipher(&self.uuid, conn)?; CollectionCipher::delete_all_by_cipher(&self.uuid, conn)?; Attachment::delete_all_by_cipher(&self.uuid, conn)?; + Favorite::delete_all_by_cipher(&self.uuid, conn)?; db_run! { conn: { diesel::delete(ciphers::table.filter(ciphers::uuid.eq(&self.uuid))) @@ -340,51 +349,14 @@ impl Cipher { // Returns whether this cipher is a favorite of the specified user. pub fn is_favorite(&self, user_uuid: &str, conn: &DbConn) -> bool { - db_run!{ conn: { - let query = favorites::table - .filter(favorites::user_uuid.eq(user_uuid)) - .filter(favorites::cipher_uuid.eq(&self.uuid)) - .count(); - - query.first::(conn).ok().unwrap_or(0) != 0 - }} + Favorite::is_favorite(&self.uuid, user_uuid, conn) } - // Updates whether this cipher is a favorite of the specified user. + // Sets whether this cipher is a favorite of the specified user. pub fn set_favorite(&self, favorite: Option, user_uuid: &str, conn: &DbConn) -> EmptyResult { - if favorite.is_none() { - // No change requested. - return Ok(()); - } - - let (old, new) = (self.is_favorite(user_uuid, &conn), favorite.unwrap()); - match (old, new) { - (false, true) => { - User::update_uuid_revision(user_uuid, &conn); - db_run!{ conn: { - diesel::insert_into(favorites::table) - .values(( - favorites::user_uuid.eq(user_uuid), - favorites::cipher_uuid.eq(&self.uuid), - )) - .execute(conn) - .map_res("Error adding favorite") - }} - } - (true, false) => { - User::update_uuid_revision(user_uuid, &conn); - db_run!{ conn: { - diesel::delete( - favorites::table - .filter(favorites::user_uuid.eq(user_uuid)) - .filter(favorites::cipher_uuid.eq(&self.uuid)) - ) - .execute(conn) - .map_res("Error removing favorite") - }} - } - // Otherwise, the favorite status is already what it should be. - _ => Ok(()) + match favorite { + None => Ok(()), // No change requested. + Some(status) => Favorite::set_favorite(status, &self.uuid, user_uuid, conn), } } diff --git a/src/db/models/favorite.rs b/src/db/models/favorite.rs new file mode 100644 index 00000000..f419e07d --- /dev/null +++ b/src/db/models/favorite.rs @@ -0,0 +1,83 @@ +use super::{Cipher, User}; + +db_object! { + #[derive(Debug, Identifiable, Queryable, Insertable, Associations)] + #[table_name = "favorites"] + #[belongs_to(User, foreign_key = "user_uuid")] + #[belongs_to(Cipher, foreign_key = "cipher_uuid")] + #[primary_key(user_uuid, cipher_uuid)] + pub struct Favorite { + pub user_uuid: String, + pub cipher_uuid: String, + } +} + +use crate::db::DbConn; + +use crate::api::EmptyResult; +use crate::error::MapResult; + +impl Favorite { + // Returns whether the specified cipher is a favorite of the specified user. + pub fn is_favorite(cipher_uuid: &str, user_uuid: &str, conn: &DbConn) -> bool { + db_run!{ conn: { + let query = favorites::table + .filter(favorites::cipher_uuid.eq(cipher_uuid)) + .filter(favorites::user_uuid.eq(user_uuid)) + .count(); + + query.first::(conn).ok().unwrap_or(0) != 0 + }} + } + + // Sets whether the specified cipher is a favorite of the specified user. + pub fn set_favorite(favorite: bool, cipher_uuid: &str, user_uuid: &str, conn: &DbConn) -> EmptyResult { + let (old, new) = (Self::is_favorite(cipher_uuid, user_uuid, &conn), favorite); + match (old, new) { + (false, true) => { + User::update_uuid_revision(user_uuid, &conn); + db_run!{ conn: { + diesel::insert_into(favorites::table) + .values(( + favorites::user_uuid.eq(user_uuid), + favorites::cipher_uuid.eq(cipher_uuid), + )) + .execute(conn) + .map_res("Error adding favorite") + }} + } + (true, false) => { + User::update_uuid_revision(user_uuid, &conn); + db_run!{ conn: { + diesel::delete( + favorites::table + .filter(favorites::user_uuid.eq(user_uuid)) + .filter(favorites::cipher_uuid.eq(cipher_uuid)) + ) + .execute(conn) + .map_res("Error removing favorite") + }} + } + // Otherwise, the favorite status is already what it should be. + _ => Ok(()) + } + } + + // Delete all favorite entries associated with the specified cipher. + pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult { + db_run! { conn: { + diesel::delete(favorites::table.filter(favorites::cipher_uuid.eq(cipher_uuid))) + .execute(conn) + .map_res("Error removing favorites by cipher") + }} + } + + // Delete all favorite entries associated with the specified user. + pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { + db_run! { conn: { + diesel::delete(favorites::table.filter(favorites::user_uuid.eq(user_uuid))) + .execute(conn) + .map_res("Error removing favorites by user") + }} + } +} diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs index 7d8951cc..1e5349f3 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -1,21 +1,21 @@ mod attachment; mod cipher; -mod device; -mod folder; -mod user; - mod collection; +mod device; +mod favorite; +mod folder; +mod org_policy; mod organization; mod two_factor; -mod org_policy; +mod user; pub use self::attachment::Attachment; pub use self::cipher::Cipher; pub use self::collection::{Collection, CollectionCipher, CollectionUser}; pub use self::device::Device; +pub use self::favorite::Favorite; pub use self::folder::{Folder, FolderCipher}; -pub use self::organization::Organization; -pub use self::organization::{UserOrgStatus, UserOrgType, UserOrganization}; +pub use self::org_policy::{OrgPolicy, OrgPolicyType}; +pub use self::organization::{Organization, UserOrgStatus, UserOrgType, UserOrganization}; pub use self::two_factor::{TwoFactor, TwoFactorType}; pub use self::user::{Invitation, User}; -pub use self::org_policy::{OrgPolicy, OrgPolicyType}; \ No newline at end of file diff --git a/src/db/models/user.rs b/src/db/models/user.rs index 1965bf2b..471e09e3 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -128,7 +128,7 @@ impl User { } } -use super::{Cipher, Device, Folder, TwoFactor, UserOrgType, UserOrganization}; +use super::{Cipher, Device, Favorite, Folder, TwoFactor, UserOrgType, UserOrganization}; use crate::db::DbConn; use crate::api::EmptyResult; @@ -205,6 +205,7 @@ impl User { UserOrganization::delete_all_by_user(&self.uuid, conn)?; Cipher::delete_all_by_user(&self.uuid, conn)?; + Favorite::delete_all_by_user(&self.uuid, conn)?; Folder::delete_all_by_user(&self.uuid, conn)?; Device::delete_all_by_user(&self.uuid, conn)?; TwoFactor::delete_all_by_user(&self.uuid, conn)?;