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

ZMS-[9x] Automatic API generation #535

Merged
merged 32 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e4c30ca
api.js added endpoint for generating openapi docs. added new info to …
NickOvt Sep 28, 2023
d2caad5
try to first generate json representation of the api docs
NickOvt Sep 28, 2023
a69c6ad
add initial Joi Object parsing
NickOvt Oct 2, 2023
e7a3c72
api.js make generation dynamic. messages.js add schemas from separate…
NickOvt Oct 3, 2023
f3fe6cb
add additions to schemas. Add new schemas to messages.js and also add…
NickOvt Oct 4, 2023
cccd2c0
add initial openapi doc yml file generation
NickOvt Oct 6, 2023
c58a969
remove manual yaml parsing with js-yaml JSON -> YAML parsing
NickOvt Oct 10, 2023
d57405c
fix replaceWithRefs and parseComponentsDecoupled functions, refactor,…
NickOvt Oct 12, 2023
e5e72cb
add support for another endpoint
NickOvt Oct 12, 2023
09632a5
move big code from api.js to tools
NickOvt Oct 12, 2023
dc3e7ee
fix array type representation, fix response objects, add necessary da…
NickOvt Oct 13, 2023
35f19d5
Merge branch 'master' into ZMS-87
NickOvt Oct 16, 2023
9ba060c
redo include logic into exclude login
NickOvt Oct 17, 2023
77bbe9e
Merge branch 'ZMS-87' of github.com:nodemailer/wildduck into ZMS-87
NickOvt Oct 17, 2023
d6aae5f
fix api generation in tools.js to accomodate new naming of objects
NickOvt Oct 18, 2023
06b9a45
fix messages.js, add structuredClone check in tools.js
NickOvt Oct 18, 2023
419e3ec
fix structured clone definition
NickOvt Oct 19, 2023
740ae00
Merge branch 'master' of github.com:nodemailer/wildduck into ZMS-87
NickOvt Oct 19, 2023
b5df32a
add one endpoint in messages.js to the api generation
NickOvt Oct 19, 2023
e8513d4
messages.js add one more endpoint to API generation
NickOvt Oct 27, 2023
73b96e0
add response to prev commit. Add new endpoint to API generation. Arch…
NickOvt Oct 27, 2023
417c093
finish with post endpoints in messages.js
NickOvt Oct 30, 2023
1db7528
added general request and response schemas. Also added req and res sc…
NickOvt Nov 2, 2023
71c6496
add multiple GET endpoints to API generation and changed them to new …
NickOvt Nov 2, 2023
00a5afb
fix incorrect import of successRes
NickOvt Nov 2, 2023
fd310ae
fix mailboxes.js
NickOvt Nov 2, 2023
2d31d2b
refactor general-schemas.js. Fix searchSchema in messages.js. Mailbox…
NickOvt Nov 2, 2023
5e4a20a
tools.js rename methodObj in API generation to operationObj
NickOvt Nov 2, 2023
9234b0d
tools.js api generation remove string fallbacks
NickOvt Nov 2, 2023
70ebdbc
messages.js finish with GET endpoints, addition to API doc generation
NickOvt Nov 3, 2023
f85fccc
Merge branch 'master' into ZMS-87
NickOvt Nov 20, 2023
4ac3072
remove yaml import
NickOvt Nov 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions api.js
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,17 @@ module.exports = done => {
);
}

server.get(
{ path: '/openapi', name: 'openapi-docs-generation' },
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');

const routes = server.router.getRoutes();

tools.generateAPiDocs(routes);
})
);

