Feat: make some events webhook compatible - #12936
Conversation
|
Thanks for opening your first pull request in this repository! ✌️ |
cfb6a98 to
de266d2
Compare
📝 WalkthroughWalkthroughThis PR adds webhook serialization support to message events. MessageDeletedEvent, MessageFlaggedEvent, and MessageSentEvent each implement 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
lib/Events/MessageSentEvent.php (1)
40-43: ⚡ Quick winStabilize the webhook contract with an explicit payload schema.
Serializing
LocalMessagedirectly couples webhook output to internal entity shape and can expose more fields than intended. Prefer a curated flat payload (for example IDs + explicitly selected message fields).lib/Events/NewMessageReceivedEvent.php (1)
27-31: ⚡ Quick winAvoid emitting raw
Messageentities in webhook payloads.Returning the entity directly makes the external webhook schema implicit and fragile. Prefer an explicit, versionable payload with selected fields only.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: c4a4f676-a8aa-41b8-8c4c-d5989f2ddac6
📒 Files selected for processing (5)
lib/Events/MessageDeletedEvent.phplib/Events/MessageFlaggedEvent.phplib/Events/MessageSentEvent.phplib/Events/NewMessageReceivedEvent.phplib/Listener/NewMessagesNotifier.php
8211490 to
d9199cc
Compare
|
@janepie is the target 34? |
Would be nice but I think I can live with 35, let me doublecheck |
229f1a6 to
4c6375e
Compare
|
Checked, 35 is fine! |
| return $this->set; | ||
| } | ||
|
|
||
| public function getWebhookSerializable(): array { |
There was a problem hiding this comment.
Do you have a use case in mind for this event?
I'm just wondering because you cannot use the messageUid for our api because the expects the message id (oc_mail_messages.id). The uid is an identifier on the imap server. The accountId and mailboxId are database ids from nextcloud mail.
mail/lib/Controller/MessageApiController.php
Line 234 in 3800491
With the uid you could directly lookup a message on the imap server, but for that you need the name of the mailbox (which you currently won't pass, only the id but that one the imap server wont know).
There was a problem hiding this comment.
Use case in mind is some hook that reacts whenever a message is flagged. Yeah I guess that would actually need the internal ID to do sth with it, tbh I just forwarded the information already available in the event as I thought that would be fine. Is there an easy way to get the internal id from here?
e7497e4 to
1e9b471
Compare
1e9b471 to
9450589
Compare
9450589 to
341d4db
Compare
|
Sorry @janepie for the slow response 🙈 I'd pushed a slight different version of the change. |
|
All good, I also left it lying around a bit too long 😅 |
bafb0cb to
906c0ff
Compare
|
What kind of tests would you want to have for this? |
906c0ff to
0077e94
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Suppressed comments (4)
lib/Events/MessageSentEvent.php:45
- Webhook field naming is inconsistent with the other events added/updated in this PR:
sendAtvssentAt(used byNewMessageReceivedEvent) andinReplyTovsinReplyToMessageId. For webhook consumers, consistent key names across event types reduces special-casing; consider aligning to a shared naming scheme (e.g.,sentAtandinReplyToMessageId).
public function getWebhookSerializable(): array {
return [
'messageId' => $this->localMessage->getId(),
'accountId' => $this->localMessage->getAccountId(),
'sendAt' => $this->localMessage->getSendAt(),
'subject' => $this->localMessage->getSubject(),
'inReplyTo' => $this->localMessage->getInReplyToMessageId(),
'failed' => $this->localMessage->isFailed()
];
}
lib/Service/MailManager.php:443
- This introduces a silent early return that skips dispatching
MessageFlaggedEvent(and therefore skips downstream behavior) when the message isn’t found in the cache. Even if this is expected to be rare, it will be very hard to diagnose in production without a log entry. Consider logging a warning (including mailbox id/name + uid) before returning so missing-cache situations can be traced.
// Looking the message up by uid is a shortcut to avoid changing this method's
// signature, which the JMAP PR does anyway.
$messages = $this->dbMessageMapper->findByUids($mb, [$uid]);
if (count($messages) < 1) {
// The message should be in the database cache, otherwise the client wouldn't
// know about the uid. Skip the event rather than fail the whole flag operation.
return;
}
tests/Unit/Listener/MessageCacheUpdaterListenerTest.php:49
- These tests now call
mapper->update($message)with aMessageinstance that only has a UID set. In production,update()on a DB entity typically expects the primary key (id) to be set as well; keeping the test entity closer to a real persisted message (e.g., set anid) will make the unit tests more robust against mapper validation changes and better reflect the production contract.
$message = new Message();
$message->setUid(123);
tests/Unit/Listener/MessageCacheUpdaterListenerTest.php:60
- These tests now call
mapper->update($message)with aMessageinstance that only has a UID set. In production,update()on a DB entity typically expects the primary key (id) to be set as well; keeping the test entity closer to a real persisted message (e.g., set anid) will make the unit tests more robust against mapper validation changes and better reflect the production contract.
->method('update')
->with($message);
0077e94 to
8d07b02
Compare
|
I've modified and extended the PR a bit to address some inconsistencies found during review. For example, the messageId in MessageDeletedEvent is actually the message's UID. I hope it's a bit cleaner now, but it needs fresh review. @ChristophWurst @DerDreschner |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Suppressed comments (6)
lib/Events/MessageFlaggedEvent.php:27
- The constructor signature changes from
(… , int $uid, …)to(…, Message $message, …), which is a breaking change for any existing event dispatchers/listeners. To preserve compatibility, consider adding a deprecated alternate constructor/factory (e.g.,fromUid(Account $account, Mailbox $mailbox, int $uid, …)) that creates a lightweightMessagewith just the UID set, while keeping the newMessage-based constructor as the preferred path.
class MessageFlaggedEvent extends Event implements IWebhookCompatibleEvent {
public function __construct(
private Account $account,
private Mailbox $mailbox,
private Message $message,
private string $flag,
private bool $set,
) {
parent::__construct();
}
lib/Events/NewMessageReceivedEvent.php:25
- The event previously appears to have been constructible with only a URI; it now requires
Account,Mailbox, andMessage. If any external/other internal code constructs this event using the old signature, this is a breaking change. Consider supporting the previous constructor shape via an additional named constructor (deprecated) or by making the extra parameters optional when webhook serialization isn’t needed.
class NewMessageReceivedEvent extends Event implements IWebhookCompatibleEvent {
public function __construct(
private Account $account,
private Mailbox $mailbox,
private Message $message,
private string $uri,
) {
parent::__construct();
}
lib/Events/MessageDeletedEvent.php:24
- This changes the public API from
messageId/getMessageId()touid/getUid(). If this event is consumed outside this PR’s updated call sites, it’s a breaking change. Consider keeping a deprecatedgetMessageId(): intmethod that forwards togetUid(), and (optionally) accepting the old constructor parameter name via a deprecated named constructor/factory to ease upgrade.
class MessageDeletedEvent extends Event implements IWebhookCompatibleEvent {
public function __construct(
private Account $account,
private Mailbox $mailbox,
private int $uid,
) {
parent::__construct();
}
lib/Events/MessageDeletedEvent.php:36
- This changes the public API from
messageId/getMessageId()touid/getUid(). If this event is consumed outside this PR’s updated call sites, it’s a breaking change. Consider keeping a deprecatedgetMessageId(): intmethod that forwards togetUid(), and (optionally) accepting the old constructor parameter name via a deprecated named constructor/factory to ease upgrade.
public function getUid(): int {
return $this->uid;
}
lib/Service/MailManager.php:443
- This silently skips dispatching
MessageFlaggedEventwhen the message isn’t in the DB cache. That can make webhook/flag-related behavior hard to diagnose in production. Consider logging at least a warning/debug entry including mailbox + UID so operators can understand why expected webhook/cache updates didn’t occur.
// Looking the message up by uid is a shortcut to avoid changing this method's
// signature, which the JMAP PR does anyway.
$messages = $this->dbMessageMapper->findByUids($mb, [$uid]);
if (count($messages) < 1) {
// The message should be in the database cache, otherwise the client wouldn't
// know about the uid. Skip the event rather than fail the whole flag operation.
return;
}
lib/Events/MessageSentEvent.php:44
- The webhook payload sources
accountIdfromLocalMessageeven though the event already carries anAccount. For consistency with the other webhook events (which use$this->account->getId()), and to avoid cases whereLocalMessage::getAccountId()might be unset on non-persisted instances, consider using theAccountobject as the source of truth. Also, the timestamp keysendAtdiffers fromsentAtused elsewhere; if these represent the same concept, standardizing the key would reduce consumer confusion.
public function getWebhookSerializable(): array {
// No local message id: the row is deleted right after a successful send
return [
'accountId' => $this->localMessage->getAccountId(),
'inReplyToRfcMessageId' => $this->localMessage->getInReplyToMessageId(),
'sendAt' => $this->localMessage->getSendAt(),
'subject' => $this->localMessage->getSubject(),
];
}
| public function getUid(): int { | ||
| return $this->uid; | ||
| return $this->message->getUid(); | ||
| } |
Assisted-by: ClaudeCode:claude-opus-5 Signed-off-by: Jana Peper <jana.peper@nextcloud.com> Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
The messageId in MessageDeletedEvent is actually the message's uid. Assisted-by: ClaudeCode:claude-opus-5 Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
8d07b02 to
fb166ee
Compare
Makes these event types webhook compatible: