Add the Delete Send API (#584)

This commit is contained in:
David Perez
2024-01-11 21:23:04 -06:00
committed by Álison Fernandes
parent e6c20e0e88
commit ee688b79e7
11 changed files with 217 additions and 30 deletions

View File

@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.Flow
/**
* Primary access point for disk information related to vault data.
*/
@Suppress("TooManyFunctions")
interface VaultDiskSource {
/**
@@ -43,6 +44,11 @@ interface VaultDiskSource {
*/
suspend fun saveSend(userId: String, send: SyncResponseJson.Send)
/**
* Deletes a send from the data source for the given [userId] and [sendId].
*/
suspend fun deleteSend(userId: String, sendId: String)
/**
* Retrieves all sends from the data source for a given [userId].
*/

View File

@@ -22,6 +22,7 @@ import kotlinx.serialization.json.Json
/**
* Default implementation of [VaultDiskSource].
*/
@Suppress("TooManyFunctions")
class VaultDiskSourceImpl(
private val ciphersDao: CiphersDao,
private val collectionsDao: CollectionsDao,
@@ -140,6 +141,10 @@ class VaultDiskSourceImpl(
)
}
override suspend fun deleteSend(userId: String, sendId: String) {
sendsDao.deleteSend(userId, sendId)
}
override fun getSends(
userId: String,
): Flow<List<SyncResponseJson.Send>> =

View File

@@ -29,6 +29,13 @@ interface SendsDao {
userId: String,
): Flow<List<SendEntity>>
/**
* Deletes the specified send associated with the given [userId] and [sendId]. This will return
* the number of rows deleted by this query.
*/
@Query("DELETE FROM sends WHERE user_id = :userId AND id = :sendId")
suspend fun deleteSend(userId: String, sendId: String): Int
/**
* Deletes all the stored sends associated with the given [userId]. This will return the
* number of rows deleted by this query.

View File

@@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
@@ -157,4 +158,9 @@ interface VaultRepository : VaultLockManager {
* Attempt to remove the password from a send.
*/
suspend fun removePasswordSend(sendId: String): RemovePasswordSendResult
/**
* Attempt to delete a send.
*/
suspend fun deleteSend(sendId: String): DeleteSendResult
}

View File

@@ -30,6 +30,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
@@ -472,6 +473,17 @@ class VaultRepositoryImpl(
)
}
override suspend fun deleteSend(sendId: String): DeleteSendResult {
val userId = requireNotNull(activeUserId)
return sendsService
.deleteSend(sendId)
.onSuccess { vaultDiskSource.deleteSend(userId, sendId) }
.fold(
onSuccess = { DeleteSendResult.Success },
onFailure = { DeleteSendResult.Error },
)
}
private fun storeProfileData(
syncResponse: SyncResponseJson,
) {

View File

@@ -0,0 +1,17 @@
package com.x8bit.bitwarden.data.vault.repository.model
/**
* Models result of deleting a send.
*/
sealed class DeleteSendResult {
/**
* Send delete successfully.
*/
data object Success : DeleteSendResult()
/**
* Generic error while deleting a send.
*/
data object Error : DeleteSendResult()
}

View File

@@ -13,6 +13,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.baseWebSendUrl
import com.x8bit.bitwarden.data.platform.repository.util.takeUntilLoaded
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
@@ -141,6 +142,7 @@ class AddSendViewModel @Inject constructor(
private fun handleInternalAction(action: AddSendAction.Internal): Unit = when (action) {
is AddSendAction.Internal.CreateSendResultReceive -> handleCreateSendResultReceive(action)
is AddSendAction.Internal.UpdateSendResultReceive -> handleUpdateSendResultReceive(action)
is AddSendAction.Internal.DeleteSendResultReceive -> handleDeleteSendResultReceive(action)
is AddSendAction.Internal.RemovePasswordResultReceive -> handleRemovePasswordResultReceive(
action,
)
@@ -206,6 +208,29 @@ class AddSendViewModel @Inject constructor(
}
}
private fun handleDeleteSendResultReceive(
action: AddSendAction.Internal.DeleteSendResultReceive,
) {
when (action.result) {
is DeleteSendResult.Error -> {
mutableStateFlow.update {
it.copy(
dialogState = AddSendState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(),
message = R.string.generic_error_message.asText(),
),
)
}
}
is DeleteSendResult.Success -> {
mutableStateFlow.update { it.copy(dialogState = null) }
sendEvent(AddSendEvent.NavigateBack)
sendEvent(AddSendEvent.ShowToast(message = R.string.send_deleted.asText()))
}
}
}
private fun handleRemovePasswordResultReceive(
action: AddSendAction.Internal.RemovePasswordResultReceive,
) {
@@ -315,25 +340,29 @@ class AddSendViewModel @Inject constructor(
}
private fun handleDeleteClick() {
// TODO Add deletion support (BIT-1435)
sendEvent(AddSendEvent.ShowToast("Not yet implemented".asText()))
onEdit {
mutableStateFlow.update {
it.copy(dialogState = AddSendState.DialogState.Loading(R.string.deleting.asText()))
}
viewModelScope.launch {
val result = vaultRepo.deleteSend(it.sendItemId)
sendAction(AddSendAction.Internal.DeleteSendResultReceive(result))
}
}
}
private fun handleRemovePasswordClick() {
when (val addSendType = state.addSendType) {
AddSendType.AddItem -> Unit
is AddSendType.EditItem -> {
mutableStateFlow.update {
it.copy(
dialogState = AddSendState.DialogState.Loading(
message = R.string.removing_send_password.asText(),
),
)
}
viewModelScope.launch {
val result = vaultRepo.removePasswordSend(addSendType.sendItemId)
sendAction(AddSendAction.Internal.RemovePasswordResultReceive(result))
}
onEdit {
mutableStateFlow.update {
it.copy(
dialogState = AddSendState.DialogState.Loading(
message = R.string.removing_send_password.asText(),
),
)
}
viewModelScope.launch {
val result = vaultRepo.removePasswordSend(it.sendItemId)
sendAction(AddSendAction.Internal.RemovePasswordResultReceive(result))
}
}
}
@@ -493,6 +522,12 @@ class AddSendViewModel @Inject constructor(
(state.viewState as? AddSendState.ViewState.Content)?.let(block)
}
private inline fun onEdit(
crossinline block: (AddSendType.EditItem) -> Unit,
) {
(state.addSendType as? AddSendType.EditItem)?.let(block)
}
private inline fun updateContent(
crossinline block: (
AddSendState.ViewState.Content,
@@ -825,6 +860,11 @@ sealed class AddSendAction {
*/
data class RemovePasswordResultReceive(val result: RemovePasswordSendResult) : Internal()
/**
* Indicates a result for deleting the send has been received.
*/
data class DeleteSendResultReceive(val result: DeleteSendResult) : Internal()
/**
* Indicates that the send item data has been received.
*/