server.on('error', err => {
if (!started) {
started = true;
Expand Down
28 changes: 19 additions & 9 deletions lib/api/mailboxes.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,19 +246,29 @@ module.exports = (db, server, mailboxHandler) => {
);

server.post(
'/users/:user/mailboxes',
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');

const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
{
path: '/users/:user/mailboxes',
pathParams: { user: Joi.string().hex().lowercase().length(24).required().description('This is the user path param') },
description: 'Some description for the API path',
requestBody: {
path: Joi.string()
.regex(/\/{2,}|\/$/, { invert: true })
.required(),
hidden: booleanSchema.default(false),
retention: Joi.number().min(0),
sess: sessSchema,
ip: sessIPSchema
retention: Joi.number().min(0)
},
queryParams: { sess: sessSchema, ip: sessIPSchema },
tags: ['Mailboxes']
},
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');

const { pathParams, requestBody, queryParams } = req.route.spec;

const schema = Joi.object({
...pathParams,
...requestBody,
...queryParams
});

const result = schema.validate(req.params, {
Expand Down
212 changes: 112 additions & 100 deletions lib/api/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const { getMongoDBQuery /*, getElasticSearchQuery*/ } = require('../search-query
//const { getClient } = require('../elasticsearch');

const BimiHandler = require('../bimi-handler');
const { Address, AddressOptionalNameArray, Header, Attachment, ReferenceWithAttachments, Bimi } = require('../schemas/request/messages-schemas');

module.exports = (db, server, messageHandler, userHandler, storageHandler, settingsHandler) => {
let maildrop = new Maildropper({
Expand Down Expand Up @@ -1529,114 +1530,91 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti
);

server.post(
'/users/:user/mailboxes/:mailbox/messages',
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');
{
path: '/users/:user/mailboxes/:mailbox/messages',
summary: 'Upload Message',
description:
'This method allows to upload either an RFC822 formatted message or a message structure to a mailbox. Raw message is stored unmodified, no headers are added or removed. If you want to generate the uploaded message from structured data fields, then do not use the raw property.',
validationObjs: {
pathParams: {
user: Joi.string().hex().lowercase().length(24).required(),
mailbox: Joi.string().hex().lowercase().length(24).required()
},
requestBody: {
date: Joi.date(),
unseen: booleanSchema.default(false),
flagged: booleanSchema.default(false),
draft: booleanSchema.default(false),

const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
mailbox: Joi.string().hex().lowercase().length(24).required(),
date: Joi.date(),
unseen: booleanSchema.default(false),
flagged: booleanSchema.default(false),
draft: booleanSchema.default(false),
raw: Joi.binary().max(consts.MAX_ALLOWED_MESSAGE_SIZE).empty(''),

raw: Joi.binary().max(consts.MAX_ALLOWED_MESSAGE_SIZE).empty(''),
from: Address,

from: Joi.object().keys({
name: Joi.string().empty('').max(255),
address: Joi.string().email({ tlds: false }).required()
}),
replyTo: Address,

replyTo: Joi.object().keys({
name: Joi.string().empty('').max(255),
address: Joi.string().email({ tlds: false }).required()
}),
to: AddressOptionalNameArray,

to: Joi.array().items(
Joi.object().keys({
name: Joi.string().empty('').max(255),
address: Joi.string().email({ tlds: false }).required()
})
),
cc: AddressOptionalNameArray,

cc: Joi.array().items(
Joi.object().keys({
name: Joi.string().empty('').max(255),
address: Joi.string().email({ tlds: false }).required()
})
),
bcc: AddressOptionalNameArray,

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)
})
),
headers: Joi.array().items(Header),

subject: Joi.string()
.empty('')
.max(2 * 1024),
text: Joi.string()
.empty('')
.max(1024 * 1024),
html: Joi.string()
.empty('')
.max(1024 * 1024),
subject: Joi.string()
.empty('')
.max(2 * 1024),
text: Joi.string()
.empty('')
.max(1024 * 1024),
html: Joi.string()
.empty('')
.max(1024 * 1024),

files: Joi.array().items(Joi.string().hex().lowercase().length(24)),
files: Joi.array().items(Joi.string().hex().lowercase().length(24)),

attachments: Joi.array().items(
Joi.object().keys({
filename: Joi.string().empty('').max(255),
attachments: Joi.array().items(Attachment),

contentType: Joi.string().empty('').max(255),
contentTransferEncoding: Joi.string().empty(''),
contentDisposition: Joi.string().empty('').trim().lowercase().valid('inline', 'attachment'),
cid: Joi.string().empty('').max(255),
metaData: metaDataSchema.label('metaData'),

encoding: Joi.string().empty('').default('base64'),
content: Joi.string().required()
})
),
reference: ReferenceWithAttachments,

metaData: metaDataSchema.label('metaData'),
replacePrevious: Joi.object({
mailbox: Joi.string().hex().lowercase().length(24),
id: Joi.number().required()
}),

reference: Joi.object().keys({
mailbox: Joi.string().hex().lowercase().length(24).required(),
id: Joi.number().required(),
action: Joi.string().valid('reply', 'replyAll', 'forward').required(),
attachments: Joi.alternatives().try(
booleanSchema,
Joi.array().items(
Joi.string()
.regex(/^ATT\d+$/i)
.uppercase()
)
)
}),
bimi: Bimi,

replacePrevious: Joi.object({
mailbox: Joi.string().hex().lowercase().length(24),
id: Joi.number().required()
}),
sess: sessSchema,
ip: sessIPSchema
},
queryParams: {},
response: {
200: {
description: 'Success',
model: Joi.object({
success: Joi.boolean(),
message: Joi.object({
id: Joi.number(),
malbox: Joi.string(),
size: Joi.number()
}),
previousDeleted: Joi.boolean()
})
}
}
},
tags: ['Messages']
},
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');

bimi: Joi.object().keys({
domain: Joi.string().domain().required(),
selector: Joi.string().empty('').max(255)
}),
const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs;

sess: sessSchema,
ip: sessIPSchema
const schema = Joi.object({
...pathParams,
...requestBody,
...queryParams
});

if (!req.params.raw && req.body && (Buffer.isBuffer(req.body) || typeof req.body === 'string')) {
Expand Down Expand Up @@ -1983,18 +1961,52 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti
);

server.post(
'/users/:user/mailboxes/:mailbox/messages/:message/forward',
{
path: '/users/:user/mailboxes/:mailbox/messages/:message/forward',
validationObjs: {
pathParams: {
user: Joi.string().hex().lowercase().length(24).required(),
mailbox: Joi.string().hex().lowercase().length(24).required(),
message: Joi.number().required()
},
queryParams: {},
requestBody: {
target: Joi.number().min(1).max(1000),
addresses: Joi.array().items(Joi.string().email({ tlds: false })),
sess: sessSchema,
ip: sessIPSchema
},
response: {
200: {
description: 'Success',
model: Joi.object({
success: Joi.boolean(),
queueId: Joi.string(),
forwarded: Joi.array().items(
Joi.object({
seq: Joi.string(),
type: Joi.string(),
value: Joi.string()
}).$_setFlag('objectName', 'Forwarded')
)
})
NickOvt marked this conversation as resolved.
Show resolved Hide resolved
}
}
},
summary: 'Forward stored Message',
description:
'This method allows either to re-forward a message to an original forward target or forward it to some other address. This is useful if a user had forwarding turned on but the message was not delivered so you can try again. Forwarding does not modify the original message.',
tags: ['Messages']
},
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).required(),
message: Joi.number().required(),
target: Joi.number().min(1).max(1000),
addresses: Joi.array().items(Joi.string().email({ tlds: false })),
sess: sessSchema,
ip: sessIPSchema
const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs;

const schema = Joi.object({
...pathParams,
...requestBody,
...queryParams
});

const result = schema.validate(req.params, {
Expand Down
60 changes: 60 additions & 0 deletions lib/schemas/request/messages-schemas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use strict';

const Joi = require('joi');
const { booleanSchema } = require('../../schemas');

const Address = Joi.object({
name: Joi.string().empty('').max(255),
address: Joi.string().email({ tlds: false }).required()
}).$_setFlag('objectName', 'Address');

const AddressOptionalName = Joi.object({
name: Joi.string().empty('').max(255),
address: Joi.string().email({ tlds: false }).required()
}).$_setFlag('objectName', 'AddressOptionalName');

const AddressOptionalNameArray = Joi.array().items(AddressOptionalName);

const Header = Joi.object({
key: Joi.string().empty('').max(255),
value: Joi.string()
.empty('')
.max(100 * 1024)
}).$_setFlag('objectName', 'Header');

const Attachment = Joi.object({
filename: Joi.string().empty('').max(255),
contentType: Joi.string().empty('').max(255),
encoding: Joi.string().empty('').default('base64'),
contentTransferEncoding: Joi.string().empty(''),
content: Joi.string().required(),
cid: Joi.string().empty('').max(255)
}).$_setFlag('objectName', 'Attachment');

const ReferenceWithAttachments = Joi.object({
mailbox: Joi.string().hex().lowercase().length(24).required(),
id: Joi.number().required(),
action: Joi.string().valid('reply', 'replyAll', 'forward').required(),
attachments: Joi.alternatives().try(
booleanSchema,
Joi.array().items(
Joi.string()
.regex(/^ATT\d+$/i)
.uppercase()
)
)
}).$_setFlag('objectName', 'ReferenceWithAttachments');

const Bimi = Joi.object({
domain: Joi.string().domain().required(),
selector: Joi.string().empty('').max(255)
}).$_setFlag('objectName', 'Bimi');

module.exports = {
Address,
AddressOptionalNameArray,
Header,
Attachment,
ReferenceWithAttachments,
Bimi
};
Loading
Loading