From a543bbcd4bd22182dd1340353854229a6f0589e9 Mon Sep 17 00:00:00 2001 From: GretaD Date: Fri, 6 Mar 2020 16:50:26 +0100 Subject: [PATCH] Add delete action for folders Signed-off-by: GretaD Signed-off-by: Christoph Wurst --- appinfo/routes.php | 5 ++ lib/Contracts/IMailManager.php | 8 +++ lib/Controller/FoldersController.php | 13 ++++ lib/IMAP/FolderMapper.php | 13 ++++ lib/Service/MailManager.php | 17 ++++++ src/components/NavigationFolder.vue | 29 +++++++++ src/service/FolderService.js | 9 +++ src/store/actions.js | 11 +++- src/store/mutations.js | 12 ++++ src/tests/unit/store/mutations.spec.js | 84 ++++++++++++++++++++++++++ 10 files changed, 200 insertions(+), 1 deletion(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index 766f7e683..5529db11f 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -79,6 +79,11 @@ return [ 'url' => '/api/accounts/{accountId}/folders/{folderId}/stats', 'verb' => 'GET' ], + [ + 'name' => 'folders#delete', + 'url' => '/api/accounts/{accountId}/folders/{folderId}', + 'verb' => 'DELETE' + ], [ 'name' => 'messages#downloadAttachment', 'url' => '/api/accounts/{accountId}/folders/{folderId}/messages/{messageId}/attachment/{attachmentId}', diff --git a/lib/Contracts/IMailManager.php b/lib/Contracts/IMailManager.php index f77018fd8..b19eaf2d8 100644 --- a/lib/Contracts/IMailManager.php +++ b/lib/Contracts/IMailManager.php @@ -142,4 +142,12 @@ interface IMailManager { * @return Quota|null */ public function getQuota(Account $account): ?Quota; + + /** + * @param Account $account + * @param string $folderId + * + * @throws ServiceException + */ + public function deleteMailbox(Account $account, string $folderId): void; } diff --git a/lib/Controller/FoldersController.php b/lib/Controller/FoldersController.php index ed13b6dad..6385f44d0 100644 --- a/lib/Controller/FoldersController.php +++ b/lib/Controller/FoldersController.php @@ -234,4 +234,17 @@ class FoldersController extends Controller { return new JSONResponse($this->mailManager->createFolder($account, $name)); } + + /** + * @NoAdminRequired + * @TrapError + * @param int $accountId + * @param string $folderId + * @throws ServiceException + */ + public function delete(int $accountId, string $folderId): JSONResponse { + $account = $this->accountService->find($this->currentUserId, $accountId); + $this->mailManager->deleteMailbox($account, base64_decode($folderId)); + return new JSONResponse(); + } } diff --git a/lib/IMAP/FolderMapper.php b/lib/IMAP/FolderMapper.php index 434ef68ea..6b60e1ed8 100644 --- a/lib/IMAP/FolderMapper.php +++ b/lib/IMAP/FolderMapper.php @@ -219,4 +219,17 @@ class FolderMapper { } } } + + /** + * @param Horde_Imap_Client_Socket $client + * @param string $folderId + * @throws ServiceException + */ + public function delete(Horde_Imap_Client_Socket $client, string $folderId): void { + try { + $client->deleteMailbox($folderId); + } catch (Horde_Imap_Client_Exception $e) { + throw new ServiceException('Could not delete mailbox: '.$e->getMessage(), 0, $e); + } + } } diff --git a/lib/Service/MailManager.php b/lib/Service/MailManager.php index dc3e66784..3182797ce 100644 --- a/lib/Service/MailManager.php +++ b/lib/Service/MailManager.php @@ -373,4 +373,21 @@ class MailManager implements IMailManager { 1024 * (int)($storage['limit'] ?? 0) ); } + + /** + * @param Account $account + * @param string $folderId + * @throws ServiceException + */ + public function deleteMailbox(Account $account, + string $folderId): void { + try { + $mailbox = $this->mailboxMapper->find($account, $folderId); + } catch (DoesNotExistException $e) { + throw new ServiceException("Source mailbox $folderId does not exist", 0, $e); + } + $client = $this->imapClientFactory->getClient($account); + $this->folderMapper->delete($client, $folderId); + $this->mailboxMapper->delete($mailbox); + } } diff --git a/src/components/NavigationFolder.vue b/src/components/NavigationFolder.vue index 33f201692..e81f56d7f 100644 --- a/src/components/NavigationFolder.vue +++ b/src/components/NavigationFolder.vue @@ -70,6 +70,9 @@ @click="clearCache"> {{ t('mail', 'Clear locally cached data, in case there are issues with synchronization.') }} + + {{ t('mail', 'Delete folder') }} + @@ -294,6 +297,32 @@ export default { this.clearCache = false } }, + deleteFolder() { + const id = this.folder.id + logger.info('delete folder', { folder: this.folder }) + OC.dialogs.confirmDestructive( + t('mail', 'The folder and all messages in it will be deleted.', { + folderId: this.folderId, + }), + t('mail', 'Delete folder'), + { + type: OC.dialogs.YES_NO_BUTTONS, + confirm: t('mail', 'Delete folder {folderId}', { folderId: this.folderId }), + confirmClasses: 'error', + cancel: t('mail', 'Cancel'), + }, + (result) => { + if (result) { + return this.$store + .dispatch('deleteFolder', { account: this.account, folder: this.folder }) + .then(() => { + logger.info(`folder ${id} deleted`) + }) + .catch((error) => logger.error('could not delete folder', { error })) + } + } + ) + }, }, } diff --git a/src/service/FolderService.js b/src/service/FolderService.js index c18158d05..d1ec0243e 100644 --- a/src/service/FolderService.js +++ b/src/service/FolderService.js @@ -39,3 +39,12 @@ export function markFolderRead(accountId, folderId) { return Axios.post(url).then((resp) => resp.data) } + +export const deleteFolder = async(accountId, folderId) => { + const url = generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}', { + accountId, + folderId, + }) + + await Axios.delete(url) +} diff --git a/src/store/actions.js b/src/store/actions.js index 90503061e..344b643c1 100644 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -50,7 +50,12 @@ import { update as updateAccount, updateSignature, } from '../service/AccountService' -import { create as createFolder, fetchAll as fetchAllFolders, markFolderRead } from '../service/FolderService' +import { + create as createFolder, + fetchAll as fetchAllFolders, + markFolderRead, + deleteFolder, +} from '../service/FolderService' import { deleteMessage, fetchEnvelope, @@ -147,6 +152,10 @@ export default { throw err }) }, + async deleteFolder({ commit }, { account, folder }) { + await deleteFolder(account.id, folder.id) + commit('removeFolder', { accountId: account.id, folderId: folder.id }) + }, createFolder({ commit }, { account, name }) { return createFolder(account.id, name).then((folder) => { console.debug(`folder ${name} created for account ${account.id}`, { folder }) diff --git a/src/store/mutations.js b/src/store/mutations.js index 37176badb..c2f20bfe0 100644 --- a/src/store/mutations.js +++ b/src/store/mutations.js @@ -110,6 +110,18 @@ export default { account.folders.push(id) }) }, + removeFolder(state, { accountId, folderId }) { + const account = state.accounts[accountId] + const id = normalizedFolderId(accountId, folderId) + Vue.delete(state.folders, id) + account.folders = account.folders.filter((fId) => fId !== id) + account.folders.forEach((fId) => { + const folder = state.folders[fId] + if (folder.folders) { + folder.folders = folder.folders.filter((fId) => fId !== id) + } + }) + }, addEnvelope(state, { accountId, folderId, query, envelope }) { const folder = state.folders[normalizedFolderId(accountId, folderId)] Vue.set(state.envelopes, envelope.uuid, envelope) diff --git a/src/tests/unit/store/mutations.spec.js b/src/tests/unit/store/mutations.spec.js index 9261c4d1d..e2131064d 100644 --- a/src/tests/unit/store/mutations.spec.js +++ b/src/tests/unit/store/mutations.spec.js @@ -254,4 +254,88 @@ describe('Vuex store mutations', () => { }, }) }) + + it('removes a folder', () => { + const state = { + accounts: { + 13: { + accountId: 13, + id: 13, + folders: ['13-INBOX'], + }, + }, + folders: { + '13-INBOX': { + id: 'INBOX', + specialUse: ['inbox'], + specialRole: 'inbox', + }, + }, + } + + mutations.removeFolder(state, { + accountId: 13, + folderId: 'INBOX', + }) + + expect(state).to.deep.equal({ + accounts: { + 13: { + accountId: 13, + id: 13, + folders: [], + }, + }, + folders: {}, + }) + }) + + it('removes a subfolder', () => { + const state = { + accounts: { + 13: { + accountId: 13, + id: 13, + folders: ['13-INBOX'], + }, + }, + folders: { + '13-INBOX': { + id: 'INBOX', + specialUse: ['inbox'], + specialRole: 'inbox', + folders: ['13-INBOX.sub'], + }, + '13-INBOX.sub': { + id: 'INBOX.sub', + specialUse: ['inbox'], + specialRole: 'inbox', + folders: [], + }, + }, + } + + mutations.removeFolder(state, { + accountId: 13, + folderId: 'INBOX.sub', + }) + + expect(state).to.deep.equal({ + accounts: { + 13: { + accountId: 13, + id: 13, + folders: ['13-INBOX'], + }, + }, + folders: { + '13-INBOX': { + id: 'INBOX', + specialUse: ['inbox'], + specialRole: 'inbox', + folders: [], + }, + }, + }) + }) })