mirror of
https://github.com/dani-garcia/vaultwarden.wiki.git
synced 2026-07-22 19:33:34 +00:00
Add support for archiving items (#6916)
* Add archiving * Update Diesel macros and remove unnecessary SUPPORTED_FEATURE_FLAG * Add IF EXISTS to down.sql migratinos * Rename migration folders, separate logic based on PR threads
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
use chrono::NaiveDateTime;
|
||||
use diesel::prelude::*;
|
||||
|
||||
use super::{CipherId, User, UserId};
|
||||
use crate::api::EmptyResult;
|
||||
use crate::db::schema::archives;
|
||||
use crate::db::DbConn;
|
||||
use crate::error::MapResult;
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = archives)]
|
||||
#[diesel(primary_key(user_uuid, cipher_uuid))]
|
||||
pub struct Archive {
|
||||
pub user_uuid: UserId,
|
||||
pub cipher_uuid: CipherId,
|
||||
pub archived_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
impl Archive {
|
||||
// Returns the date the specified cipher was archived
|
||||
pub async fn get_archived_at(cipher_uuid: &CipherId, user_uuid: &UserId, conn: &DbConn) -> Option<NaiveDateTime> {
|
||||
db_run! { conn: {
|
||||
archives::table
|
||||
.filter(archives::cipher_uuid.eq(cipher_uuid))
|
||||
.filter(archives::user_uuid.eq(user_uuid))
|
||||
.select(archives::archived_at)
|
||||
.first::<NaiveDateTime>(conn).ok()
|
||||
}}
|
||||
}
|
||||
|
||||
// Saves (inserts or updates) an archive record with the provided timestamp
|
||||
pub async fn save(
|
||||
user_uuid: &UserId,
|
||||
cipher_uuid: &CipherId,
|
||||
archived_at: NaiveDateTime,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
User::update_uuid_revision(user_uuid, conn).await;
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
diesel::replace_into(archives::table)
|
||||
.values((
|
||||
archives::user_uuid.eq(user_uuid),
|
||||
archives::cipher_uuid.eq(cipher_uuid),
|
||||
archives::archived_at.eq(archived_at),
|
||||
))
|
||||
.execute(conn)
|
||||
.map_res("Error saving archive")
|
||||
}
|
||||
postgresql {
|
||||
diesel::insert_into(archives::table)
|
||||
.values((
|
||||
archives::user_uuid.eq(user_uuid),
|
||||
archives::cipher_uuid.eq(cipher_uuid),
|
||||
archives::archived_at.eq(archived_at),
|
||||
))
|
||||
.on_conflict((archives::user_uuid, archives::cipher_uuid))
|
||||
.do_update()
|
||||
.set(archives::archived_at.eq(archived_at))
|
||||
.execute(conn)
|
||||
.map_res("Error saving archive")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deletes an archive record for a specific cipher
|
||||
pub async fn delete_by_cipher(user_uuid: &UserId, cipher_uuid: &CipherId, conn: &DbConn) -> EmptyResult {
|
||||
User::update_uuid_revision(user_uuid, conn).await;
|
||||
db_run! { conn: {
|
||||
diesel::delete(
|
||||
archives::table
|
||||
.filter(archives::user_uuid.eq(user_uuid))
|
||||
.filter(archives::cipher_uuid.eq(cipher_uuid))
|
||||
)
|
||||
.execute(conn)
|
||||
.map_res("Error deleting archive")
|
||||
}}
|
||||
}
|
||||
|
||||
/// Return a vec with (cipher_uuid, archived_at)
|
||||
/// This is used during a full sync so we only need one query for all archive matches
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<(CipherId, NaiveDateTime)> {
|
||||
db_run! { conn: {
|
||||
archives::table
|
||||
.filter(archives::user_uuid.eq(user_uuid))
|
||||
.select((archives::cipher_uuid, archives::archived_at))
|
||||
.load::<(CipherId, NaiveDateTime)>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
}
|
||||
}
|
||||
+19
-2
@@ -10,8 +10,8 @@ use diesel::prelude::*;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{
|
||||
Attachment, CollectionCipher, CollectionId, Favorite, FolderCipher, FolderId, Group, Membership, MembershipStatus,
|
||||
MembershipType, OrganizationId, User, UserId,
|
||||
Archive, Attachment, CollectionCipher, CollectionId, Favorite, FolderCipher, FolderId, Group, Membership,
|
||||
MembershipStatus, MembershipType, OrganizationId, User, UserId,
|
||||
};
|
||||
use crate::api::core::{CipherData, CipherSyncData, CipherSyncType};
|
||||
use macros::UuidFromParam;
|
||||
@@ -380,6 +380,11 @@ impl Cipher {
|
||||
} else {
|
||||
self.is_favorite(user_uuid, conn).await
|
||||
});
|
||||
json_object["archivedDate"] = json!(if let Some(cipher_sync_data) = cipher_sync_data {
|
||||
cipher_sync_data.cipher_archives.get(&self.uuid).map_or(Value::Null, |d| Value::String(format_date(d)))
|
||||
} else {
|
||||
self.get_archived_at(user_uuid, conn).await.map_or(Value::Null, |d| Value::String(format_date(&d)))
|
||||
});
|
||||
// These values are true by default, but can be false if the
|
||||
// cipher belongs to a collection or group where the org owner has enabled
|
||||
// the "Read Only" or "Hide Passwords" restrictions for the user.
|
||||
@@ -742,6 +747,18 @@ impl Cipher {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_archived_at(&self, user_uuid: &UserId, conn: &DbConn) -> Option<NaiveDateTime> {
|
||||
Archive::get_archived_at(&self.uuid, user_uuid, conn).await
|
||||
}
|
||||
|
||||
pub async fn set_archived_at(&self, archived_at: NaiveDateTime, user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
Archive::save(user_uuid, &self.uuid, archived_at, conn).await
|
||||
}
|
||||
|
||||
pub async fn unarchive(&self, user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
Archive::delete_by_cipher(user_uuid, &self.uuid, conn).await
|
||||
}
|
||||
|
||||
pub async fn get_folder_uuid(&self, user_uuid: &UserId, conn: &DbConn) -> Option<FolderId> {
|
||||
db_run! { conn: {
|
||||
folders_ciphers::table
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
mod archive;
|
||||
mod attachment;
|
||||
mod auth_request;
|
||||
mod cipher;
|
||||
@@ -17,6 +18,7 @@ mod two_factor_duo_context;
|
||||
mod two_factor_incomplete;
|
||||
mod user;
|
||||
|
||||
pub use self::archive::Archive;
|
||||
pub use self::attachment::{Attachment, AttachmentId};
|
||||
pub use self::auth_request::{AuthRequest, AuthRequestId};
|
||||
pub use self::cipher::{Cipher, CipherId, RepromptType};
|
||||
|
||||
@@ -342,6 +342,16 @@ table! {
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
archives (user_uuid, cipher_uuid) {
|
||||
user_uuid -> Text,
|
||||
cipher_uuid -> Text,
|
||||
archived_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
joinable!(archives -> users (user_uuid));
|
||||
joinable!(archives -> ciphers (cipher_uuid));
|
||||
joinable!(attachments -> ciphers (cipher_uuid));
|
||||
joinable!(ciphers -> organizations (organization_uuid));
|
||||
joinable!(ciphers -> users (user_uuid));
|
||||
@@ -373,6 +383,7 @@ joinable!(auth_requests -> users (user_uuid));
|
||||
joinable!(sso_users -> users (user_uuid));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(
|
||||
archives,
|
||||
attachments,
|
||||
ciphers,
|
||||
ciphers_collections,
|
||||
|
||||
Reference in New Issue
Block a user