From f3abc061918ea8e4ea7cdda697bc1bc38696a2d7 Mon Sep 17 00:00:00 2001 From: Hamza Mahjoubi Date: Thu, 12 Dec 2024 21:31:13 +0700 Subject: [PATCH] fixup! perf: reduce number of avatar requests Signed-off-by: Hamza Mahjoubi --- appinfo/routes.php | 5 ++++ lib/Controller/AvatarsController.php | 37 ++++++++++++++++++++++++++ lib/IMAP/PreviewEnhancer.php | 21 ++++++++------- src/service/AvatarService.js | 39 ++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 src/service/AvatarService.js diff --git a/appinfo/routes.php b/appinfo/routes.php index d5980a1ffa..eddd1a5ee3 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -275,6 +275,11 @@ 'url' => '/api/messages/{messageId}/smartreply', 'verb' => 'GET' ], + [ + 'name' => 'avatars#url', + 'url' => '/api/avatars/url/{email}', + 'verb' => 'GET' + ], [ 'name' => 'avatars#image', 'url' => '/api/avatars/image/{email}', diff --git a/lib/Controller/AvatarsController.php b/lib/Controller/AvatarsController.php index 4659cb3c30..4dc20790b6 100644 --- a/lib/Controller/AvatarsController.php +++ b/lib/Controller/AvatarsController.php @@ -34,6 +34,43 @@ public function __construct(string $appName, $this->uid = $UserId; } + /** + * @NoAdminRequired + * @NoCSRFRequired + * + * @param string $email + * @return JSONResponse + */ + #[TrapError] + public function url(string $email): JSONResponse { + if (empty($email)) { + return new JSONResponse([], Http::STATUS_BAD_REQUEST); + } + + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + return new JSONResponse([], Http::STATUS_BAD_REQUEST); + } + + $avatar = $this->avatarService->getAvatar($email, $this->uid); + if (is_null($avatar)) { + // No avatar found + $response = new JSONResponse([], Http::STATUS_NOT_FOUND); + + // Debounce this a bit + // (cache for one day) + $response->cacheFor(24 * 60 * 60, false, true); + + return $response; + } + + $response = new JSONResponse($avatar); + + // Let the browser cache this for a week + $response->cacheFor(7 * 24 * 60 * 60, false, true); + + return $response; + } + /** * @NoAdminRequired * @NoCSRFRequired diff --git a/lib/IMAP/PreviewEnhancer.php b/lib/IMAP/PreviewEnhancer.php index 989a10601a..a31e48406b 100644 --- a/lib/IMAP/PreviewEnhancer.php +++ b/lib/IMAP/PreviewEnhancer.php @@ -38,21 +38,21 @@ class PreviewEnhancer { /** @var AvatarService */ private $avatarService; - /** @var string */ - private $UserId; + /** @var string|null */ + private $userId; public function __construct(IMAPClientFactory $clientFactory, ImapMapper $imapMapper, DbMapper $dbMapper, LoggerInterface $logger, AvatarService $avatarService, - string $UserId) { + string $userId) { $this->clientFactory = $clientFactory; $this->imapMapper = $imapMapper; $this->mapper = $dbMapper; $this->logger = $logger; $this->avatarService = $avatarService; - $this->UserId = $UserId; + $this->userId = $userId; } /** @@ -63,9 +63,9 @@ public function __construct(IMAPClientFactory $clientFactory, public function process(Account $account, Mailbox $mailbox, array $messages): array { $needAnalyze = array_reduce($messages, function (array $carry, Message $message) { if ($message->getStructureAnalyzed()) { - // Nothing to do - if ($message->getAvatar() === null) { - $avatar = $this->avatarService->getAvatar($message->getFrom()->first()->getEmail(), $this->UserId); + // Try fetching the avatar if it's not set + if ($message->getAvatar() === null && $message->getFrom()->first() !== null) { + $avatar = $this->avatarService->getAvatar($message->getFrom()->first()->getEmail(), $this->userId); $message->setAvatar($avatar); } return $carry; @@ -112,8 +112,11 @@ public function process(Account $account, Mailbox $mailbox, array $messages): ar $message->setEncrypted($structureData->isEncrypted()); $message->setMentionsMe($structureData->getMentionsMe()); - $avatar = $this->avatarService->getAvatar($message->getFrom()->first()->getEmail(), $this->UserId); - $message->setAvatar($avatar); + if ($message->getFrom()->first() !== null) { + $avatar = $this->avatarService->getAvatar($message->getFrom()->first()->getEmail(), $this->userId); + $message->setAvatar($avatar); + + } return $message; }, $messages)); diff --git a/src/service/AvatarService.js b/src/service/AvatarService.js new file mode 100644 index 0000000000..41f14db09d --- /dev/null +++ b/src/service/AvatarService.js @@ -0,0 +1,39 @@ +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import memoize from 'lodash/fp/memoize.js' +import Axios from '@nextcloud/axios' +import { generateUrl } from '@nextcloud/router' + +export const fetchAvatarUrl = (email) => { + if (email === null) { + return Promise.resolve(undefined) + } + + const url = generateUrl('/apps/mail/api/avatars/url/{email}', { + email, + }) + + return Axios.get(url) + .then((resp) => resp.data) + .then((avatar) => { + if (avatar.isExternal) { + return generateUrl('/apps/mail/api/avatars/image/{email}', { + email, + }) + } else { + return avatar.url + } + }) + .catch((err) => { + if (err.response.status === 404) { + return undefined + } + + return Promise.reject(err) + }) +} + +export const fetchAvatarUrlMemoized = memoize(fetchAvatarUrl)