emailengine-app 2.68.0 → 2.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/.github/codeql/codeql-config.yml +16 -0
  2. package/.github/workflows/codeql.yml +102 -0
  3. package/.github/workflows/deploy.yml +8 -0
  4. package/.github/workflows/release.yaml +4 -0
  5. package/.github/workflows/test.yml +3 -0
  6. package/CHANGELOG.md +49 -0
  7. package/SECURITY.md +80 -0
  8. package/SECURITY.txt +27 -0
  9. package/config/default.toml +2 -0
  10. package/data/google-crawlers.json +13 -1
  11. package/lib/account.js +62 -25
  12. package/lib/api-routes/account-routes.js +493 -75
  13. package/lib/api-routes/blocklist-routes.js +337 -0
  14. package/lib/api-routes/delivery-test-routes.js +321 -0
  15. package/lib/api-routes/export-routes.js +1 -12
  16. package/lib/api-routes/gateway-routes.js +376 -0
  17. package/lib/api-routes/license-routes.js +142 -0
  18. package/lib/api-routes/mailbox-routes.js +318 -0
  19. package/lib/api-routes/message-routes.js +21 -129
  20. package/lib/api-routes/oauth2-app-routes.js +631 -0
  21. package/lib/api-routes/outbox-routes.js +173 -0
  22. package/lib/api-routes/pubsub-routes.js +98 -0
  23. package/lib/api-routes/route-helpers.js +45 -0
  24. package/lib/api-routes/settings-routes.js +331 -0
  25. package/lib/api-routes/stats-routes.js +77 -0
  26. package/lib/api-routes/submit-routes.js +472 -0
  27. package/lib/api-routes/template-routes.js +7 -55
  28. package/lib/api-routes/token-routes.js +297 -0
  29. package/lib/api-routes/webhook-route-routes.js +152 -0
  30. package/lib/email-client/gmail-client.js +14 -0
  31. package/lib/email-client/imap/mailbox.js +34 -11
  32. package/lib/email-client/imap/subconnection.js +20 -12
  33. package/lib/email-client/imap/sync-operations.js +130 -2
  34. package/lib/email-client/imap-client.js +116 -58
  35. package/lib/email-client/outlook-client.js +85 -13
  36. package/lib/export.js +60 -19
  37. package/lib/imapproxy/imap-core/lib/commands/starttls.js +18 -0
  38. package/lib/imapproxy/imap-core/lib/imap-command.js +7 -2
  39. package/lib/imapproxy/imap-core/lib/imap-connection.js +113 -23
  40. package/lib/imapproxy/imap-core/lib/imap-server.js +25 -1
  41. package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
  42. package/lib/imapproxy/imap-server.js +92 -29
  43. package/lib/message-port-stream.js +113 -16
  44. package/lib/reject-worker-calls.js +42 -0
  45. package/lib/routes-ui.js +37 -8778
  46. package/lib/schemas.js +26 -1
  47. package/lib/tools.js +73 -0
  48. package/lib/ui-routes/account-routes.js +40 -210
  49. package/lib/ui-routes/admin-config-routes.js +913 -487
  50. package/lib/ui-routes/admin-entities-routes.js +1 -0
  51. package/lib/ui-routes/auth-routes.js +1339 -0
  52. package/lib/ui-routes/dashboard-routes.js +188 -0
  53. package/lib/ui-routes/document-store-routes.js +800 -0
  54. package/lib/ui-routes/export-routes.js +217 -0
  55. package/lib/ui-routes/internals-routes.js +354 -0
  56. package/lib/ui-routes/network-config-routes.js +759 -0
  57. package/lib/ui-routes/{oauth-routes.js → oauth-config-routes.js} +371 -91
  58. package/lib/ui-routes/route-helpers.js +316 -0
  59. package/lib/ui-routes/smtp-test-routes.js +236 -0
  60. package/lib/ui-routes/unsubscribe-routes.js +234 -0
  61. package/lib/webhook-request.js +36 -0
  62. package/package.json +17 -17
  63. package/sbom.json +1 -1
  64. package/server.js +217 -19
  65. package/static/licenses.html +52 -182
  66. package/translations/messages.pot +131 -151
  67. package/views/dashboard.hbs +7 -26
  68. package/views/internals/index.hbs +15 -0
  69. package/views/tokens/index.hbs +9 -0
  70. package/workers/api.js +198 -4401
  71. package/workers/export.js +87 -54
  72. package/workers/imap.js +29 -13
  73. package/workers/submit.js +20 -11
  74. package/workers/webhooks.js +6 -20
