From b3d48ce9b7e9e73154140987f911499872c633b4 Mon Sep 17 00:00:00 2001 From: Nikolai Ovtsinnikov Date: Tue, 23 Apr 2024 10:48:37 +0300 Subject: [PATCH 1/3] Added submission api endpoint to api docs generation --- lib/api/submit.js | 209 +++++++++++++++++++++++----------------------- 1 file changed, 105 insertions(+), 104 deletions(-) diff --git a/lib/api/submit.js b/lib/api/submit.js index 86561551..47af61c9 100644 --- a/lib/api/submit.js +++ b/lib/api/submit.js @@ -12,8 +12,11 @@ const tools = require('../tools'); const Maildropper = require('../maildropper'); const roles = require('../roles'); const Transform = require('stream').Transform; -const { sessSchema, sessIPSchema, booleanSchema } = require('../schemas'); +const { sessSchema, sessIPSchema, booleanSchema, metaDataSchema } = require('../schemas'); const { preprocessAttachments } = require('../data-url'); +const { userId, mailboxId } = require('../schemas/request/general-schemas'); +const { AddressOptionalName, AddressOptionalNameArray, Header, Attachment, ReferenceWithAttachments } = require('../schemas/request/messages-schemas'); +const { successRes } = require('../schemas/response/general-schemas'); class StreamCollect extends Transform { constructor() { @@ -620,113 +623,111 @@ module.exports = (db, server, messageHandler, userHandler, settingsHandler) => { const submitMessageWrapper = util.promisify(submitMessage); server.post( - { name: 'send', path: '/users/:user/submit' }, + { + name: 'send', + path: '/users/:user/submit', + tags: ['Submission'], + summary: 'Submit a Message for Delivery', + description: 'Use this method to send emails from a user account', + validationObjs: { + requestBody: { + mailbox: mailboxId, + from: AddressOptionalName.description('Addres for the From: header'), + replyTo: AddressOptionalName.description('Address for the Reply-To: header'), + to: Joi.array() + .items( + Joi.object({ + name: Joi.string().empty('').max(255).description('Name of the sender'), + address: Joi.string().email({ tlds: false }).failover('').required().description('Address of the sender') + }).$_setFlag('objectName', 'AddressOptionalName') + ) + .description('Addresses for the To: header'), + + cc: AddressOptionalNameArray.description('Addresses for the Cc: header'), + + bcc: AddressOptionalNameArray.description('Addresses for the Bcc: header'), + + headers: Joi.array() + .items(Header) + .description( + 'Custom headers for the message. If reference message is set then In-Reply-To and References headers are set automatically' + ), + subject: Joi.string() + .empty('') + .max(2 * 1024) + .description('Message subject. If not then resolved from Reference message'), + text: Joi.string() + .empty('') + .max(1024 * 1024) + .description('Plaintext message'), + html: Joi.string() + .empty('') + .max(1024 * 1024) + .description('HTML formatted message'), + attachments: Joi.array().items(Attachment).description('Attachments for the message'), + + meta: metaDataSchema.label('metaData').description('Optional metadata, must be an object or JSON formatted string'), + sess: sessSchema, + ip: sessIPSchema, + reference: ReferenceWithAttachments.description( + 'Optional referenced email. If uploaded message is a reply draft and relevant fields are not provided then these are resolved from the message to be replied to' + ), + // if true then treat this message as a draft + isDraft: booleanSchema.default(false).description('Is the message a draft or not'), + // if set then this message is based on a draft that should be deleted after processing + draft: Joi.object() + .keys({ + mailbox: mailboxId, + id: Joi.number().required().description('Message ID') + }) + .description('Draft message to base this one on'), + sendTime: Joi.date().description('Send time'), + uploadOnly: booleanSchema.default(false).description('If true only uploads the message but does not send it'), + envelope: Joi.object() + .keys({ + from: AddressOptionalName.description('Addres for the From: header'), + to: Joi.array().items( + Joi.object() + .keys({ + name: Joi.string().empty('').max(255).description('Name of the sender'), + address: Joi.string().email({ tlds: false }).required().description('Address of the sender') + }) + .description('Addresses for the To: header') + ) + }) + .description('Optional envelope') + }, + queryParams: {}, + pathParams: { + user: userId + }, + response: { + 200: { + description: 'Success', + model: Joi.object({ + success: successRes, + message: Joi.object({ + mailbox: Joi.string().required().description('Mailbox ID the message was stored to'), + id: Joi.number().description('Message ID in the Mailbox').required(), + queueId: Joi.string().required().description('Queue ID in MTA') + }) + .required() + .description('Information about submitted Message') + .$_setFlag('objectName', 'MessageWithQueueId') + }) + } + } + } + }, tools.responseWrapper(async (req, res) => { res.charSet('utf-8'); - const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), - - mailbox: Joi.string().hex().lowercase().length(24), - - reference: Joi.object().keys({ - mailbox: Joi.string().hex().lowercase().length(24).required(), - id: Joi.number().required(), - action: Joi.string().valid('reply', 'replyAll', 'forward').required() - }), - - // if true then treat this message as a draft - isDraft: booleanSchema.default(false), - - // if set then this message is based on a draft that should be deleted after processing - draft: Joi.object().keys({ - mailbox: Joi.string().hex().lowercase().length(24).required(), - id: Joi.number().required() - }), - - uploadOnly: booleanSchema.default(false), + const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs; - sendTime: Joi.date(), - - envelope: Joi.object().keys({ - from: Joi.object().keys({ - name: Joi.string().empty('').max(255), - address: Joi.string().email({ tlds: false }).required() - }), - to: Joi.array().items( - Joi.object().keys({ - name: Joi.string().empty('').max(255), - address: Joi.string().email({ tlds: false }).required() - }) - ) - }), - - from: Joi.object().keys({ - name: Joi.string().empty('').max(255), - address: Joi.string().email({ tlds: false }).required() - }), - - replyTo: Joi.object().keys({ - name: Joi.string().empty('').max(255), - address: Joi.string().email({ tlds: false }).required() - }), - - to: Joi.array().items( - Joi.object().keys({ - name: Joi.string().empty('').max(255), - address: Joi.string().email({ tlds: false }).required() - }) - ), - - cc: Joi.array().items( - Joi.object().keys({ - name: Joi.string().empty('').max(255), - address: Joi.string().email({ tlds: false }).required() - }) - ), - - bcc: Joi.array().items( - Joi.object().keys({ - name: Joi.string().empty('').max(255), - address: Joi.string().email({ tlds: false }).required() - }) - ), - - headers: Joi.array().items( - Joi.object().keys({ - key: Joi.string().empty('').max(255), - value: Joi.string() - .empty('') - .max(100 * 1024) - }) - ), - - subject: Joi.string() - .empty('') - .max(2 * 1024), - text: Joi.string() - .empty('') - .max(1024 * 1024), - html: Joi.string() - .empty('') - .max(1024 * 1024), - - attachments: Joi.array().items( - Joi.object().keys({ - filename: Joi.string().empty('').max(255), - - contentType: Joi.string().empty('').max(255), - contentTransferEncoding: Joi.string().empty('').trim().lowercase(), - contentDisposition: Joi.string().empty('').trim().lowercase().valid('inline', 'attachment'), - cid: Joi.string().empty('').max(255), - - encoding: Joi.string().empty('').default('base64'), - content: Joi.string().required() - }) - ), - meta: Joi.object().unknown(true), - sess: sessSchema, - ip: sessIPSchema + const schema = Joi.object({ + ...pathParams, + ...requestBody, + ...queryParams }); // extract embedded attachments from HTML From 4dbe5177ea6e0b4fd97f7b749ab96c2aa4011551 Mon Sep 17 00:00:00 2001 From: Nikolai Ovtsinnikov Date: Thu, 9 May 2024 10:20:24 +0300 Subject: [PATCH 2/3] exclude some acme and public endpoins from api docs generation --- api.js | 2 +- lib/api/acme.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api.js b/api.js index f8d1cdca..41c1cdf5 100644 --- a/api.js +++ b/api.js @@ -213,7 +213,7 @@ server.use( // public files server.get( - { name: 'public_get', path: '/public/*' }, + { name: 'public_get', path: '/public/*', exclude: true }, restify.plugins.serveStatic({ directory: Path.join(__dirname, 'public'), default: 'index.html' diff --git a/lib/api/acme.js b/lib/api/acme.js index f3ea777d..b7262fb5 100644 --- a/lib/api/acme.js +++ b/lib/api/acme.js @@ -12,7 +12,7 @@ module.exports = (db, server, routeOptions) => { const acmeChallenge = AcmeChallenge.create({ db: db.database }); server.get( - { name: 'acmeToken', path: '/.well-known/acme-challenge/:token' }, + { name: 'acmeToken', path: '/.well-known/acme-challenge/:token', exclude: true }, responseWrapper(async (req, res) => { res.charSet('utf-8'); From 0e0fe54760fccb289d8dc00dea8ee92e5415afe2 Mon Sep 17 00:00:00 2001 From: Nikolai Ovtsinnikov Date: Thu, 9 May 2024 11:09:08 +0300 Subject: [PATCH 3/3] rename exclude to excludeRoute --- api.js | 2 +- lib/api/acme.js | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/api.js b/api.js index 41c1cdf5..fa9899ff 100644 --- a/api.js +++ b/api.js @@ -213,7 +213,7 @@ server.use( // public files server.get( - { name: 'public_get', path: '/public/*', exclude: true }, + { name: 'public_get', path: '/public/*', excludeRoute: true }, restify.plugins.serveStatic({ directory: Path.join(__dirname, 'public'), default: 'index.html' diff --git a/lib/api/acme.js b/lib/api/acme.js index b7262fb5..b2e60043 100644 --- a/lib/api/acme.js +++ b/lib/api/acme.js @@ -12,7 +12,7 @@ module.exports = (db, server, routeOptions) => { const acmeChallenge = AcmeChallenge.create({ db: db.database }); server.get( - { name: 'acmeToken', path: '/.well-known/acme-challenge/:token', exclude: true }, + { name: 'acmeToken', path: '/.well-known/acme-challenge/:token', excludeRoute: true }, responseWrapper(async (req, res) => { res.charSet('utf-8'); diff --git a/package-lock.json b/package-lock.json index d2f4784e..cf168ebe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,7 +60,7 @@ "restify-cors-middleware2": "2.2.1", "restify-errors": "8.0.2", "restify-logger": "2.0.1", - "restifyapigenerate": "1.2.0", + "restifyapigenerate": "1.2.1", "search-string": "3.1.0", "seq-index": "1.1.0", "smtp-server": "3.13.4", @@ -8695,9 +8695,9 @@ } }, "node_modules/restifyapigenerate": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/restifyapigenerate/-/restifyapigenerate-1.2.0.tgz", - "integrity": "sha512-bSw4rQNnu60El+e4Y8oqzvgZgbtc+VemRc7xlCLPm0zE1VSm+6A/QT1Ob+4lu7E17PF+l+OZBIrCEEgxDl3lIg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/restifyapigenerate/-/restifyapigenerate-1.2.1.tgz", + "integrity": "sha512-38LUDWrsbK14KnhdUwVxjqGCPeY+uPkXKxGLaEPc375YujWMpBl/Sve8XCyvfqoqSXce6fxqQt/tPqN4WVvt6Q==", "engines": { "node": ">=16.0.0" } diff --git a/package.json b/package.json index ee37d8ea..859ce786 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "restify-cors-middleware2": "2.2.1", "restify-errors": "8.0.2", "restify-logger": "2.0.1", - "restifyapigenerate": "1.2.0", + "restifyapigenerate": "1.2.1", "search-string": "3.1.0", "seq-index": "1.1.0", "smtp-server": "3.13.4",