Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: mail provider backend #9651

Merged
merged 1 commit into from
Sep 11, 2024
Merged

feature: mail provider backend #9651

merged 1 commit into from
Sep 11, 2024

Conversation

SebastianKrupinski
Copy link
Contributor

@SebastianKrupinski SebastianKrupinski commented May 16, 2024

Initial Mail Provider Implementation

nextcloud/server#5080

@SebastianKrupinski
Copy link
Contributor Author

Todo:

  • Copyright
  • Comments
  • Minor code improvements

@SebastianKrupinski
Copy link
Contributor Author

This PR requires the server PR first...

nextcloud/server#45383

@SebastianKrupinski
Copy link
Contributor Author

TODO: Separate attachment handling for file system files (saved on the system) and string data files (created or loaded in memory)

@SebastianKrupinski
Copy link
Contributor Author

TODO: unit tests

Copy link

github-actions bot commented Jun 2, 2024

Hello there,
Thank you so much for taking the time and effort to create a pull request to our Nextcloud project.

We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process.

Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6

Thank you for contributing to Nextcloud and we hope to hear from you soon!

@SebastianKrupinski
Copy link
Contributor Author

Testing process:

Pull this PR and server PR - nextcloud/server#45383

Make sure you have an email account setup in the mail app.

Then create calendar event with an attendee.

Invitation email should be sent from personal mail account

lib/Provider/Command/MessageSend.php Outdated Show resolved Hide resolved
lib/Provider/Command/MessageSend.php Outdated Show resolved Hide resolved
lib/Provider/MailService.php Outdated Show resolved Hide resolved
lib/Provider/MailService.php Outdated Show resolved Hide resolved
tests/Unit/Provider/MailProviderTest.php Outdated Show resolved Hide resolved
tests/Unit/Provider/MailServiceTest.php Outdated Show resolved Hide resolved
lib/Db/MailAccountMapper.php Outdated Show resolved Hide resolved
@SebastianKrupinski SebastianKrupinski force-pushed the feature/request-803 branch 2 times, most recently from 221745f to 20dc43a Compare July 13, 2024 10:44
Copy link
Contributor

@miaulalala miaulalala left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove all @since comments. Also, comments that only describe what the variable name / return type already documents can be removed, too.