@@ -0,0 +1,173 @@
1
+ 'use strict';
2
+
3
+ const Boom = require('@hapi/boom');
4
+ const Joi = require('joi');
5
+ const logger = require('../logger');
6
+ const outbox = require('../outbox');
7
+ const { failAction } = require('../tools');
8
+ const { handleError } = require('./route-helpers');
9
+ const { outboxEntrySchema } = require('../schemas');
10
+
11
+ async function init(args) {
12
+ const { server, CORS_CONFIG } = args;
13
+
14
+ server.route({
15
+ method: 'GET',
16
+ path: '/v1/outbox',
17
+
18
+ async handler(request) {
19
+ try {
20
+ return await outbox.list(Object.assign({ logger }, request.query));
21
+ } catch (err) {
22
+ handleError(request, err);
23
+ }
24
+ },
25
+
26
+ options: {
27
+ description: 'List queued messages',
28
+ notes: 'Lists messages in the Outbox',
29
+ tags: ['api', 'Outbox'],
30
+
31
+ plugins: {},
32
+
33
+ auth: {
34
+ strategy: 'api-token',
35
+ mode: 'required'
36
+ },
37
+ cors: CORS_CONFIG,
38
+
39
+ validate: {
40
+ options: {
41
+ stripUnknown: false,
42
+ abortEarly: false,
43
+ convert: true
44
+ },
45
+ failAction,
46
+
47
+ query: Joi.object({
48
+ page: Joi.number()
49
+ .integer()
50
+ .min(0)
51
+ .max(1024 * 1024)
52
+ .default(0)
53
+ .example(0)
54
+ .description('Page number (zero indexed, so use 0 for first page)')
55
+ .label('PageNumber'),
56
+ pageSize: Joi.number().integer().min(1).max(1000).default(20).example(20).description('How many entries per page').label('PageSize')
57
+ }).label('OutbixListFilter')
58
+ },
59
+
60
+ response: {
61
+ schema: Joi.object({
62
+ total: Joi.number().integer().example(120).description('How many matching entries').label('TotalNumber'),
63
+ page: Joi.number().integer().example(0).description('Current page (0-based index)').label('PageNumber'),
64
+ pages: Joi.number().integer().example(24).description('Total page count').label('PagesNumber'),
65
+
66
+ messages: Joi.array().items(outboxEntrySchema).label('OutboxListEntries')
67
+ }).label('OutboxListResponse'),
68
+ failAction: 'log'
69
+ }
70
+ }
71
+ });
72
+
73
+ server.route({
74
+ method: 'GET',
75
+ path: '/v1/outbox/{queueId}',
76
+
77
+ async handler(request) {
78
+ try {
79
+ let outboxEntry = await outbox.get({ queueId: request.params.queueId, logger });
80
+ if (!outboxEntry) {
81
+ let message = 'Requested queue entry was not found';
82
+ let error = Boom.boomify(new Error(message), { statusCode: 404 });
83
+ throw error;
84
+ }
85
+ return outboxEntry;
86
+ } catch (err) {
87
+ handleError(request, err);
88
+ }
89
+ },
90
+
91
+ options: {
92
+ description: 'Get queued message',
93
+ notes: 'Gets a queued message in the Outbox',
94
+ tags: ['api', 'Outbox'],
95
+
96
+ plugins: {},
97
+
98
+ auth: {
99
+ strategy: 'api-token',
100
+ mode: 'required'
101
+ },
102
+ cors: CORS_CONFIG,
103
+
104
+ validate: {
105
+ options: {
106
+ stripUnknown: false,
107
+ abortEarly: false,
108
+ convert: true
109
+ },
110
+ failAction,
111
+
112
+ params: Joi.object({
113
+ queueId: Joi.string().max(100).example('d41f0423195f271f').description('Queue identifier for scheduled email').required()
114
+ }).label('OutboxEntryParams')
115
+ },
116
+
117
+ response: {
118
+ schema: outboxEntrySchema,
119
+ failAction: 'log'
120
+ }
121
+ }
122
+ });
123
+
124
+ server.route({
125
+ method: 'DELETE',
126
+ path: '/v1/outbox/{queueId}',
127
+
128
+ async handler(request) {
129
+ try {
130
+ return {
131
+ deleted: await outbox.del({ queueId: request.params.queueId, logger })
132
+ };
133
+ } catch (err) {
134
+ handleError(request, err);
135
+ }
136
+ },
137
+ options: {
138
+ description: 'Remove a message',
139
+ notes: 'Remove a message from the outbox',
140
+ tags: ['api', 'Outbox'],
141
+
142
+ plugins: {},
143
+
144
+ auth: {
145
+ strategy: 'api-token',
146
+ mode: 'required'
147
+ },
148
+ cors: CORS_CONFIG,
149
+
150
+ validate: {
151
+ options: {
152
+ stripUnknown: false,
153
+ abortEarly: false,
154
+ convert: true
155
+ },
156
+ failAction,
157
+
158
+ params: Joi.object({
159
+ queueId: Joi.string().max(100).example('d41f0423195f271f').description('Queue identifier for scheduled email').required()
160
+ }).label('OutboxEntryParams')
161
+ },
162
+
163
+ response: {
164
+ schema: Joi.object({
165
+ deleted: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(true).description('Was the message deleted')
166
+ }).label('DeleteOutboxEntryResponse'),
167
+ failAction: 'log'
168
+ }
169
+ }
170
+ });
171
+ }
172
+
173
+ module.exports = init;
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ const Joi = require('joi');
4
+ const { failAction } = require('../tools');
5
+ const { oauth2Apps } = require('../oauth2-apps');
6
+ const { pubSubErrorSchema } = require('../schemas');
7
+ const { handleError, flattenOAuthAppMeta } = require('./route-helpers');
8
+
9
+ async function init(args) {
10
+ const { server, CORS_CONFIG } = args;
11
+
12
+ server.route({
13
+ method: 'GET',
14
+ path: '/v1/pubsub/status',
15
+
16
+ async handler(request) {
17
+ try {
18
+ let response = await oauth2Apps.list(request.query.page, request.query.pageSize, { pubsub: true });
19
+
20
+ let apps = response.apps.map(app => {
21
+ flattenOAuthAppMeta(app);
22
+ return { id: app.id, name: app.name || null, lastError: app.lastError || null, pubSubError: app.pubSubError || null };
23
+ });
24
+
25
+ return {
26
+ total: response.total,
27
+ page: response.page,
28
+ pages: response.pages,
29
+ apps
30
+ };
31
+ } catch (err) {
32
+ handleError(request, err);
33
+ }
34
+ },
35
+
36
+ options: {
37
+ description: 'List Pub/Sub status',
38
+ notes: 'Lists Pub/Sub enabled OAuth2 applications and their subscription status',
39
+ tags: ['api', 'OAuth2 Applications'],
40
+
41
+ plugins: {},
42
+
43
+ auth: {
44
+ strategy: 'api-token',
45
+ mode: 'required'
46
+ },
47
+ cors: CORS_CONFIG,
48
+
49
+ validate: {
50
+ options: {
51
+ stripUnknown: false,
52
+ abortEarly: false,
53
+ convert: true
54
+ },
55
+ failAction,
56
+
57
+ query: Joi.object({
58
+ page: Joi.number()
59
+ .integer()
60
+ .min(0)
61
+ .max(1024 * 1024)
62
+ .default(0)
63
+ .example(0)
64
+ .description('Page number (zero indexed, so use 0 for first page)')
65
+ .label('PageNumber'),
66
+ pageSize: Joi.number().integer().min(1).max(1000).default(20).example(20).description('How many entries per page').label('PageSize')
67
+ }).label('PubSubStatusFilter')
68
+ },
69
+
70
+ response: {
71
+ schema: Joi.object({
72
+ total: Joi.number().integer().example(120).description('How many matching entries').label('TotalNumber'),
73
+ page: Joi.number().integer().example(0).description('Current page (0-based index)').label('PageNumber'),
74
+ pages: Joi.number().integer().example(24).description('Total page count').label('PagesNumber'),
75
+
76
+ apps: Joi.array()
77
+ .items(
78
+ Joi.object({
79
+ id: Joi.string().max(256).required().example('AAABhaBPHscAAAAH').description('OAuth2 application ID'),
80
+ name: Joi.string().allow(null).max(256).example('My Gmail App').description('Display name for the app'),
81
+ lastError: Joi.object({
82
+ response: Joi.string().example('Enable the Cloud Pub/Sub API').description('Setup error message')
83
+ })
84
+ .allow(null)
85
+ .description('Setup error from ensurePubsub, if any')
86
+ .label('PubSubSetupError'),
87
+ pubSubError: pubSubErrorSchema.allow(null)
88
+ }).label('PubSubAppStatus')
89
+ )
90
+ .label('PubSubAppStatusList')
91
+ }).label('PubSubStatusResponse'),
92
+ failAction: 'log'
93
+ }
94
+ }
95
+ });
96
+ }
97
+
98
+ module.exports = init;
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ const Boom = require('@hapi/boom');
4
+
5
+ // Shared helpers for the extracted API route modules under lib/api-routes/.
6
+
7
+ // Standard API error handler. Logs the failure, passes Boom errors through unchanged, and converts
8
+ // plain errors into a Boom error while preserving the original statusCode and an optional
9
+ // machine-readable err.code. This function ALWAYS throws and never returns a value, so callers use it
10
+ // as the final statement inside a catch block: `catch (err) { handleError(request, err); }`.
11
+ function handleError(request, err) {
12
+ request.logger.error({ msg: 'API request failed', err });
13
+ if (Boom.isBoom(err)) {
14
+ throw err;
15
+ }
16
+ // Lower-level libraries (e.g. ImapFlow) flag "this server lacks the required capability" with a
17
+ // machine code but no HTTP status. Surface it as a 422 client error instead of a generic 500 - the
18
+ // request is well-formed, the account just cannot satisfy it (e.g. label search on non-Gmail IMAP).
19
+ let statusCode = err.statusCode || (err.code === 'MissingServerExtension' ? 422 : 500);
20
+ const error = Boom.boomify(err, { statusCode });
21
+ if (err.code) {
22
+ error.output.payload.code = err.code;
23
+ }
24
+ throw error;
25
+ }
26
+
27
+ // Strips the internal `meta` field from an OAuth2 application object before returning it to the API
28
+ // client, surfacing any authentication or Pub/Sub error messages as `lastError`/`pubSubError`.
29
+ // Pure function: it mutates the passed object and closes over no module state.
30
+ function flattenOAuthAppMeta(app) {
31
+ if (!app.meta) {
32
+ return;
33
+ }
34
+ let authFlag = app.meta.authFlag;
35
+ let pubSubFlag = app.meta.pubSubFlag;
36
+ delete app.meta;
37
+ if (authFlag && authFlag.message) {
38
+ app.lastError = { response: authFlag.message };
39
+ }
40
+ if (pubSubFlag && pubSubFlag.message) {
41
+ app.pubSubError = { message: pubSubFlag.message, description: pubSubFlag.description || null };
42
+ }
43
+ }
44
+
45
+ module.exports = { handleError, flattenOAuthAppMeta };
@@ -0,0 +1,331 @@
1
+ 'use strict';
2
+
3
+ const Joi = require('joi');
4
+ const { redis, documentsQueue, notifyQueue, submitQueue } = require('../db');
5
+ const settings = require('../settings');
6
+ const consts = require('../consts');
7
+ const { REDIS_PREFIX } = consts;
8
+ const { failAction } = require('../tools');
9
+ const { handleError } = require('./route-helpers');
10
+ const { settingsSchema, settingsQuerySchema } = require('../schemas');
11
+
12
+ async function init(args) {
13
+ const { server, notify, CORS_CONFIG } = args;
14
+
15
+ server.route({
16
+ method: 'GET',
17
+ path: '/v1/settings',
18
+
19
+ async handler(request) {
20
+ let values = {};
21
+ for (let key of Object.keys(request.query)) {
22
+ if (request.query[key]) {
23
+ if (key === 'eventTypes') {
24
+ values[key] = Object.keys(consts)
25
+ .map(key => {
26
+ if (/_NOTIFY?/.test(key)) {
27
+ return consts[key];
28
+ }
29
+ return false;
30
+ })
31
+ .map(key => key);
32
+ continue;
33
+ }
34
+
35
+ let value = await settings.get(key);
36
+
37
+ if (settings.encryptedKeys.includes(key)) {
38
+ // do not reveal secret values
39
+ // instead show boolean value true if value is set, or false if it's not
40
+ value = value ? true : false;
41
+ }
42
+
43
+ values[key] = value;
44
+ }
45
+ }
46
+ return values;
47
+ },
48
+ options: {
49
+ description: 'List specific settings',
50
+ notes: 'List setting values for specific keys',
51
+ tags: ['api', 'Settings'],
52
+
53
+ auth: {
54
+ strategy: 'api-token',
55
+ mode: 'required'
56
+ },
57
+ cors: CORS_CONFIG,
58
+
59
+ validate: {
60
+ options: {
61
+ stripUnknown: false,
62
+ abortEarly: false,
63
+ convert: true
64
+ },
65
+ failAction,
66
+
67
+ query: Joi.object(settingsQuerySchema).label('SettingsQuery')
68
+ },
69
+
70
+ response: {
71
+ schema: Joi.object(settingsSchema).label('SettingsQueryResponse'),
72
+ failAction: 'log'
73
+ }
74
+ }
75
+ });
76
+
77
+ server.route({
78
+ method: 'POST',
79
+ path: '/v1/settings',
80
+
81
+ async handler(request) {
82
+ let updated = [];
83
+ for (let key of Object.keys(request.payload)) {
84
+ switch (key) {
85
+ case 'serviceUrl': {
86
+ let url = new URL(request.payload.serviceUrl);
87
+ request.payload.serviceUrl = url.origin;
88
+ break;
89
+ }
90
+
91
+ case 'webhooksEnabled':
92
+ if (!request.payload.webhooksEnabled) {
93
+ // clear error message (if exists)
94
+ await settings.clear('webhookErrorFlag');
95
+ }
96
+ break;
97
+ }
98
+
99
+ await settings.set(key, request.payload[key]);
100
+ updated.push(key);
101
+ }
102
+
103
+ // Broadcast to all workers (including this one); each reloads its HTTP proxy agent via
104
+ // the 'settings' message handler, so no inline reload is needed here.
105
+ notify('settings', request.payload);
106
+ return { updated };
107
+ },
108
+ options: {
109
+ description: 'Set setting values',
110
+ notes: 'Set setting values for specific keys',
111
+ tags: ['api', 'Settings'],
112
+
113
+ plugins: {},
114
+
115
+ auth: {
116
+ strategy: 'api-token',
117
+ mode: 'required'
118
+ },
119
+ cors: CORS_CONFIG,
120
+
121
+ validate: {
122
+ options: {
123
+ stripUnknown: false,
124
+ abortEarly: false,
125
+ convert: true
126
+ },
127
+ failAction,
128
+
129
+ payload: Joi.object(settingsSchema).label('Settings')
130
+ },
131
+
132
+ response: {
133
+ schema: Joi.object({
134
+ updated: Joi.array().items(Joi.string().example('notifyHeaders')).description('List of updated setting keys').label('UpdatedSettings')
135
+ }).label('SettingsUpdatedResponse'),
136
+ failAction: 'log'
137
+ }
138
+ }
139
+ });
140
+
141
+ server.route({
142
+ method: 'GET',
143
+ path: '/v1/settings/queue/{queue}',
144
+
145
+ async handler(request) {
146
+ try {
147
+ let queue = request.params.queue;
148
+ let values = {
149
+ queue
150
+ };
151
+
152
+ const [resActive, resDelayed, resPaused, resWaiting, resMeta] = await redis
153
+ .multi()
154
+ .llen(`${REDIS_PREFIX}bull:${queue}:active`)
155
+ .zcard(`${REDIS_PREFIX}bull:${queue}:delayed`)
156
+ .llen(`${REDIS_PREFIX}bull:${queue}:paused`)
157
+ .llen(`${REDIS_PREFIX}bull:${queue}:wait`)
158
+ .hget(`${REDIS_PREFIX}bull:${queue}:meta`, 'paused')
159
+ .exec();
160
+
161
+ if (resActive[0] || resDelayed[0] || resPaused[0] || resWaiting[0]) {
162
+ // counting failed
163
+ let err = new Error('Failed to count queue lengtho');
164
+ err.statusCode = 500;
165
+ throw err;
166
+ }
167
+
168
+ values.jobs = {
169
+ active: Number(resActive[1]) || 0,
170
+ delayed: Number(resDelayed[1]) || 0,
171
+ paused: Number(resPaused[1]) || 0,
172
+ waiting: Number(resWaiting[1]) || 0
173
+ };
174
+
175
+ values.paused = !!Number(resMeta[1]) || false;
176
+
177
+ return values;
178
+ } catch (err) {
179
+ handleError(request, err);
180
+ }
181
+ },
182
+ options: {
183
+ description: 'Show queue information',
184
+ notes: 'Show queue status and current state',
185
+ tags: ['api', 'Settings'],
186
+
187
+ auth: {
188
+ strategy: 'api-token',
189
+ mode: 'required'
190
+ },
191
+ cors: CORS_CONFIG,
192
+
193
+ validate: {
194
+ options: {
195
+ stripUnknown: false,
196
+ abortEarly: false,
197
+ convert: true
198
+ },
199
+ failAction,
200
+
201
+ params: Joi.object({
202
+ queue: Joi.string()
203
+ .empty('')
204
+ .trim()
205
+ .valid('notify', 'submit', 'documents')
206
+ .required()
207
+ .example('notify')
208
+ .description('Queue ID')
209
+ .label('QueueId')
210
+ })
211
+ },
212
+
213
+ response: {
214
+ schema: Joi.object({
215
+ queue: Joi.string()
216
+ .empty('')
217
+ .trim()
218
+ .valid('notify', 'submit', 'documents')
219
+ .required()
220
+ .example('notify')
221
+ .description('Queue ID')
222
+ .label('QueueIdResponse'),
223
+ jobs: Joi.object({
224
+ active: Joi.number().integer().example(123).description('Jobs that are currently being processed'),
225
+ delayed: Joi.number().integer().example(123).description('Jobs that are processed in the future'),
226
+ paused: Joi.number().integer().example(123).description('Jobs that would be processed once queue processing is resumed'),
227
+ waiting: Joi.number()
228
+ .integer()
229
+ .example(123)
230
+ .description('Jobs that should be processed, but are waiting until there are any free handlers')
231
+ }).label('QueueJobs'),
232
+ paused: Joi.boolean().example(false).description('Is the queue paused or not')
233
+ }).label('SettingsQueueResponse'),
234
+ failAction: 'log'
235
+ }
236
+ }
237
+ });
238
+
239
+ server.route({
240
+ method: 'PUT',
241
+ path: '/v1/settings/queue/{queue}',
242
+
243
+ async handler(request) {
244
+ try {
245
+ let queue = request.params.queue;
246
+
247
+ let queueObj = {
248
+ documents: documentsQueue,
249
+ notify: notifyQueue,
250
+ submit: submitQueue
251
+ }[queue];
252
+
253
+ let values = {
254
+ queue
255
+ };
256
+
257
+ for (let key of Object.keys(request.payload)) {
258
+ switch (key) {
259
+ case 'paused':
260
+ if (request.payload[key]) {
261
+ await queueObj.pause();
262
+ } else {
263
+ await queueObj.resume();
264
+ }
265
+ break;
266
+ }
267
+ }
268
+
269
+ values.paused = await queueObj.isPaused();
270
+
271
+ return values;
272
+ } catch (err) {
273
+ handleError(request, err);
274
+ }
275
+ },
276
+ options: {
277
+ description: 'Set queue settings',
278
+ notes: 'Set queue settings',
279
+ tags: ['api', 'Settings'],
280
+
281
+ plugins: {},
282
+
283
+ auth: {
284
+ strategy: 'api-token',
285
+ mode: 'required'
286
+ },
287
+ cors: CORS_CONFIG,
288
+
289
+ validate: {
290
+ options: {
291
+ stripUnknown: false,
292
+ abortEarly: false,
293
+ convert: true
294
+ },
295
+ failAction,
296
+
297
+ params: Joi.object({
298
+ queue: Joi.string()
299
+ .empty('')
300
+ .trim()
301
+ .valid('notify', 'submit', 'documents')
302
+ .required()
303
+ .example('notify')
304
+ .description('Queue ID')
305
+ .label('QueueIdParam')
306
+ }),
307
+
308
+ payload: Joi.object({
309
+ paused: Joi.boolean().empty('').example(false).description('Set queue state to paused')
310
+ }).label('SettingsPutQueuePayload')
311
+ },
312
+
313
+ response: {
314
+ schema: Joi.object({
315
+ queue: Joi.string()
316
+ .empty('')
317
+ .trim()
318
+ .valid('notify', 'submit', 'documents')
319
+ .required()
320
+ .example('notify')
321
+ .description('Queue ID')
322
+ .label('QueueIdPutResponse'),
323
+ paused: Joi.boolean().example(false).description('Is the queue paused or not')
324
+ }).label('SettingsPutQueueResponse'),
325
+ failAction: 'log'
326
+ }
327
+ }
328
+ });
329
+ }
330
+
331
+ module.exports = init;