fix: ZMS-210 API and docs fallback values updates (#802)

* in APIs remove fallbacks to false and fallback to undefined. Update documentation

* update docs

* Updae autoreply test. Don't account for undefined fields (not present in response)
This commit is contained in:
NickOvt 2025-04-08 14:45:39 +03:00 committed by GitHub
parent f0f35e1b67
commit 5ce9989313
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 63 additions and 103 deletions

View file

@ -6,7 +6,7 @@
"contact": {
"url": "https://github.com/zone-eu/wildduck"
},
"version": "1.45.4"
"version": "1.45.5"
},
"servers": [
{
@ -278,6 +278,10 @@
},
"description": "An array of forwarding targets. The value could either be an email address or a relay url to next MX server (\"smtp://mx2.zone.eu:25\") or an URL where mail contents are POSTed to"
},
"mtaRelay": {
"type": "string",
"description": "An address of an SMTP MTA relay. The value should be a relay url. If specified uses the this relay as the outbound MTA."
},
"spamLevel": {
"type": "number",
"description": "Relative scale for detecting spam. 0 means that everything is spam, 100 means that nothing is spam",
@ -592,6 +596,10 @@
},
"description": "An array of forwarding targets. The value could either be an email address or a relay url to next MX server (\"smtp://mx2.zone.eu:25\") or an URL where mail contents are POSTed to"
},
"mtaRelay": {
"type": "string",
"description": "An address of an SMTP MTA relay. The value should be a relay url. If specified uses the this relay as the outbound MTA."
},
"spamLevel": {
"type": "number",
"description": "Relative scale for detecting spam. 0 means that everything is spam, 100 means that nothing is spam"
@ -9339,7 +9347,7 @@
},
"retention": {
"type": "number",
"description": "Default retention time (in ms). false if not enabled"
"description": "Default retention time (in ms). Not present if not enabled"
},
"enabled2fa": {
"type": "array",
@ -9384,6 +9392,10 @@
},
"description": "List of forwarding targets"
},
"mtaRelay": {
"type": "string",
"description": "MTA Relay url"
},
"spamLevel": {
"type": "number",
"description": "Relative scale for detecting spam. 0 means that everything is spam, 100 means that nothing is spam"
@ -9440,7 +9452,6 @@
"username",
"name",
"address",
"retention",
"enabled2fa",
"autoreply",
"encryptMessages",
@ -9723,7 +9734,6 @@
},
"required": [
"id",
"name",
"address",
"user",
"forwarded",
@ -9853,7 +9863,6 @@
},
"required": [
"id",
"name",
"address",
"main",
"created",
@ -9931,7 +9940,6 @@
"required": [
"success",
"id",
"name",
"address",
"main",
"created",
@ -10079,11 +10087,7 @@
}
},
"required": [
"status",
"name",
"subject",
"text",
"html"
"status"
]
},
"GetForwardedAddressResponse": {
@ -10150,7 +10154,6 @@
"success",
"id",
"address",
"name",
"limits",
"autoreply",
"created",
@ -11542,26 +11545,12 @@
"description": "File ID"
},
"filename": {
"oneOf": [
{
"type": "string"
},
{
"type": "boolean"
}
],
"description": "Filename. False if none"
"type": "string",
"description": "Filename"
},
"contentType": {
"oneOf": [
{
"type": "string"
},
{
"type": "boolean"
}
],
"description": "Content-Type of the file. False if none"
"type": "string",
"description": "Content-Type of the file"
},
"cid": {
"type": "string",
@ -11583,8 +11572,6 @@
},
"required": [
"id",
"filename",
"contentType",
"size",
"created",
"md5"
@ -12136,18 +12123,14 @@
"properties": {
"time": {
"type": "string",
"description": "Datestring of last use or false if password has not been used",
"description": "Datestring of last use or not present if password has not been used",
"format": "date-time"
},
"event": {
"type": "string",
"description": "Event ID of the security log for the last authentication"
}
},
"required": [
"time",
"event"
]
}
},
"GetASPsResult": {
"type": "object",
@ -12978,18 +12961,12 @@
"start": {
"type": "string",
"description": "Datestring of the start of the autoreply or boolean false to disable start checks",
"format": "date-time",
"enum": [
false
]
"format": "date-time"
},
"end": {
"type": "string",
"description": "Datestring of the end of the autoreply or boolean false to disable end checks",
"format": "date-time",
"enum": [
false
]
"format": "date-time"
},
"created": {
"type": "string",
@ -12998,8 +12975,7 @@
}
},
"required": [
"success",
"created"
"success"
]
},
"Reference": {

View file

@ -245,7 +245,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
results: (listing.results || []).map(addressData => {
let values = {
id: addressData._id.toString(),
name: addressData.name || false,
name: addressData.name || undefined,
address: addressData.address,
user: addressData.user && addressData.user.toString(),
forwarded: !!addressData.targets,
@ -704,7 +704,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
results: addresses.map(addressData => {
let values = {
id: addressData._id.toString(),
name: addressData.name || false,
name: addressData.name || undefined,
address: addressData.address,
main: addressData.address === userData.address,
tags: addressData.tags || [],
@ -747,7 +747,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
model: Joi.object({
success: successRes,
id: addressId,
name: Joi.string().required().description('Identity name'),
name: Joi.string().description('Identity name'),
address: addressEmail,
main: booleanSchema.required().description('Indicates if this is the default address for the User'),
created: Joi.date().required().description('Datestring of the time the address was created'),
@ -850,7 +850,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
let value = {
success: true,
id: addressData._id.toString(),
name: addressData.name || false,
name: addressData.name || undefined,
address: addressData.address,
main: addressData.address === userData.address,
tags: addressData.tags || [],
@ -1428,7 +1428,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
success: true,
results: addresses.map(addressData => {
let name = addressData.name || false;
let name = addressData.name || undefined;
try {
// try to decode
if (name) {
@ -1439,7 +1439,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
}
return {
id: addressData._id.toString(),
name: addressData.name || false,
name: addressData.name || undefined,
address: addressData.address
};
})
@ -2167,7 +2167,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
success: successRes,
id: addressId,
address: addressEmail,
name: Joi.string().required().description('Identity name'),
name: Joi.string().description('Identity name'),
targets: Joi.array().items(Joi.string()).description('List of forwarding targets'),
limits: AddressLimits,
autoreply: AutoreplyInfo,
@ -2254,7 +2254,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
const values = {
success: true,
id: addressData._id.toString(),
name: addressData.name || false,
name: addressData.name || undefined,
address: addressData.address,
targets: addressData.targets && addressData.targets.map(t => t.value),
limits: {

View file

@ -50,8 +50,8 @@ module.exports = (db, server, userHandler) => {
.required()
.description('Allowed scopes for the Application Password'),
lastUse: Joi.object({
time: Joi.date().required().description('Datestring of last use or false if password has not been used'),
event: Joi.string().required().description('Event ID of the security log for the last authentication')
time: Joi.date().description('Datestring of last use or not present if password has not been used'),
event: Joi.string().description('Event ID of the security log for the last authentication')
})
.required()
.$_setFlag('objectName', 'LastUse')
@ -170,8 +170,8 @@ module.exports = (db, server, userHandler) => {
description: asp.description,
scopes: asp.scopes.includes('*') ? [...consts.SCOPES] : asp.scopes,
lastUse: {
time: asp.used || false,
event: asp.authEvent || false
time: asp.used || undefined,
event: asp.authEvent || undefined
},
expires: asp.expires,
created: asp.created
@ -209,8 +209,8 @@ module.exports = (db, server, userHandler) => {
.required()
.description('Allowed scopes for the Application Password'),
lastUse: Joi.object({
time: Joi.date().required().description('Datestring of last use or false if password has not been used'),
event: Joi.string().required().description('Event ID of the security log for the last authentication')
time: Joi.date().description('Datestring of last use or not present if password has not been used'),
event: Joi.string().description('Event ID of the security log for the last authentication')
})
.required()
.$_setFlag('objectName', 'LastUse')
@ -286,8 +286,8 @@ module.exports = (db, server, userHandler) => {
description: aspData.description,
scopes: aspData.scopes.includes('*') ? [...consts.SCOPES] : aspData.scopes,
lastUse: {
time: aspData.used || false,
event: aspData.authEvent || false
time: aspData.used || undefined,
event: aspData.authEvent || undefined
},
expires: asp.expires,
created: aspData.created

View file

@ -181,12 +181,9 @@ module.exports = (db, server) => {
.trim()
.max(128 * 1024)
.description('HTML formatted content of the autoreply message'),
start: Joi.date()
.empty('')
.allow(false)
.description('Datestring of the start of the autoreply or boolean false to disable start checks'),
end: Joi.date().empty('').allow(false).description('Datestring of the end of the autoreply or boolean false to disable end checks'),
created: Joi.date().required().description('Datestring of when the Autoreply was created')
start: Joi.date().empty('').description('Datestring of the start of the autoreply or boolean false to disable start checks'),
end: Joi.date().empty('').description('Datestring of the end of the autoreply or boolean false to disable end checks'),
created: Joi.date().description('Datestring of when the Autoreply was created')
}).$_setFlag('objectName', 'GetAutoreplyResponse')
}
}
@ -236,9 +233,9 @@ module.exports = (db, server) => {
subject: entry.subject || '',
text: entry.text || '',
html: entry.html || '',
start: entry.start || false,
end: entry.end || false,
created: entry.created || entry._id?.getTimestamp() || false
start: entry.start || undefined,
end: entry.end || undefined,
created: entry.created || entry._id?.getTimestamp() || undefined
});
})
);

View file

@ -6,7 +6,7 @@ const ObjectId = require('mongodb').ObjectId;
const tools = require('../tools');
const roles = require('../roles');
const consts = require('../consts');
const { nextPageCursorSchema, previousPageCursorSchema, pageNrSchema, sessSchema, sessIPSchema, booleanSchema } = require('../schemas');
const { nextPageCursorSchema, previousPageCursorSchema, pageNrSchema, sessSchema, sessIPSchema } = require('../schemas');
const { userId } = require('../schemas/request/general-schemas');
const { successRes, totalRes, pageRes, previousCursorRes, nextCursorRes } = require('../schemas/response/general-schemas');
@ -157,14 +157,8 @@ module.exports = (db, server, storageHandler) => {
.items(
Joi.object({
id: Joi.string().required().description('File ID'),
filename: Joi.alternatives()
.try(Joi.string().required(), booleanSchema.required())
.required()
.description('Filename. False if none'),
contentType: Joi.alternatives()
.try(Joi.string().required(), booleanSchema.required())
.required()
.description('Content-Type of the file. False if none'),
filename: Joi.string().description('Filename'),
contentType: Joi.string().description('Content-Type of the file'),
cid: Joi.string().description('Content ID'),
size: Joi.number().required().description('File size'),
created: Joi.date().required().description('Created datestring'),
@ -297,8 +291,8 @@ module.exports = (db, server, storageHandler) => {
nextCursor: listing.hasNext ? listing.next : false,
results: (listing.results || []).map(fileData => ({
id: fileData._id.toString(),
filename: fileData.filename || false,
contentType: fileData.contentType || false,
filename: fileData.filename || undefined,
contentType: fileData.contentType || undefined,
cid: fileData.metadata?.cid,
size: fileData.length,
created: fileData.uploadDate.toISOString(),

View file

@ -793,7 +793,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
username: Joi.string().required().description('Username of the User'),
name: Joi.string().required().description('Name of the User'),
address: Joi.string().required().description('Main email address of the User'),
retention: Joi.number().required().description('Default retention time (in ms). false if not enabled'),
retention: Joi.number().description('Default retention time (in ms). Not present if not enabled'),
enabled2fa: Joi.array().items(Joi.string()).required().description('List of enabled 2FA methods'),
autoreply: booleanSchema
.required()
@ -814,7 +814,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
.required()
.description('Custom internal metadata object set for this user. Not available for user-role tokens'),
targets: Joi.array().items(Joi.string()).required().description('List of forwarding targets'),
mtaRelay: Joi.string().required().description('MTA Relay url'),
mtaRelay: Joi.string().description('MTA Relay url'),
spamLevel: Joi.number()
.required()
.description('Relative scale for detecting spam. 0 means that everything is spam, 100 means that nothing is spam'),
@ -1073,7 +1073,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
address: userData.address,
language: userData.language,
retention: userData.retention || false,
retention: userData.retention || undefined,
enabled2fa: tools.getEnabled2fa(userData.enabled2fa),
autoreply: !!userData.autoreply,
@ -1092,7 +1092,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
.map(target => target.value)
.filter(target => target),
mtaRelay: userData.mtaRelay?.value || false,
mtaRelay: userData.mtaRelay?.value || undefined,
limits: {
quota: {

View file

@ -6,7 +6,7 @@ const { addressId, addressEmail } = require('../request/general-schemas');
const GetAddressesResult = Joi.object({
id: Joi.string().required().description('ID of the Address'),
name: Joi.string().required().description('Identity name'),
name: Joi.string().description('Identity name'),
address: Joi.string().required().description('E-mail address string'),
user: Joi.string().required().description('User ID this address belongs to if this is a User address'),
forwarded: booleanSchema.required().description('If true then it is a forwarded address'),
@ -19,7 +19,7 @@ const GetAddressesResult = Joi.object({
const GetUserAddressesResult = Joi.object({
id: addressId,
name: Joi.string().required().description('Identity name'),
name: Joi.string().description('Identity name'),
address: addressEmail,
main: booleanSchema.required().description('Indicates if this is the default address for the User'),
created: Joi.date().required().description('Datestring of the time the address was created'),
@ -50,10 +50,10 @@ const AddressLimits = Joi.object({
const AutoreplyInfo = Joi.object({
status: booleanSchema.required().description('If true, then autoreply is enabled for this address'),
name: Joi.string().required().description('Name that is used for the From: header in autoreply message'),
subject: Joi.string().required().description('Autoreply subject line'),
text: Joi.string().required().description('Autoreply plaintext content'),
html: Joi.string().required().description('Autoreply HTML content')
name: Joi.string().description('Name that is used for the From: header in autoreply message'),
subject: Joi.string().description('Autoreply subject line'),
text: Joi.string().description('Autoreply plaintext content'),
html: Joi.string().description('Autoreply HTML content')
})
.required()
.description('Autoreply information')

View file

@ -415,10 +415,7 @@ describe('API tests', function () {
name: '',
subject: '',
text: '',
html: '',
start: false,
end: false,
created: false
html: ''
});
r = await server
@ -467,7 +464,6 @@ describe('API tests', function () {
subject: '',
text: 'Away from office until Dec.19',
html: '',
start: false,
end: '2017-12-19T00:00:00.000Z',
created: r.body.created // created might have been changed to new date
});
@ -480,10 +476,7 @@ describe('API tests', function () {
name: '',
subject: '',
text: '',
html: '',
start: false,
end: false,
created: false
html: ''
});
});
});