folder.rs (6534B)
1 use super::User; 2 use crate::util; 3 use chrono::{NaiveDateTime, Utc}; 4 use diesel::result::{self, DatabaseErrorKind}; 5 use serde_json::Value; 6 7 db_object! { 8 #[derive(AsChangeset, Insertable, Queryable)] 9 #[diesel(table_name = folders)] 10 pub struct Folder { 11 pub uuid: String, 12 created_at: NaiveDateTime, 13 pub updated_at: NaiveDateTime, 14 pub user_uuid: String, 15 pub name: String, 16 } 17 18 #[derive(Insertable, Queryable)] 19 #[diesel(table_name = folders_ciphers)] 20 pub struct FolderCipher { 21 cipher_uuid: String, 22 folder_uuid: String, 23 } 24 } 25 26 /// Local methods 27 impl Folder { 28 pub fn new(user_uuid: String, name: String) -> Self { 29 let now = Utc::now().naive_utc(); 30 Self { 31 uuid: util::get_uuid(), 32 created_at: now, 33 updated_at: now, 34 35 user_uuid, 36 name, 37 } 38 } 39 pub fn to_json(&self) -> Value { 40 use util::format_date; 41 json!({ 42 "id": self.uuid, 43 "revisionDate": format_date(&self.updated_at), 44 "name": self.name, 45 "object": "folder", 46 }) 47 } 48 } 49 50 impl FolderCipher { 51 pub fn new(folder_uuid: &str, cipher_uuid: &str) -> Self { 52 Self { 53 folder_uuid: folder_uuid.to_owned(), 54 cipher_uuid: cipher_uuid.to_owned(), 55 } 56 } 57 } 58 59 use crate::api::EmptyResult; 60 use crate::db::DbConn; 61 use crate::error::MapResult; 62 63 /// Database methods 64 impl Folder { 65 pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { 66 User::update_uuid_revision(&self.user_uuid, conn).await; 67 self.updated_at = Utc::now().naive_utc(); 68 db_run! { conn: 69 { 70 match diesel::replace_into(folders::table) 71 .values(FolderDb::to_db(self)) 72 .execute(conn) 73 { 74 Ok(_) => Ok(()), 75 // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. 76 Err(result::Error::DatabaseError(DatabaseErrorKind::ForeignKeyViolation, _)) => { 77 diesel::update(folders::table) 78 .filter(folders::uuid.eq(&self.uuid)) 79 .set(FolderDb::to_db(self)) 80 .execute(conn) 81 .map_res("Error saving folder") 82 } 83 Err(e) => Err(e.into()), 84 }.map_res("Error saving folder") 85 } 86 } 87 } 88 89 pub async fn delete(&self, conn: &DbConn) -> EmptyResult { 90 User::update_uuid_revision(&self.user_uuid, conn).await; 91 FolderCipher::delete_all_by_folder(&self.uuid, conn).await?; 92 db_run! { conn: { 93 diesel::delete(folders::table.filter(folders::uuid.eq(&self.uuid))) 94 .execute(conn) 95 .map_res("Error deleting folder") 96 }} 97 } 98 99 pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { 100 for folder in Self::find_by_user(user_uuid, conn).await { 101 folder.delete(conn).await?; 102 } 103 Ok(()) 104 } 105 106 pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { 107 db_run! { conn: { 108 folders::table 109 .filter(folders::uuid.eq(uuid)) 110 .first::<FolderDb>(conn) 111 .ok() 112 .from_db() 113 }} 114 } 115 116 pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { 117 db_run! { conn: { 118 folders::table 119 .filter(folders::user_uuid.eq(user_uuid)) 120 .load::<FolderDb>(conn) 121 .expect("Error loading folders") 122 .from_db() 123 }} 124 } 125 } 126 127 impl FolderCipher { 128 pub async fn save(&self, conn: &DbConn) -> EmptyResult { 129 db_run! { conn: 130 { 131 // Not checking for ForeignKey Constraints here. 132 // Table folders_ciphers does not have ForeignKey Constraints which would cause conflicts. 133 // This table has no constraints pointing to itself, but only to others. 134 diesel::replace_into(folders_ciphers::table) 135 .values(FolderCipherDb::to_db(self)) 136 .execute(conn) 137 .map_res("Error adding cipher to folder") 138 } 139 } 140 } 141 142 pub async fn delete(self, conn: &DbConn) -> EmptyResult { 143 db_run! { conn: { 144 diesel::delete( 145 folders_ciphers::table 146 .filter(folders_ciphers::cipher_uuid.eq(self.cipher_uuid)) 147 .filter(folders_ciphers::folder_uuid.eq(self.folder_uuid)), 148 ) 149 .execute(conn) 150 .map_res("Error removing cipher from folder") 151 }} 152 } 153 154 pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult { 155 db_run! { conn: { 156 diesel::delete(folders_ciphers::table.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid))) 157 .execute(conn) 158 .map_res("Error removing cipher from folders") 159 }} 160 } 161 162 async fn delete_all_by_folder(folder_uuid: &str, conn: &DbConn) -> EmptyResult { 163 db_run! { conn: { 164 diesel::delete(folders_ciphers::table.filter(folders_ciphers::folder_uuid.eq(folder_uuid))) 165 .execute(conn) 166 .map_res("Error removing ciphers from folder") 167 }} 168 } 169 170 pub async fn find_by_folder_and_cipher( 171 folder_uuid: &str, 172 cipher_uuid: &str, 173 conn: &DbConn, 174 ) -> Option<Self> { 175 db_run! { conn: { 176 folders_ciphers::table 177 .filter(folders_ciphers::folder_uuid.eq(folder_uuid)) 178 .filter(folders_ciphers::cipher_uuid.eq(cipher_uuid)) 179 .first::<FolderCipherDb>(conn) 180 .ok() 181 .from_db() 182 }} 183 } 184 /// Return a vec with (cipher_uuid, folder_uuid) 185 /// This is used during a full sync so we only need one query for all folder matches. 186 pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<(String, String)> { 187 db_run! { conn: { 188 folders_ciphers::table 189 .inner_join(folders::table) 190 .filter(folders::user_uuid.eq(user_uuid)) 191 .select(folders_ciphers::all_columns) 192 .load::<(String, String)>(conn) 193 .unwrap_or_default() 194 }} 195 } 196 }