lib/Db/MailAccountMapper.php Show resolved Hide resolved
lib/Provider/Command/MessageSend.php Outdated Show resolved Hide resolved
lib/Provider/MailProvider.php Outdated Show resolved Hide resolved
lib/Provider/MailProvider.php Outdated Show resolved Hide resolved
lib/Provider/MailProvider.php Outdated Show resolved Hide resolved
lib/Provider/MailService.php Outdated Show resolved Hide resolved
lib/Provider/MailService.php Outdated Show resolved Hide resolved
lib/Provider/MailService.php Outdated Show resolved Hide resolved
lib/Service/AccountService.php Outdated Show resolved Hide resolved
*
* @throws ClientException
*/
public function findByUserIdAndAddress(string $userId, string $address): array {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public function findByUserIdAndAddress(string $userId, string $address): array {
public function findByUserIdAndAddress(string $userId, string $email): array {

*
* @param array<int,IAddress> $addresses collection of IAddress objects
*
* @return array<int,array{email: string, label?: string}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @return array<int,array{email: string, label?: string}>
* @return array<int,array{email: ?string, label?: ?string}>

Copy link
Contributor Author

@SebastianKrupinski SebastianKrupinski Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This didn't work, caused more Psalm errors.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ERROR: InvalidArgument - lib/Provider/Command/MessageSend.php:109:5 - Argument 3 of OCA\Mail\Service\OutboxService::saveMessage expects array<int, array<array-key, string>>, but array<int, array{email: null|string, label?: string}> provided (see https://psalm.dev/004)
				$to,

ERROR: InvalidArgument - lib/Provider/Command/MessageSend.php:110:5 - Argument 4 of OCA\Mail\Service\OutboxService::saveMessage expects array<int, array<array-key, string>>, but array<int, array{email: null|string, label?: string}> provided (see https://psalm.dev/004)
				$cc,

ERROR: InvalidArgument - lib/Provider/Command/MessageSend.php:111:5 - Argument 5 of OCA\Mail\Service\OutboxService::saveMessage expects array<int, array<array-key, string>>, but array<int, array{email: null|string, label?: string}> provided (see https://psalm.dev/004)
				$bcc,

#10114

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

	/**
	 * Converts IAddress objects collection to plain array
	 *
	 * @since 4.0.0
	 *
	 * @param array<int,IAddress> $addresses collection of IAddress objects
	 *
	 * @return array<int,array{email: ?string, label?: string}>
	 */
	protected function convertAddressArray(array $addresses): array {
		return array_map(static function (IAddress $address) {
			$label = $address->getLabel();
			return !empty($label)
				? ['email' => $address->getAddress(), 'label' => $label]
				: ['email' => $address->getAddress()];
		}, $addresses);
	}

Please update the function as above.

  • The annotated return type for label is always a string.
  • It's required to introduce a local variable for label, otherwise psalm is unable to detect.

}
}
// determine if required To address is set
if (empty($message->getTo()) || empty($message->getTo()[0]->getAddress())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Easiest way to fix the psalm warning about possibly null: Introduce a local variable for to.

A bit nicer: Move the validation to a function.

	/**
	 * @param IAddress[] $addresses
	 * @throws SendException
	 */
	private function validateTo(array $addresses): void
	{
		if (count($addresses) === 0 || empty($addresses[0]->getAddress())) {
			throw new SendException('Invalid Message Parameter: MUST contain at least one TO address with a valid address');
		}
	}

Copy link
Member

@ChristophWurst ChristophWurst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These seem to be the important changes remaining

lib/Provider/Command/MessageSend.php Outdated Show resolved Hide resolved
public function listServices(string $userId): array {
// retrieve service(s) details from data store
try {
$accounts = $this->accountService->findByUserId($userId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

findByUserId does not throw anything

Copy link
Contributor Author

@SebastianKrupinski SebastianKrupinski Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, findByUserId it self does not throw an error. But wouldn't it be better to catch any and all errors and log them? That way the mail manager does not have to handle any/all the different errors that might occur with the different providers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no reason for a catch-all right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

lib/Provider/MailProvider.php Outdated Show resolved Hide resolved
$entry->getType(),
$entry->getContents()
)->jsonSerialize();
} catch (\Throwable $e) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} catch (\Throwable $e) {
} catch (UploadException $e) {

Copy link
Contributor Author

@SebastianKrupinski SebastianKrupinski Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to catch any and all errors and present them as one type? That way the consuming app/service does not have to handle any/all the different errors that might occur. For instance, the addFileFromString function currently does not thrown a proper UploadException for any database errors, so if the filename is too long or the mime type is too long, this would cause a different error.

$this->mailAttachment->getType(),
$this->mailAttachment->getContents()
)->will(
$this->throwException(new \Exception('Something went wrong'))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$this->throwException(new \Exception('Something went wrong'))
$this->throwException(new UploadException('Something went wrong'))

return array_map(static function ($a) {
return new Account($a);
}, $this->mapper->findByUserIdAndAddress($userId, $address));
} catch (DoesNotExistException $e) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SebastianKrupinski just remove the try-catch. There is no exception to expect from this method. The db exception is not an expected runtime exception either, hence not documented in the mapper method.

image

@kesselb
Copy link
Contributor

kesselb commented Sep 6, 2024

The branch is a bit behind main.
Please merge main (no rebase, no squash) to have a CI run against the latest main.

@ChristophWurst
Copy link
Member

Please merge main (no rebase, no squash) to have a CI run against the latest main.

@SebastianKrupinski to provide the reason for this: it allows us to follow your changes. Every time you change the git history with an amend or a rebase, github is no longer able to diff the changes between our last visit and your last push. Every time you change history, we have to review the full diff. In this PR, that's 1200 LOC.

Hope this makes sense.

Please avoid rebases in PRs like this where reviewers left request for changes.

Copy link
Member

@ChristophWurst ChristophWurst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The remaining must haves

$bcc,
$attachments
);
} catch (\Throwable $e) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not find any documented exception to be thrown by saveMessage. Remove the try-catch.

Copy link
Contributor Author

@SebastianKrupinski SebastianKrupinski Sep 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left this in one in on purpose, the saveMessage function actually spawns a Imap connection, so I though it would be a good idea to remove the local attachments if the function fails to prevent ghost attachment entries in the database.

Removed

private $attachmentService;
/** @var MessageSend */
private $commandSend;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is spamming the logs on CI and when you run tests locally. I strongly suggest to fix this in this PR. The required effort is low.

@SebastianKrupinski
Copy link
Contributor Author

Please merge main (no rebase, no squash) to have a CI run against the latest main.

@SebastianKrupinski to provide the reason for this: it allows us to follow your changes. Every time you change the git history with an amend or a rebase, github is no longer able to diff the changes between our last visit and your last push. Every time you change history, we have to review the full diff. In this PR, that's 1200 LOC.

Hope this makes sense.

Please avoid rebases in PRs like this where reviewers left request for changes.

Sorry, i did a merge the first time and it messed up the app, couldn't get it to run, so after trying to fix it, I had to delete it re-clone it and redo the changes.

kesselb
kesselb previously requested changes Sep 9, 2024
*/
protected function purgeSavedAttachments(array $attachments): void {
foreach ($attachments as $attachment) {
$this->attachmentService->deleteAttachment($attachment->getUserId(), $attachment->getId());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getUserId and getId only work when you pass an LocalAttachment object. You are calling jsonSerialize in Line 95, and therefore we have a plain array here.

I prefer working with real objects rather than plain arrays and therefore recommend removing jsonSerialize in Line 95, add the phpdoc back for purgeSavedAttachmends, and use array_map(static fn(LocalAttachment $attachment) => $attachment->jsonSerialize(), $attachments) in Line 109.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I fixed the oversight with the LocalAttachment. But i left he phpdoc as paslm is still not happy and I don't want miss another deadline because of paslm.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@param array<int, LocalAttachment> $attachments collection of local attachment objects

@SebastianKrupinski
Copy link
Contributor Author

@ChristophWurst

These two comments...

#9651 (comment)
#9651 (comment)

From the selected code block, I think you are looking at a old version of the PR the code was already changed in this commit.

7f85f0d#diff-b2fd4b9c63f5641101d4ec474c50008f95fc16af2c64ae2972a22e607f88932d

image

@ChristophWurst
Copy link
Member

From the selected code block, I think you are looking at a old version of the PR the code was already changed in this commit.

See https://github.com/nextcloud/mail/actions/runs/10755754956/job/29827858067?pr=9651. This is the latest CI run.

@SebastianKrupinski
Copy link
Contributor Author

From the selected code block, I think you are looking at a old version of the PR the code was already changed in this commit.

See nextcloud/mail/actions/runs/10755754956/job/29827858067?pr=9651. This is the latest CI run.

Okay, I found the confusion it was a different variable then what was highlighted. Fixed.

@kesselb kesselb dismissed their stale review September 11, 2024 16:31

the phpdoc is correct now

@ChristophWurst
Copy link
Member

@SebastianKrupinski please rebase & squash into one commit

Signed-off-by: SebastianKrupinski <[email protected]>
@SebastianKrupinski
Copy link
Contributor Author

@ChristophWurst done

@ChristophWurst ChristophWurst merged commit 18f93fd into main Sep 11, 2024
33 of 34 checks passed
@ChristophWurst ChristophWurst deleted the feature/request-803 branch September 11, 2024 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants