From 76d0e8f9e29c09b60129d3c8bacfc1db64328c73 Mon Sep 17 00:00:00 2001 From: NickOvt Date: Fri, 12 Jan 2024 12:54:56 +0200 Subject: [PATCH] feat(api-mailboxes): Mailboxes API endpoints added to automatic API docs generation ZMS-114 (#602) * added List Mailboxes for a User endpoint to API generation * Request Mailbox information endpoint added to API generation * Update Mailbox information endpoint added to API generation * added Delete a Mailbox endpoint to API generation. Add example to Request Mailbox information API endpoint * fix mailbox path param in Request Mailbox information * GetMailboxesResult retention should be not required, fix accidental typo --- lib/api/mailboxes.js | 182 ++++++++++++++++++---- lib/schemas/response/mailboxes-schemas.js | 25 +++ 2 files changed, 176 insertions(+), 31 deletions(-) create mode 100644 lib/schemas/response/mailboxes-schemas.js diff --git a/lib/api/mailboxes.js b/lib/api/mailboxes.js index e1f3d105..680161b3 100644 --- a/lib/api/mailboxes.js +++ b/lib/api/mailboxes.js @@ -9,6 +9,7 @@ const util = require('util'); const { sessSchema, sessIPSchema, booleanSchema } = require('../schemas'); const { userId, mailboxId } = require('../schemas/request/general-schemas'); const { successRes } = require('../schemas/response/general-schemas'); +const { GetMailboxesResult } = require('../schemas/response/mailboxes-schemas'); module.exports = (db, server, mailboxHandler) => { const getMailboxCounter = util.promisify(tools.getMailboxCounter); @@ -17,18 +18,50 @@ module.exports = (db, server, mailboxHandler) => { const createMailbox = mailboxHandler.createAsync.bind(mailboxHandler); server.get( - '/users/:user/mailboxes', + { + path: '/users/:user/mailboxes', + tags: ['Mailboxes'], + summary: 'List Mailboxes for a User', + validationObjs: { + requestBody: {}, + pathParams: { + user: userId + }, + queryParams: { + specialUse: booleanSchema.default(false).description('Should the response include only folders with specialUse flag set.'), + showHidden: booleanSchema.default(false).description('Hidden folders are not included in the listing by default.'), + counters: booleanSchema + .default(false) + .description('Should the response include counters (total + unseen). Counters come with some overhead.'), + sizes: booleanSchema + .default(false) + .description( + 'Should the response include mailbox size in bytes. Size numbers come with a lot of overhead as an aggregated query is ran.' + ), + sess: sessSchema, + ip: sessIPSchema + }, + + response: { + 200: { + description: 'Success', + model: Joi.object({ + success: successRes, + results: Joi.array().items(GetMailboxesResult).description('List of user mailboxes').required() + }) + } + } + } + }, tools.responseWrapper(async (req, res) => { res.charSet('utf-8'); - const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), - specialUse: booleanSchema.default(false), - showHidden: booleanSchema.default(false), - counters: booleanSchema.default(false), - sizes: booleanSchema.default(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, { @@ -329,15 +362,56 @@ module.exports = (db, server, mailboxHandler) => { ); server.get( - '/users/:user/mailboxes/:mailbox', + { + path: '/users/:user/mailboxes/:mailbox', + summary: 'Request Mailbox information', + tags: ['Mailboxes'], + validationObjs: { + requestBody: {}, + queryParams: { + path: Joi.string() + .regex(/\/{2,}|\/$/, { invert: true }) + .description('If mailbox is specified as `resolve` in the path then use this param as mailbox path instead of the given mailbox id.'), + sess: sessSchema, + ip: sessIPSchema + }, + pathParams: { + user: userId, + mailbox: mailboxId.allow('resolve') + }, + response: { + 200: { + description: 'Success', + model: Joi.object({ + success: successRes, + id: mailboxId, + name: Joi.string().required().description('Name for the mailbox (unicode string)'), + path: Joi.string() + .required() + .description('Full path of the mailbox, folders are separated by slashes, ends with the mailbox name (unicode string)'), + specialUse: Joi.string() + .required() + .example('\\Draft') + .description('Either special use identifier or null. One of Drafts, Junk, Sent or Trash'), + modifyIndex: Joi.number().required().description('Modification sequence number. Incremented on every change in the mailbox.'), + subscribed: booleanSchema.required().description('Mailbox subscription status. IMAP clients may unsubscribe from a folder.'), + hidden: booleanSchema.required().description('Is the folder hidden or not'), + total: Joi.number().required().description('How many messages are stored in this mailbox'), + unseen: Joi.number().required().description('How many unseen messages are stored in this mailbox') + }) + } + } + } + }, tools.responseWrapper(async (req, res) => { res.charSet('utf-8'); + + const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs; + const schema = Joi.object({ - user: Joi.string().hex().lowercase().length(24).required(), - mailbox: Joi.string().hex().lowercase().length(24).allow('resolve').required(), - path: Joi.string().regex(/\/{2,}|\/$/, { invert: true }), - sess: sessSchema, - ip: sessIPSchema + ...pathParams, + ...requestBody, + ...queryParams }); const result = schema.validate(req.params, { @@ -457,19 +531,47 @@ module.exports = (db, server, mailboxHandler) => { ); server.put( - '/users/:user/mailboxes/:mailbox', + { + path: '/users/:user/mailboxes/:mailbox', + summary: 'Update Mailbox information', + tags: ['Mailboxes'], + validationObjs: { + requestBody: { + path: Joi.string() + .regex(/\/{2,}|\/$/, { invert: true }) + .description('Full path of the mailbox, use this to rename an existing Mailbox'), + retention: Joi.number() + .empty('') + .min(0) + .description( + 'Retention policy for the Mailbox (in ms). Changing retention value only affects messages added to this folder after the change' + ), + subscribed: booleanSchema.description('Change Mailbox subscription state'), + hidden: booleanSchema.description('Is the folder hidden or not. Hidden folders can not be opened in IMAP.'), + sess: sessSchema, + ip: sessIPSchema + }, + pathParams: { user: userId, mailbox: mailboxId }, + queryParams: {}, + response: { + 200: { + description: 'Success', + model: Joi.object({ + success: successRes + }) + } + } + } + }, 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(), - path: Joi.string().regex(/\/{2,}|\/$/, { invert: true }), - retention: Joi.number().empty('').min(0), - subscribed: booleanSchema, - hidden: booleanSchema, - 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, { @@ -521,15 +623,33 @@ module.exports = (db, server, mailboxHandler) => { ); server.del( - '/users/:user/mailboxes/:mailbox', + { + path: '/users/:user/mailboxes/:mailbox', + summary: 'Delete a Mailbox', + tags: ['Mailboxes'], + validationObjs: { + requestBody: { user: userId, mailbox: mailboxId }, + queryParams: { sess: sessSchema, ip: sessIPSchema }, + pathParams: {}, + response: { + 200: { + description: 'Success', + model: Joi.object({ + success: successRes + }) + } + } + } + }, 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(), - 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, { diff --git a/lib/schemas/response/mailboxes-schemas.js b/lib/schemas/response/mailboxes-schemas.js new file mode 100644 index 00000000..7023a690 --- /dev/null +++ b/lib/schemas/response/mailboxes-schemas.js @@ -0,0 +1,25 @@ +'use strict'; + +const Joi = require('joi'); +const { mailboxId } = require('../request/general-schemas'); +const { booleanSchema } = require('../../schemas'); + +const GetMailboxesResult = Joi.object({ + id: mailboxId, + name: Joi.string().required().description('Name for the mailbox (unicode string)'), + path: Joi.string().required().description('Full path of the mailbox, folders are separated by slashes, ends with the mailbox name (unicode string)'), + specialUse: Joi.string().required().description('Either special use identifier or null. One of Drafts, Junk, Sent or Trash'), + modifyIndex: Joi.number().required().description('Modification sequence number. Incremented on every change in the mailbox.'), + subscribed: booleanSchema.required().description('Mailbox subscription status. IMAP clients may unsubscribe from a folder.'), + retention: Joi.number().description( + 'Default retention policy for this mailbox (in ms). If set then messages added to this maibox will be automatically deleted after retention time.' + ), + hidden: booleanSchema.required().description('Is the folder hidden or not'), + total: Joi.number().required().description('How many messages are stored in this mailbox'), + unseen: Joi.number().required().description('How many unseen messages are stored in this mailbox'), + size: Joi.number().description('Total size of mailbox in bytes.') +}); + +module.exports = { + GetMailboxesResult +};