Skip to content

Commit

Permalink
fix: perform bulk message actions
Browse files Browse the repository at this point in the history
Signed-off-by: SebastianKrupinski <[email protected]>
  • Loading branch information
SebastianKrupinski committed Oct 27, 2024
1 parent a137752 commit f92842a
Show file tree
Hide file tree
Showing 10 changed files with 314 additions and 44 deletions.
25 changes: 24 additions & 1 deletion lib/Controller/MessagesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AiIntegrations\AiIntegrationsService;
use OCA\Mail\Service\ItineraryService;
use OCA\Mail\Service\MessageOperationService;
use OCA\Mail\Service\SmimeService;
use OCA\Mail\Service\SnoozeService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\JSONResponse;
Expand Down Expand Up @@ -94,7 +97,8 @@ public function __construct(string $appName,
IDkimService $dkimService,
IUserPreferences $preferences,
SnoozeService $snoozeService,
AiIntegrationsService $aiIntegrationService) {
AiIntegrationsService $aiIntegrationService,
private MessageOperationService $messageOperationService) {
parent::__construct($appName, $request);
$this->accountService = $accountService;
$this->mailManager = $mailManager;
Expand Down Expand Up @@ -782,6 +786,25 @@ public function setFlags(int $id, array $flags): JSONResponse {
return new JSONResponse();
}

/**
*
* @NoAdminRequired
*
* @param array<int,int> $identifiers
* @param array<int,string> $flags
*
* @return JSONResponse
*/
#[FrontpageRoute(verb: 'PUT', url: '/api/messages/flags')]

Check warning on line 798 in lib/Controller/MessagesController.php

View check run for this annotation

Codecov / codecov/patch

lib/Controller/MessagesController.php#L798

Added line #L798 was not covered by tests
#[NoAdminRequired]
#[TrapError]
public function changeFlags(array $identifiers, array $flags): JSONResponse {

$this->messageOperationService->changeFlags($this->currentUserId, $identifiers, $flags);

Check warning on line 803 in lib/Controller/MessagesController.php

View check run for this annotation

Codecov / codecov/patch

lib/Controller/MessagesController.php#L803

Added line #L803 was not covered by tests

Check failure on line 803 in lib/Controller/MessagesController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

InvalidArgument

lib/Controller/MessagesController.php:803:83: InvalidArgument: Argument 3 of OCA\Mail\Service\MessageOperationService::changeFlags expects array<string, bool>, but array<int, string> provided (see https://psalm.dev/004)
// TODO: add proper responses
return new JSONResponse();

Check warning on line 805 in lib/Controller/MessagesController.php

View check run for this annotation

Codecov / codecov/patch

lib/Controller/MessagesController.php#L805

Added line #L805 was not covered by tests
}

/**
* @NoAdminRequired
*
Expand Down
19 changes: 19 additions & 0 deletions lib/Db/MailAccountMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,25 @@ public function findById(int $id): MailAccount {
return $this->findEntity($query);
}

/**
* Finds all mail accounts by account ids
*
* @param array<int,int> $identifiers
*
* @return array<int,MailAccount>
*/
public function findByIds(array $identifiers): array {

Check warning on line 76 in lib/Db/MailAccountMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/Db/MailAccountMapper.php#L76

Added line #L76 was not covered by tests

$cmd = $this->db->getQueryBuilder();
$cmd->select('*')
->from($this->getTableName())
->where(
$cmd->expr()->in('id', $cmd->createNamedParameter($identifiers, IQueryBuilder::PARAM_STR_ARRAY), IQueryBuilder::PARAM_STR_ARRAY)
);

Check warning on line 83 in lib/Db/MailAccountMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/Db/MailAccountMapper.php#L78-L83

Added lines #L78 - L83 were not covered by tests

return $this->findEntities($cmd);

Check warning on line 85 in lib/Db/MailAccountMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/Db/MailAccountMapper.php#L85

Added line #L85 was not covered by tests
}

/**
* Finds all Mail Accounts by user id existing for this user
*
Expand Down
22 changes: 22 additions & 0 deletions lib/Db/MessageMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,28 @@ public function findUidsForIds(Mailbox $mailbox, array $ids) {
}, array_chunk($ids, 1000));
}

/**
* @param array<int,int> $identifiers
*
* @return array
*/
public function findMailboxAndUid(array $identifiers): array {

Check warning on line 186 in lib/Db/MessageMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/Db/MessageMapper.php#L186

Added line #L186 was not covered by tests

if ($identifiers === []) {
return [];

Check warning on line 189 in lib/Db/MessageMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/Db/MessageMapper.php#L188-L189

Added lines #L188 - L189 were not covered by tests
}

$cmd = $this->db->getQueryBuilder();
$cmd->select('mailbox_id', 'uid')
->from($this->getTableName())
->where(
$cmd->expr()->in('id', $cmd->createNamedParameter($identifiers, IQueryBuilder::PARAM_STR_ARRAY), IQueryBuilder::PARAM_STR_ARRAY)
);

Check warning on line 197 in lib/Db/MessageMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/Db/MessageMapper.php#L192-L197

Added lines #L192 - L197 were not covered by tests

return $cmd->executeQuery()->fetchAll();

Check warning on line 199 in lib/Db/MessageMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/Db/MessageMapper.php#L199

Added line #L199 was not covered by tests

}

/**
* @param Account $account
*
Expand Down
38 changes: 38 additions & 0 deletions lib/IMAP/MessageMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,44 @@ public function removeFlag(Horde_Imap_Client_Socket $client,
);
}

/**
* @throws Horde_Imap_Client_Exception
*/
public function setFlags(

Check warning on line 441 in lib/IMAP/MessageMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/IMAP/MessageMapper.php#L441

Added line #L441 was not covered by tests
Horde_Imap_Client_Socket $client,
Mailbox $mailbox,
array $uids,
array $addFlags = [],
array $removeFlags = [],
array $replaceFlags = []
): array {

if (count($uids) === 0) {
return [];

Check warning on line 451 in lib/IMAP/MessageMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/IMAP/MessageMapper.php#L450-L451

Added lines #L450 - L451 were not covered by tests
}

$cmd = [];

Check warning on line 454 in lib/IMAP/MessageMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/IMAP/MessageMapper.php#L454

Added line #L454 was not covered by tests

if (count($replaceFlags) > 0) {
$cmd['replace'] = $replaceFlags;

Check warning on line 457 in lib/IMAP/MessageMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/IMAP/MessageMapper.php#L456-L457

Added lines #L456 - L457 were not covered by tests
} else {
if (count($addFlags) > 0) {
$cmd['add'] = $addFlags;

Check warning on line 460 in lib/IMAP/MessageMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/IMAP/MessageMapper.php#L459-L460

Added lines #L459 - L460 were not covered by tests
}
if (count($removeFlags) > 0) {
$cmd['remove'] = $removeFlags;

Check warning on line 463 in lib/IMAP/MessageMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/IMAP/MessageMapper.php#L462-L463

Added lines #L462 - L463 were not covered by tests
}
}

if (count($cmd) > 0) {
$cmd['ids'] = new Horde_Imap_Client_Ids($uids);
$result = $client->store($mailbox->getName(), $cmd);
return $result->ids;

Check warning on line 470 in lib/IMAP/MessageMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/IMAP/MessageMapper.php#L467-L470

Added lines #L467 - L470 were not covered by tests
}

return [];

Check warning on line 473 in lib/IMAP/MessageMapper.php

View check run for this annotation

Codecov / codecov/patch

lib/IMAP/MessageMapper.php#L473

Added line #L473 was not covered by tests
}

/**
* @param Horde_Imap_Client_Socket $client
* @param Mailbox $mailbox
Expand Down
3 changes: 2 additions & 1 deletion lib/Service/MailManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ class MailManager implements IMailManager {
'deleted' => [Horde_Imap_Client::FLAG_DELETED],
'draft' => [Horde_Imap_Client::FLAG_DRAFT],
'recent' => [Horde_Imap_Client::FLAG_RECENT],
'junk' => [Horde_Imap_Client::FLAG_JUNK, 'junk'],
'junk' => [Horde_Imap_Client::FLAG_JUNK],
'notjunk' => [Horde_Imap_Client::FLAG_NOTJUNK],
'mdnsent' => [Horde_Imap_Client::FLAG_MDNSENT],
];

Expand Down
111 changes: 111 additions & 0 deletions lib/Service/MessageOperationService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Mail\Service;

use Horde_Imap_Client_Exception;
use OCA\Mail\Account;
use OCA\Mail\Db\MailAccountMapper;
use OCA\Mail\Db\MailboxMapper;
use OCA\Mail\Db\MessageMapper;
use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\IMAP\MessageMapper as ImapMessageMapper;
use OCA\Mail\Service\MailManager;

class MessageOperationService {

public function __construct(

Check warning on line 23 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L23

Added line #L23 was not covered by tests
protected IMAPClientFactory $clientFactory,
protected MailAccountMapper $accountMapper,
protected MailboxMapper $mailboxMapper,
protected MessageMapper $messageMapper,
protected MailManager $mailManager,
protected ImapMessageMapper $imapMessageMapper
) {}

Check warning on line 30 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L30

Added line #L30 was not covered by tests

// group messages by mailbox ['mailbox_id' => [message_id, message_id]]
protected function groupByMailbox(array $collection) {
return array_reduce($collection, function ($carry, $pair) {
if (!isset($carry[$pair['mailbox_id']])) {
$carry[$pair['mailbox_id']] = [];

Check warning on line 36 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L33-L36

Added lines #L33 - L36 were not covered by tests
}
$carry[$pair['mailbox_id']][] = $pair['uid'];
return $carry;
}, []);

Check warning on line 40 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L38-L40

Added lines #L38 - L40 were not covered by tests
}

// group mailboxes by account ['account_id' => [mailbox object]]
protected function groupByAccount(array $collection) {
return array_reduce($collection, function ($carry, $entry) {
if (!isset($carry[$entry->getAccountId()])) {
$carry[$entry->getAccountId()] = [];

Check warning on line 47 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L44-L47

Added lines #L44 - L47 were not covered by tests
}
$carry[$entry->getAccountId()][] = $entry;
return $carry;
}, []);

Check warning on line 51 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L49-L51

Added lines #L49 - L51 were not covered by tests
}

/**
* @param string $userId system user id
* @param array<int,int> $identifiers message ids
* @param array<string,bool> $flags message flags
*/
public function changeFlags(string $userId, array $identifiers, array $flags): void {

Check warning on line 59 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L59

Added line #L59 was not covered by tests

// retrieve meta data [uid, mailbox_id] for all messages
$messages = $this->groupByMailbox($this->messageMapper->findMailboxAndUid($identifiers));

Check warning on line 62 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L62

Added line #L62 was not covered by tests
// retrieve all mailboxes
$mailboxes = $this->groupByAccount($this->mailboxMapper->findByIds(array_keys($messages)));

Check warning on line 64 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L64

Added line #L64 was not covered by tests
// retrieve all accounts
$accounts = $this->accountMapper->findByIds(array_keys($mailboxes));

Check warning on line 66 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L66

Added line #L66 was not covered by tests

foreach ($accounts as $account) {
$account = new Account($account);

Check warning on line 69 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L68-L69

Added lines #L68 - L69 were not covered by tests
// determine if account belongs to the user and skip if not
if ($account->getUserId() != $userId) {
continue;

Check warning on line 72 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L71-L72

Added lines #L71 - L72 were not covered by tests
}

$client = $this->clientFactory->getClient($account);

Check warning on line 75 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L75

Added line #L75 was not covered by tests

try {
foreach ($mailboxes[$account->getId()] as $mailbox) {
$addFlags = [];
$removeFlags = [];
foreach ($flags as $flag => $value) {
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN);

Check warning on line 82 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L78-L82

Added lines #L78 - L82 were not covered by tests
// Only send system flags to the IMAP server as other flags might not be supported
$imapFlags = $this->mailManager->filterFlags($client, $account, $flag, $mailbox->getName());
if (empty($imapFlags)) {
continue;

Check warning on line 86 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L84-L86

Added lines #L84 - L86 were not covered by tests
}
if ($value) {
$addFlags = array_merge($addFlags, $imapFlags);

Check warning on line 89 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L88-L89

Added lines #L88 - L89 were not covered by tests
} else {
$removeFlags = array_merge($removeFlags, $imapFlags);

Check warning on line 91 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L91

Added line #L91 was not covered by tests
}
}
$this->imapMessageMapper->setFlags(
$client,
$mailbox,
$messages[$mailbox->getId()],
$addFlags,
$removeFlags
);

Check warning on line 100 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L94-L100

Added lines #L94 - L100 were not covered by tests
}
} catch (Horde_Imap_Client_Exception $e) {

Check warning on line 102 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L102

Added line #L102 was not covered by tests
// TODO: Add proper error handling
} finally {
$client->logout();

Check warning on line 105 in lib/Service/MessageOperationService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/MessageOperationService.php#L105

Added line #L105 was not covered by tests
}
}

}

}
46 changes: 31 additions & 15 deletions src/components/EnvelopeList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
}}
</ActionButton>
<ActionButton :close-after-click="true"
@click.prevent="favoriteOrUnfavoriteAll">
@click.prevent="markSelectedFavoriteOrUnfavorite">
<template #icon>
<IconFavorite :size="16" />
</template>
Expand Down Expand Up @@ -414,12 +414,11 @@ export default {
return this.selection.includes(idx)
},
markSelectedSeenOrUnseen() {
const seen = !this.areAllSelectedRead
this.selectedEnvelopes.forEach((envelope) => {
this.$store.dispatch('toggleEnvelopeSeen', {
envelope,
seen,
})
const state = !this.areAllSelectedRead
const envelopes = this.selectedEnvelopes
this.$store.dispatch('markEnvelopesSeenOrUnseen', {
envelopes,
state,
})
this.unselectAll()
},
Expand All @@ -442,6 +441,14 @@ export default {
this.unselectAll()
},
async markSelectionJunk() {
const state = true
const envelopes = this.selectedEnvelopes
this.$store.dispatch('markEnvelopesJunkOrNotJunk', {
envelopes,
state,
})
this.unselectAll()
/*
for (const envelope of this.selectedEnvelopes) {
if (!envelope.flags.$junk) {
await this.$store.dispatch('toggleEnvelopeJunk', {
Expand All @@ -451,8 +458,17 @@ export default {
}
}
this.unselectAll()
*/
},
async markSelectionNotJunk() {
const state = false
const envelopes = this.selectedEnvelopes
this.$store.dispatch('markEnvelopesJunkOrNotJunk', {
envelopes,
state,
})
this.unselectAll()
/*
for (const envelope of this.selectedEnvelopes) {
if (envelope.flags.$junk) {
await this.$store.dispatch('toggleEnvelopeJunk', {
Expand All @@ -462,14 +478,14 @@ export default {
}
}
this.unselectAll()
},
favoriteOrUnfavoriteAll() {
const favFlag = !this.areAllSelectedFavorite
this.selectedEnvelopes.forEach((envelope) => {
this.$store.dispatch('markEnvelopeFavoriteOrUnfavorite', {
envelope,
favFlag,
})
*/
},
markSelectedFavoriteOrUnfavorite() {
const state = !this.areAllSelectedFavorite
const envelopes = this.selectedEnvelopes
this.$store.dispatch('markEnvelopesFavoriteOrUnfavorite', {
envelopes,
state,
})
this.unselectAll()
},
Expand Down
9 changes: 4 additions & 5 deletions src/service/MessageService.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,14 @@ export async function clearCache(accountId, id) {
/**
* Set flags for envelope
*
* @param {int} id
* @param {array} identifiers
* @param {object} flags
*/
export async function setEnvelopeFlags(id, flags) {
const url = generateUrl('/apps/mail/api/messages/{id}/flags', {
id,
})
export async function setEnvelopeFlags(identifiers, flags) {
const url = generateUrl('/apps/mail/api/messages/flags')

return await axios.put(url, {
identifiers,
flags,
})
}
Expand Down
Loading

0 comments on commit f92842a

Please sign in to comment.