use crate::storage_error::StorageError; use crate::util::db; use rusqlite::params; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum PendingUserOperationKind { Create, Attach, Release, Purge, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum PendingUserOperationPhase { Prepared, CredentialWritten, RemoteCommitted, LocalCommitted, DatabasePurged, E2eePurged, FilesystemPurged, } impl PendingUserOperationPhase { pub fn as_str(self) -> &'static str { match self { Self::Prepared => "prepared", Self::CredentialWritten => "credential_written", Self::RemoteCommitted => "remote_committed", Self::LocalCommitted => "local_committed", Self::DatabasePurged => "database_purged", Self::E2eePurged => "e2ee_purged", Self::FilesystemPurged => "filesystem_purged", } } fn parse(value: &str) -> Result { match value { "prepared" => Ok(Self::Prepared), "credential_written" => Ok(Self::CredentialWritten), "remote_committed" => Ok(Self::RemoteCommitted), "local_committed" => Ok(Self::LocalCommitted), "database_purged" => Ok(Self::DatabasePurged), "e2ee_purged" => Ok(Self::E2eePurged), "filesystem_purged" => Ok(Self::FilesystemPurged), _ => Err(StorageError::Other( "unknown pending user operation phase".into(), )), } } } impl PendingUserOperationKind { pub fn as_str(self) -> &'static str { match self { Self::Create => "create", Self::Attach => "attach", Self::Release => "release", Self::Purge => "purge", } } fn parse(value: &str) -> Result { match value { "create" => Ok(Self::Create), "attach" => Ok(Self::Attach), "release" => Ok(Self::Release), "purge" => Ok(Self::Purge), _ => Err(StorageError::Other("unknown pending user operation".into())), } } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct PendingUserOperation { pub user_id: i64, pub operation: PendingUserOperationKind, pub username: String, pub public_key: Option, pub private_key_hash: Option, pub reset_token: Option, pub registration_token: Option, pub phase: PendingUserOperationPhase, pub created_at: i64, } pub fn upsert(operation: &PendingUserOperation) -> Result<(), StorageError> { db::with_immediate_transaction(|tx| { tx.execute( r#" INSERT INTO pending_user_operations ( user_id, operation, username, public_key, private_key_hash, reset_token, registration_token, phase, created_at ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9) ON CONFLICT(user_id) DO UPDATE SET operation = excluded.operation, username = excluded.username, public_key = excluded.public_key, private_key_hash = excluded.private_key_hash, reset_token = excluded.reset_token, registration_token = excluded.registration_token, phase = excluded.phase, created_at = excluded.created_at "#, params![ operation.user_id, operation.operation.as_str(), operation.username, operation.public_key, operation.private_key_hash, operation.reset_token, operation.registration_token, operation.phase.as_str(), operation.created_at, ], )?; Ok(()) }) } pub fn get_all() -> Result, StorageError> { db::with_db(|conn| { let mut statement = conn.prepare( "SELECT user_id, operation, username, public_key, private_key_hash, reset_token, registration_token, phase, created_at FROM pending_user_operations ORDER BY created_at", )?; let rows = statement.query_map([], |row| { Ok(( row.get::<_, i64>(0)?, row.get::<_, String>(1)?, row.get::<_, String>(2)?, row.get::<_, Option>(3)?, row.get::<_, Option>(4)?, row.get::<_, Option>(5)?, row.get::<_, Option>(6)?, row.get::<_, String>(7)?, row.get::<_, i64>(8)?, )) })?; rows.map(|row| { let ( user_id, operation, username, public_key, private_key_hash, reset_token, registration_token, phase, created_at, ) = row?; Ok(PendingUserOperation { user_id, operation: PendingUserOperationKind::parse(&operation)?, username, public_key, private_key_hash, reset_token, registration_token, phase: PendingUserOperationPhase::parse(&phase)?, created_at, }) }) .collect() }) } pub fn update_phase(user_id: i64, phase: PendingUserOperationPhase) -> Result<(), StorageError> { db::with_immediate_transaction(|tx| { tx.execute( "UPDATE pending_user_operations SET phase = ?1 WHERE user_id = ?2", params![phase.as_str(), user_id], )?; Ok(()) }) } pub fn remove(user_id: i64) -> Result<(), StorageError> { db::with_immediate_transaction(|tx| { tx.execute( "DELETE FROM pending_user_operations WHERE user_id = ?1", [user_id], )?; Ok(()) }) } pub fn complete_purge(user_id: i64, updated_at: i64) -> Result<(), StorageError> { db::with_immediate_transaction(|tx| { let changed = tx.execute( "UPDATE user_residency SET data_state = 'empty', updated_at = ?2 WHERE user_id = ?1", params![user_id, updated_at], )?; if changed == 0 { return Err(StorageError::Other("user residency was not found".into())); } tx.execute( "DELETE FROM pending_user_operations WHERE user_id = ?1 AND operation = 'purge'", [user_id], )?; Ok(()) }) }