From 6d4fbcc077787c70c3596bea4f231b1a81ccac10 Mon Sep 17 00:00:00 2001 From: steven-mpawulo Date: Thu, 20 Aug 2026 09:28:02 +0300 Subject: [PATCH] feat: expose message snoozing via the web API Signed-off-by: steven-mpawulo --- lib/Controller/MessageApiController.php | 41 + openapi.json | 2000 +++++++++++++++++ .../Controller/MessageApiControllerTest.php | 49 + 3 files changed, 2090 insertions(+) create mode 100644 openapi.json diff --git a/lib/Controller/MessageApiController.php b/lib/Controller/MessageApiController.php index 52882b81a4..4d7bfa3a4c 100644 --- a/lib/Controller/MessageApiController.php +++ b/lib/Controller/MessageApiController.php @@ -25,6 +25,7 @@ use OCA\Mail\Service\ItineraryService; use OCA\Mail\Service\MailManager; use OCA\Mail\Service\OutboxService; +use OCA\Mail\Service\SnoozeService; use OCA\Mail\Service\TrustedSenderService; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Http; @@ -68,6 +69,7 @@ public function __construct( private ItineraryService $itineraryService, private TrustedSenderService $trustedSenderService, private DelegationService $delegationService, + private SnoozeService $snoozeService, ) { parent::__construct($appName, $request); $this->userId = $userId; @@ -488,4 +490,43 @@ private function handleAttachments(): array { return $messageAttachments; } + + /** + * Snooze a mail message + * + * @param int $id the message id + * @param int $destMailboxId the destination mail box + * @param int $unixTimestamp the time the message should reappear + * @return DataResponse|DataResponse + * + * 200: Mail Message snoozed successfully + * 404: User, Message, Account or Mailbox not found + * @throws ClientException + * @throws ServiceException + * @throws Throwable + */ + #[ApiRoute(verb: 'POST', url: '/message/{id}/snooze/{destMailboxId}')] + #[NoAdminRequired] + #[NoCSRFRequired] + #[TrapError] + public function snooze(int $id, int $unixTimestamp, int $destMailboxId): DataResponse { + if ($this->userId === null) { + return new DataResponse('Account not found.', Http::STATUS_NOT_FOUND); + } + try { + $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->userId); + $message = $this->mailManager->getMessage($effectiveUserId, $id); + $srcMailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId()); + $dstMailbox = $this->mailManager->getMailbox($effectiveUserId, $destMailboxId); + $srcAccount = $this->accountService->find($effectiveUserId, $srcMailbox->getAccountId()); + $dstAccount = $this->accountService->find($effectiveUserId, $dstMailbox->getAccountId()); + } catch (DoesNotExistException $e) { + return new DataResponse('Message, Account or Mailbox not found', Http::STATUS_NOT_FOUND); + } + + $this->snoozeService->snoozeMessage($message, $unixTimestamp, $srcAccount, $srcMailbox, $dstAccount, $dstMailbox); + $this->delegationService->logDelegatedAction($this->userId, $effectiveUserId, "$this->userId snoozed message <$id> to <$unixTimestamp> on behalf of $effectiveUserId"); + + return new DataResponse('', Http::STATUS_OK); + } } diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000000..ef56b9b85f --- /dev/null +++ b/openapi.json @@ -0,0 +1,2000 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "mail", + "version": "0.0.1", + "description": "💌 A mail app for Nextcloud", + "license": { + "name": "agpl" + } + }, + "components": { + "securitySchemes": { + "basic_auth": { + "type": "http", + "scheme": "basic" + }, + "bearer_auth": { + "type": "http", + "scheme": "bearer" + } + }, + "schemas": { + "AccountListResponse": { + "type": "object", + "required": [ + "id", + "email", + "isDelegated", + "aliases" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "email": { + "type": "string" + }, + "isDelegated": { + "type": "boolean" + }, + "aliases": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "email", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "email": { + "type": "string" + }, + "name": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "IMAPFullMessage": { + "type": "object", + "required": [ + "uid", + "messageId", + "from", + "to", + "replyTo", + "cc", + "bcc", + "subject", + "dateInt", + "flags", + "dispositionNotificationTo", + "hasDkimSignature", + "phishingDetails", + "unsubscribeUrl", + "isOneClickUnsubscribe", + "unsubscribeMailTo", + "scheduling", + "attachments", + "inlineAttachments" + ], + "properties": { + "uid": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "messageId": { + "type": "string" + }, + "from": { + "type": "array", + "items": { + "type": "object", + "required": [ + "label", + "email" + ], + "properties": { + "label": { + "type": "string" + }, + "email": { + "type": "string" + } + } + } + }, + "to": { + "type": "array", + "items": { + "type": "object", + "required": [ + "label", + "email" + ], + "properties": { + "label": { + "type": "string" + }, + "email": { + "type": "string" + } + } + } + }, + "replyTo": { + "type": "array", + "items": { + "type": "object", + "required": [ + "label", + "email" + ], + "properties": { + "label": { + "type": "string" + }, + "email": { + "type": "string" + } + } + } + }, + "cc": { + "type": "array", + "items": { + "type": "object", + "required": [ + "label", + "email" + ], + "properties": { + "label": { + "type": "string" + }, + "email": { + "type": "string" + } + } + } + }, + "bcc": { + "type": "array", + "items": { + "type": "object", + "required": [ + "label", + "email" + ], + "properties": { + "label": { + "type": "string" + }, + "email": { + "type": "string" + } + } + } + }, + "subject": { + "type": "string" + }, + "dateInt": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "flags": { + "type": "object", + "required": [ + "seen", + "flagged", + "answered", + "deleted", + "draft", + "forwarded", + "hasAttachments", + "mdnsent", + "important" + ], + "properties": { + "seen": { + "type": "boolean" + }, + "flagged": { + "type": "boolean" + }, + "answered": { + "type": "boolean" + }, + "deleted": { + "type": "boolean" + }, + "draft": { + "type": "boolean" + }, + "forwarded": { + "type": "boolean" + }, + "hasAttachments": { + "type": "boolean" + }, + "mdnsent": { + "type": "boolean" + }, + "important": { + "type": "boolean" + } + } + }, + "hasHtmlBody": { + "type": "boolean" + }, + "body": { + "type": "string" + }, + "dispositionNotificationTo": { + "type": "string" + }, + "hasDkimSignature": { + "type": "boolean" + }, + "phishingDetails": { + "type": "object", + "required": [ + "checks", + "warning" + ], + "properties": { + "checks": { + "type": "array", + "items": { + "type": "object", + "required": [ + "type", + "isPhishing", + "message", + "additionalData" + ], + "properties": { + "type": { + "type": "string" + }, + "isPhishing": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "additionalData": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + }, + "warning": { + "type": "boolean" + } + } + }, + "unsubscribeUrl": { + "type": "string", + "nullable": true + }, + "isOneClickUnsubscribe": { + "type": "boolean" + }, + "unsubscribeMailTo": { + "type": "string", + "nullable": true + }, + "scheduling": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "messageId", + "method", + "contents" + ], + "properties": { + "id": { + "type": "string", + "nullable": true + }, + "messageId": { + "type": "string" + }, + "method": { + "type": "string" + }, + "contents": { + "type": "string" + } + } + } + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "messageId", + "filename", + "mime", + "size", + "cid", + "disposition" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64", + "minimum": 1 + }, + "messageId": { + "type": "integer", + "format": "int64", + "minimum": 1 + }, + "filename": { + "type": "string" + }, + "mime": { + "type": "string" + }, + "size": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "cid": { + "type": "string", + "nullable": true + }, + "disposition": { + "type": "string" + }, + "downloadUrl": { + "type": "string" + } + } + } + }, + "inlineAttachments": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "messageId", + "filename", + "mime", + "size", + "cid", + "disposition" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64", + "minimum": 1 + }, + "messageId": { + "type": "integer", + "format": "int64", + "minimum": 1 + }, + "filename": { + "type": "string" + }, + "mime": { + "type": "string" + }, + "size": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "cid": { + "type": "string", + "nullable": true + }, + "disposition": { + "type": "string" + }, + "downloadUrl": { + "type": "string" + } + } + } + } + } + }, + "MessageApiAttachment": { + "type": "object", + "required": [ + "name", + "mime", + "size", + "content" + ], + "properties": { + "name": { + "type": "string" + }, + "mime": { + "type": "string" + }, + "size": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "content": { + "type": "string" + } + } + }, + "MessageApiResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/IMAPFullMessage" + }, + { + "type": "object", + "required": [ + "signature", + "id", + "isSenderTrusted", + "smime", + "rawUrl" + ], + "properties": { + "signature": { + "type": "string", + "nullable": true + }, + "itineraries": { + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "id": { + "type": "integer", + "format": "int64", + "minimum": 1 + }, + "isSenderTrusted": { + "type": "boolean" + }, + "smime": { + "type": "object", + "required": [ + "isSigned", + "signatureIsValid", + "isEncrypted" + ], + "properties": { + "isSigned": { + "type": "boolean" + }, + "signatureIsValid": { + "type": "boolean", + "nullable": true + }, + "isEncrypted": { + "type": "boolean" + } + } + }, + "dkimValid": { + "type": "boolean" + }, + "rawUrl": { + "type": "string" + } + } + } + ] + }, + "OCSMeta": { + "type": "object", + "required": [ + "status", + "statuscode" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "totalitems": { + "type": "string" + }, + "itemsperpage": { + "type": "string" + } + } + } + } + }, + "paths": { + "/ocs/v2.php/apps/mail/message/{id}": { + "get": { + "operationId": "message_api-get", + "summary": "Get a mail message with its metadata", + "tags": [ + "message_api" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "the message id", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Message found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/MessageApiResponse" + } + } + } + } + } + } + } + }, + "206": { + "description": "Message could not be decrypted, no \"body\" data returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/MessageApiResponse" + } + } + } + } + } + } + } + }, + "404": { + "description": "Message, Account or Mailbox not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "string" + } + } + } + } + } + } + } + }, + "500": { + "description": "Could not connect to IMAP server", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "string" + } + } + } + } + } + } + } + }, + "401": { + "description": "Current user is not logged in", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/mail/message/{id}/raw": { + "get": { + "operationId": "message_api-get-raw", + "summary": "Get the raw rfc2822 email", + "tags": [ + "message_api" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "the id of the message", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Message found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "string", + "nullable": true + } + } + } + } + } + } + } + }, + "404": { + "description": "Could not find message on IMAP", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "string" + } + } + } + } + } + } + } + }, + "401": { + "description": "Current user is not logged in", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/mail/message/{id}/attachment/{attachmentId}": { + "get": { + "operationId": "message_api-get-attachment", + "summary": "Get a mail message's attachments", + "tags": [ + "message_api" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "the mail id", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "attachmentId", + "in": "path", + "description": "the attachment id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Message found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/MessageApiAttachment" + } + } + } + } + } + } + } + }, + "404": { + "description": "Could not find attachment", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "string" + } + } + } + } + } + } + } + }, + "500": { + "description": "Could not process attachment", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "string" + } + } + } + } + } + } + } + }, + "401": { + "description": "Current user is not logged in", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/mail/account/list": { + "get": { + "operationId": "account_api-list", + "summary": "List all email accounts and their aliases of the user which is currently logged-in", + "tags": [ + "account_api" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Account list", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AccountListResponse" + } + } + } + } + } + } + } + } + }, + "404": { + "description": "User was not logged in", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object" + } + } + } + } + } + } + } + }, + "401": { + "description": "Current user is not logged in", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/mail/ocs/mailboxes": { + "get": { + "operationId": "mailboxes_api-list", + "summary": "List all mailboxes of an account of the user which is currently logged-in", + "tags": [ + "mailboxes_api" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "accountId", + "in": "query", + "description": "the mail account id", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Mailbox list", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + }, + "404": { + "description": "User was not logged in or account doesn't exist", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object" + } + } + } + } + } + } + } + }, + "401": { + "description": "Current user is not logged in", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/mail/ocs/mailboxes/{mailboxId}/messages": { + "get": { + "operationId": "mailboxes_api-list-messages", + "summary": "List the newest messages in a mailbox of the user which is currently logged-in", + "tags": [ + "mailboxes_api" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "mailboxId", + "in": "path", + "description": "the mailbox id", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "cursor", + "in": "query", + "description": "the query cursor", + "schema": { + "type": "integer", + "format": "int64", + "default": null + } + }, + { + "name": "filter", + "in": "query", + "description": "the query filter", + "schema": { + "type": "string", + "default": null + } + }, + { + "name": "limit", + "in": "query", + "description": "the number of messages to be returned, can be left ampty to get all messages", + "schema": { + "type": "integer", + "format": "int64", + "nullable": true, + "default": null + } + }, + { + "name": "view", + "in": "query", + "description": "returns messages in requested view ('singleton' or 'threaded')", + "schema": { + "type": "string", + "default": null + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Message list", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + }, + "404": { + "description": "User was not logged in", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object" + } + } + } + } + } + } + } + }, + "403": { + "description": "User cannot access this mailbox", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object" + } + } + } + } + } + } + } + }, + "401": { + "description": "Current user is not logged in", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/mail/message/send": { + "post": { + "operationId": "message_api-send", + "summary": "Send an email though a mail account that has been configured with Nextcloud Mail", + "tags": [ + "message_api" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "accountId", + "fromEmail", + "subject", + "body", + "isHtml", + "to" + ], + "properties": { + "accountId": { + "type": "integer", + "format": "int64", + "description": "The mail account to use for SMTP" + }, + "fromEmail": { + "type": "string", + "description": "The \"From\" email address or alias email address" + }, + "subject": { + "type": "string", + "description": "The subject" + }, + "body": { + "type": "string", + "description": "The message body" + }, + "isHtml": { + "type": "boolean", + "description": "If the message body contains HTML" + }, + "to": { + "type": "array", + "description": "An array of \"To\" recipients in the format ['label' => 'Name', 'email' => 'Email Address'] or ['email' => 'Email Address']", + "items": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "label": { + "type": "string" + }, + "email": { + "type": "string" + } + } + } + }, + "cc": { + "type": "array", + "default": [], + "description": "An optional array of 'CC' recipients in the format ['label' => 'Name', 'email' => 'Email Address'] or ['email' => 'Email Address']", + "items": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "label": { + "type": "string" + }, + "email": { + "type": "string" + } + } + } + }, + "bcc": { + "type": "array", + "default": [], + "description": "An optional array of 'BCC' recipients in the format ['label' => 'Name', 'email' => 'Email Address'] or ['email' => 'Email Address']", + "items": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "label": { + "type": "string" + }, + "email": { + "type": "string" + } + } + } + }, + "references": { + "type": "string", + "nullable": true, + "default": null, + "description": "An optional string of an RFC2392 \"message-id\" to set the \"Reply-To\" and \"References\" header on sending" + } + } + } + } + } + }, + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "The email was sent", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "string" + } + } + } + } + } + } + } + }, + "202": { + "description": "The email was accepted but not sent by the SMTP server and will be automatically retried", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "string" + } + } + } + } + } + } + } + }, + "400": { + "description": "Message could not be processed", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "string" + } + } + } + } + } + } + } + }, + "403": { + "description": "No \"Sent\" mailbox set for account", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "string" + } + } + } + } + } + } + } + }, + "404": { + "description": "Alias email not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "string" + } + } + } + } + } + } + } + }, + "500": { + "description": "SMTP error", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "string" + } + } + } + } + } + } + } + }, + "401": { + "description": "Current user is not logged in", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/mail/message/{id}/snooze/{destMailboxId}": { + "post": { + "operationId": "message_api-snooze", + "summary": "Snooze a mail message", + "tags": [ + "message_api" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "unixTimestamp" + ], + "properties": { + "unixTimestamp": { + "type": "integer", + "format": "int64", + "description": "the time the message should reappear" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "description": "the message id", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "destMailboxId", + "in": "path", + "description": "the destination mail box", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Mail Message snoozed successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "string" + } + } + } + } + } + } + } + }, + "404": { + "description": "User, Message, Account or Mailbox not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "string" + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "Current user is not logged in", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + } + }, + "tags": [] +} diff --git a/tests/Unit/Controller/MessageApiControllerTest.php b/tests/Unit/Controller/MessageApiControllerTest.php index 62e9f4ece5..a29b0ab9c8 100644 --- a/tests/Unit/Controller/MessageApiControllerTest.php +++ b/tests/Unit/Controller/MessageApiControllerTest.php @@ -32,6 +32,7 @@ use OCA\Mail\Service\ItineraryService; use OCA\Mail\Service\MailManager; use OCA\Mail\Service\OutboxService; +use OCA\Mail\Service\SnoozeService; use OCA\Mail\Service\TrustedSenderService; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Http; @@ -60,6 +61,7 @@ class MessageApiControllerTest extends TestCase { private MockObject|ItineraryService $itineraryService; private TrustedSenderService|MockObject $trustedSenderService; private DelegationService|MockObject $delegationService; + private SnoozeService|MockObject $snoozeService; private MessageApiController $controller; private string $fromEmail = 'john@test.com'; private int $accountId = 1; @@ -87,6 +89,7 @@ protected function setUp(): void { $this->itineraryService = $this->createMock(ItineraryService::class); $this->trustedSenderService = $this->createMock(TrustedSenderService::class); $this->delegationService = $this->createMock(DelegationService::class); + $this->snoozeService = $this->createMock(SnoozeService::class); $this->delegationService->method('resolveAccountUserId')->willReturn($this->userId); $this->delegationService->method('resolveMessageUserId')->willReturn($this->userId); @@ -106,6 +109,7 @@ protected function setUp(): void { $this->itineraryService, $this->trustedSenderService, $this->delegationService, + $this->snoozeService, ); $mailAccount = new MailAccount(); @@ -133,6 +137,7 @@ public function testGet(bool $encrypted, bool $signed, array $json): void { $client = $this->createMock(\Horde_Imap_Client_Socket::class); $imapMessage = $this->createMock(IMAPMessage::class); + $this->logger->expects(self::never()) ->method('warning'); $this->logger->expects(self::never()) @@ -762,4 +767,48 @@ public function testNoAccount(): void { $this->assertEquals($expected, $actual); } + + public function testSnooze(): void { + $destMailboxId = 99; + $unixTimestamp = 1755600000; + + $message = new Message(); + $message->setId($this->messageId); + $message->setMailboxId($this->mailboxId); + $message->setUid(1); + + $srcMailbox = new Mailbox(); + $srcMailbox->setId($this->mailboxId); + $srcMailbox->setAccountId($this->accountId); + + $dstMailbox = new Mailbox(); + $dstMailbox->setId($destMailboxId); + $dstMailbox->setAccountId($this->accountId); + + $this->mailManager->expects(self::once()) + ->method('getMessage') + ->with($this->userId, $this->messageId) + ->willReturn($message); + $this->mailManager->expects(self::exactly(2)) + ->method('getMailbox') + ->willReturnMap([ + [$this->userId, $this->mailboxId, $srcMailbox], + [$this->userId, $destMailboxId, $dstMailbox], + ]); + $this->accountService->expects(self::exactly(2)) + ->method('find') + ->with($this->userId, $this->accountId) + ->willReturn($this->account); + $this->snoozeService->expects(self::once()) + ->method('snoozeMessage') + ->with($message, $unixTimestamp, $this->account, $srcMailbox, $this->account, $dstMailbox); + $this->delegationService->expects(self::once()) + ->method('logDelegatedAction') + ->with($this->userId, $this->userId, "$this->userId snoozed message <$this->messageId> to <$unixTimestamp> on behalf of $this->userId"); + + $expected = new DataResponse('', Http::STATUS_OK); + $actual = $this->controller->snooze($this->messageId, $unixTimestamp, $destMailboxId); + + $this->assertEquals($expected, $actual); + } }