emailengine-app 2.68.1 → 2.70.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 (95) hide show
  1. package/.github/workflows/deploy.yml +8 -3
  2. package/.github/workflows/release.yaml +6 -0
  3. package/CHANGELOG.md +59 -0
  4. package/Gruntfile.js +3 -1
  5. package/config/default.toml +2 -0
  6. package/data/google-crawlers.json +7 -1
  7. package/getswagger.sh +40 -4
  8. package/gettext-extract.js +163 -0
  9. package/lib/account.js +135 -72
  10. package/lib/api-routes/account-routes.js +684 -106
  11. package/lib/api-routes/blocklist-routes.js +344 -0
  12. package/lib/api-routes/chat-routes.js +32 -14
  13. package/lib/api-routes/delivery-test-routes.js +346 -0
  14. package/lib/api-routes/export-routes.js +28 -14
  15. package/lib/api-routes/gateway-routes.js +427 -0
  16. package/lib/api-routes/license-routes.js +156 -0
  17. package/lib/api-routes/mailbox-routes.js +344 -0
  18. package/lib/api-routes/message-routes.js +221 -187
  19. package/lib/api-routes/oauth2-app-routes.js +697 -0
  20. package/lib/api-routes/outbox-routes.js +185 -0
  21. package/lib/api-routes/pubsub-routes.js +102 -0
  22. package/lib/api-routes/route-helpers.js +58 -0
  23. package/lib/api-routes/settings-routes.js +357 -0
  24. package/lib/api-routes/stats-routes.js +111 -0
  25. package/lib/api-routes/submit-routes.js +461 -0
  26. package/lib/api-routes/template-routes.js +60 -75
  27. package/lib/api-routes/token-routes.js +297 -0
  28. package/lib/api-routes/webhook-route-routes.js +181 -0
  29. package/lib/autodetect-imap-settings.js +0 -2
  30. package/lib/consts.js +5 -0
  31. package/lib/email-client/base-client.js +28 -6
  32. package/lib/email-client/gmail-client.js +133 -112
  33. package/lib/email-client/imap/mailbox.js +34 -11
  34. package/lib/email-client/imap/subconnection.js +20 -13
  35. package/lib/email-client/imap/sync-operations.js +131 -3
  36. package/lib/email-client/imap-client.js +152 -75
  37. package/lib/email-client/notification-handler.js +1 -4
  38. package/lib/email-client/outlook-client.js +134 -75
  39. package/lib/export.js +97 -20
  40. package/lib/feature-flags.js +2 -2
  41. package/lib/gateway.js +4 -9
  42. package/lib/get-raw-email.js +5 -5
  43. package/lib/imapproxy/imap-core/lib/commands/starttls.js +18 -0
  44. package/lib/imapproxy/imap-core/lib/imap-command.js +6 -1
  45. package/lib/imapproxy/imap-core/lib/imap-connection.js +106 -24
  46. package/lib/imapproxy/imap-core/lib/imap-server.js +24 -0
  47. package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
  48. package/lib/logger.js +24 -21
  49. package/lib/message-port-stream.js +113 -16
  50. package/lib/metrics-collector.js +0 -2
  51. package/lib/oauth2-apps.js +13 -4
  52. package/lib/outbox.js +24 -40
  53. package/lib/redis-operations.js +1 -1
  54. package/lib/reject-worker-calls.js +42 -0
  55. package/lib/routes-ui.js +37 -8778
  56. package/lib/schemas.js +429 -84
  57. package/lib/sentry.js +139 -0
  58. package/lib/settings.js +9 -3
  59. package/lib/stream-encrypt.js +1 -1
  60. package/lib/templates.js +1 -1
  61. package/lib/tokens.js +5 -3
  62. package/lib/tools.js +70 -4
  63. package/lib/ui-routes/account-routes.js +45 -212
  64. package/lib/ui-routes/admin-config-routes.js +928 -489
  65. package/lib/ui-routes/admin-entities-routes.js +1 -0
  66. package/lib/ui-routes/auth-routes.js +1339 -0
  67. package/lib/ui-routes/dashboard-routes.js +188 -0
  68. package/lib/ui-routes/document-store-routes.js +800 -0
  69. package/lib/ui-routes/export-routes.js +217 -0
  70. package/lib/ui-routes/internals-routes.js +354 -0
  71. package/lib/ui-routes/network-config-routes.js +759 -0
  72. package/lib/ui-routes/{oauth-routes.js → oauth-config-routes.js} +369 -91
  73. package/lib/ui-routes/route-helpers.js +314 -0
  74. package/lib/ui-routes/smtp-test-routes.js +236 -0
  75. package/lib/ui-routes/unsubscribe-routes.js +232 -0
  76. package/lib/webhook-request.js +36 -0
  77. package/lib/webhooks.js +8 -4
  78. package/package.json +13 -12
  79. package/sbom.json +1 -1
  80. package/server.js +222 -39
  81. package/static/licenses.html +160 -300
  82. package/translations/messages.pot +112 -132
  83. package/update-info.sh +19 -1
  84. package/views/config/logging.hbs +48 -0
  85. package/views/dashboard.hbs +7 -26
  86. package/views/internals/index.hbs +15 -0
  87. package/views/tokens/index.hbs +9 -0
  88. package/workers/api.js +200 -4424
  89. package/workers/documents.js +2 -22
  90. package/workers/export.js +103 -104
  91. package/workers/imap-proxy.js +3 -23
  92. package/workers/imap.js +32 -36
  93. package/workers/smtp.js +2 -22
  94. package/workers/submit.js +26 -35
  95. package/workers/webhooks.js +9 -43
@@ -0,0 +1,185 @@
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, errorResponses } = 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
+ 'hapi-swagger': {
33
+ responses: errorResponses(400, 401, 403, 429, 500)
34
+ }
35
+ },
36
+
37
+ auth: {
38
+ strategy: 'api-token',
39
+ mode: 'required'
40
+ },
41
+ cors: CORS_CONFIG,
42
+
43
+ validate: {
44
+ options: {
45
+ stripUnknown: false,
46
+ abortEarly: false,
47
+ convert: true
48
+ },
49
+ failAction,
50
+
51
+ query: Joi.object({
52
+ page: Joi.number()
53
+ .integer()
54
+ .min(0)
55
+ .max(1024 * 1024)
56
+ .default(0)
57
+ .example(0)
58
+ .description('Page number (zero indexed, so use 0 for first page)')
59
+ .label('PageNumber'),
60
+ pageSize: Joi.number().integer().min(1).max(1000).default(20).example(20).description('How many entries per page').label('PageSize')
61
+ }).label('OutbixListFilter')
62
+ },
63
+
64
+ response: {
65
+ schema: Joi.object({
66
+ total: Joi.number().integer().example(120).description('How many matching entries').label('TotalNumber'),
67
+ page: Joi.number().integer().example(0).description('Current page (0-based index)').label('PageNumber'),
68
+ pages: Joi.number().integer().example(24).description('Total page count').label('PagesNumber'),
69
+
70
+ messages: Joi.array().items(outboxEntrySchema).label('OutboxListEntries')
71
+ }).label('OutboxListResponse'),
72
+ failAction: 'log'
73
+ }
74
+ }
75
+ });
76
+
77
+ server.route({
78
+ method: 'GET',
79
+ path: '/v1/outbox/{queueId}',
80
+
81
+ async handler(request) {
82
+ try {
83
+ let outboxEntry = await outbox.get({ queueId: request.params.queueId, logger });
84
+ if (!outboxEntry) {
85
+ let message = 'Requested queue entry was not found';
86
+ let error = Boom.boomify(new Error(message), { statusCode: 404 });
87
+ throw error;
88
+ }
89
+ return outboxEntry;
90
+ } catch (err) {
91
+ handleError(request, err);
92
+ }
93
+ },
94
+
95
+ options: {
96
+ description: 'Get queued message',
97
+ notes: 'Gets a queued message in the Outbox',
98
+ tags: ['api', 'Outbox'],
99
+
100
+ plugins: {
101
+ 'hapi-swagger': {
102
+ responses: errorResponses(400, 401, 403, 404, 429, 500)
103
+ }
104
+ },
105
+
106
+ auth: {
107
+ strategy: 'api-token',
108
+ mode: 'required'
109
+ },
110
+ cors: CORS_CONFIG,
111
+
112
+ validate: {
113
+ options: {
114
+ stripUnknown: false,
115
+ abortEarly: false,
116
+ convert: true
117
+ },
118
+ failAction,
119
+
120
+ params: Joi.object({
121
+ queueId: Joi.string().max(100).example('d41f0423195f271f').description('Queue identifier for scheduled email').required()
122
+ }).label('OutboxEntryParams')
123
+ },
124
+
125
+ response: {
126
+ schema: outboxEntrySchema,
127
+ failAction: 'log'
128
+ }
129
+ }
130
+ });
131
+
132
+ server.route({
133
+ method: 'DELETE',
134
+ path: '/v1/outbox/{queueId}',
135
+
136
+ async handler(request) {
137
+ try {
138
+ return {
139
+ deleted: await outbox.del({ queueId: request.params.queueId, logger })
140
+ };
141
+ } catch (err) {
142
+ handleError(request, err);
143
+ }
144
+ },
145
+ options: {
146
+ description: 'Remove a message',
147
+ notes: 'Remove a message from the outbox',
148
+ tags: ['api', 'Outbox'],
149
+
150
+ plugins: {
151
+ 'hapi-swagger': {
152
+ responses: errorResponses(400, 401, 403, 429, 500)
153
+ }
154
+ },
155
+
156
+ auth: {
157
+ strategy: 'api-token',
158
+ mode: 'required'
159
+ },
160
+ cors: CORS_CONFIG,
161
+
162
+ validate: {
163
+ options: {
164
+ stripUnknown: false,
165
+ abortEarly: false,
166
+ convert: true
167
+ },
168
+ failAction,
169
+
170
+ params: Joi.object({
171
+ queueId: Joi.string().max(100).example('d41f0423195f271f').description('Queue identifier for scheduled email').required()
172
+ }).label('OutboxEntryParams')
173
+ },
174
+
175
+ response: {
176
+ schema: Joi.object({
177
+ deleted: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(true).description('Was the message deleted')
178
+ }).label('DeleteOutboxEntryResponse'),
179
+ failAction: 'log'
180
+ }
181
+ }
182
+ });
183
+ }
184
+
185
+ module.exports = init;
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ const Joi = require('joi');
4
+ const { failAction } = require('../tools');
5
+ const { oauth2Apps } = require('../oauth2-apps');
6
+ const { pubSubErrorSchema, errorResponses } = 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
+ 'hapi-swagger': {
43
+ responses: errorResponses(400, 401, 403, 429, 500)
44
+ }
45
+ },
46
+
47
+ auth: {
48
+ strategy: 'api-token',
49
+ mode: 'required'
50
+ },
51
+ cors: CORS_CONFIG,
52
+
53
+ validate: {
54
+ options: {
55
+ stripUnknown: false,
56
+ abortEarly: false,
57
+ convert: true
58
+ },
59
+ failAction,
60
+
61
+ query: Joi.object({
62
+ page: Joi.number()
63
+ .integer()
64
+ .min(0)
65
+ .max(1024 * 1024)
66
+ .default(0)
67
+ .example(0)
68
+ .description('Page number (zero indexed, so use 0 for first page)')
69
+ .label('PageNumber'),
70
+ pageSize: Joi.number().integer().min(1).max(1000).default(20).example(20).description('How many entries per page').label('PageSize')
71
+ }).label('PubSubStatusFilter')
72
+ },
73
+
74
+ response: {
75
+ schema: Joi.object({
76
+ total: Joi.number().integer().example(120).description('How many matching entries').label('TotalNumber'),
77
+ page: Joi.number().integer().example(0).description('Current page (0-based index)').label('PageNumber'),
78
+ pages: Joi.number().integer().example(24).description('Total page count').label('PagesNumber'),
79
+
80
+ apps: Joi.array()
81
+ .items(
82
+ Joi.object({
83
+ id: Joi.string().max(256).required().example('AAABhaBPHscAAAAH').description('OAuth2 application ID'),
84
+ name: Joi.string().allow(null).max(256).example('My Gmail App').description('Display name for the app'),
85
+ lastError: Joi.object({
86
+ response: Joi.string().example('Enable the Cloud Pub/Sub API').description('Error message')
87
+ })
88
+ .allow(null)
89
+ .description('Last Pub/Sub related error for this app - either a setup error or an OAuth2 token renewal failure')
90
+ .label('PubSubSetupError'),
91
+ pubSubError: pubSubErrorSchema.allow(null)
92
+ }).label('PubSubAppStatus')
93
+ )
94
+ .label('PubSubAppStatusList')
95
+ }).label('PubSubStatusResponse'),
96
+ failAction: 'log'
97
+ }
98
+ }
99
+ });
100
+ }
101
+
102
+ module.exports = init;
@@ -0,0 +1,58 @@
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
+ if (err.details) {
25
+ error.output.payload.details = err.details;
26
+ }
27
+ throw error;
28
+ }
29
+
30
+ // Throws the canonical 404 error used when an entity getter returns a falsy value. Matches the
31
+ // error shape that the lib-level update() methods (templates, webhooks, oauth2-apps) already throw
32
+ // for missing documents, so API clients see the same error for both code paths.
33
+ function throwNotFound(message = 'Document was not found') {
34
+ let err = new Error(message);
35
+ err.code = 'NotFound';
36
+ err.statusCode = 404;
37
+ throw err;
38
+ }
39
+
40
+ // Strips the internal `meta` field from an OAuth2 application object before returning it to the API
41
+ // client, surfacing any authentication or Pub/Sub error messages as `lastError`/`pubSubError`.
42
+ // Pure function: it mutates the passed object and closes over no module state.
43
+ function flattenOAuthAppMeta(app) {
44
+ if (!app.meta) {
45
+ return;
46
+ }
47
+ let authFlag = app.meta.authFlag;
48
+ let pubSubFlag = app.meta.pubSubFlag;
49
+ delete app.meta;
50
+ if (authFlag && authFlag.message) {
51
+ app.lastError = { response: authFlag.message };
52
+ }
53
+ if (pubSubFlag && pubSubFlag.message) {
54
+ app.pubSubError = { message: pubSubFlag.message, description: pubSubFlag.description || null };
55
+ }
56
+ }
57
+
58
+ module.exports = { handleError, throwNotFound, flattenOAuthAppMeta };
@@ -0,0 +1,357 @@
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, errorResponses } = require('../schemas');
11
+
12
+ // Response variant of the settings schema. Secret values are masked and returned as booleans,
13
+ // any setting that has never been set is returned as null, and the virtual eventTypes key is
14
+ // not part of the stored settings.
15
+ const settingsOutputSchema = {};
16
+ for (let key of Object.keys(settingsSchema)) {
17
+ if (settings.encryptedKeys.includes(key)) {
18
+ settingsOutputSchema[key] = Joi.boolean()
19
+ .allow(null)
20
+ .example(true)
21
+ .description('Whether a value is set for this setting. Secret values are never returned, only a boolean marker');
22
+ } else {
23
+ // Use a distinct label for the nullable response variant, otherwise the generated
24
+ // OpenAPI spec would contain suffixed duplicates of the request-side components
25
+ settingsOutputSchema[key] = settingsSchema[key].allow(null).label(`${key}Response`);
26
+ }
27
+ }
28
+ settingsOutputSchema.eventTypes = Joi.array()
29
+ .items(Joi.string().example('messageNew').label('EventTypeEntry'))
30
+ .description('Supported webhook event types')
31
+ .label('EventTypesList');
32
+
33
+ async function init(args) {
34
+ const { server, notify, CORS_CONFIG } = args;
35
+
36
+ server.route({
37
+ method: 'GET',
38
+ path: '/v1/settings',
39
+
40
+ async handler(request) {
41
+ let values = {};
42
+ for (let key of Object.keys(request.query)) {
43
+ if (request.query[key]) {
44
+ if (key === 'eventTypes') {
45
+ values[key] = Object.keys(consts)
46
+ .filter(key => /_NOTIFY?/.test(key))
47
+ .map(key => consts[key]);
48
+ continue;
49
+ }
50
+
51
+ let value = await settings.get(key);
52
+
53
+ if (settings.encryptedKeys.includes(key)) {
54
+ // do not reveal secret values
55
+ // instead show boolean value true if value is set, or false if it's not
56
+ value = value ? true : false;
57
+ }
58
+
59
+ values[key] = value;
60
+ }
61
+ }
62
+ return values;
63
+ },
64
+ options: {
65
+ description: 'List specific settings',
66
+ notes: 'List setting values for specific keys',
67
+ tags: ['api', 'Settings'],
68
+
69
+ plugins: {
70
+ 'hapi-swagger': {
71
+ responses: errorResponses(400, 401, 403, 429, 500)
72
+ }
73
+ },
74
+
75
+ auth: {
76
+ strategy: 'api-token',
77
+ mode: 'required'
78
+ },
79
+ cors: CORS_CONFIG,
80
+
81
+ validate: {
82
+ options: {
83
+ stripUnknown: false,
84
+ abortEarly: false,
85
+ convert: true
86
+ },
87
+ failAction,
88
+
89
+ query: Joi.object(settingsQuerySchema).label('SettingsQuery')
90
+ },
91
+
92
+ response: {
93
+ schema: Joi.object(settingsOutputSchema).label('SettingsQueryResponse'),
94
+ failAction: 'log'
95
+ }
96
+ }
97
+ });
98
+
99
+ server.route({
100
+ method: 'POST',
101
+ path: '/v1/settings',
102
+
103
+ async handler(request) {
104
+ let updated = [];
105
+ for (let key of Object.keys(request.payload)) {
106
+ if (key === 'webhooksEnabled' && !request.payload.webhooksEnabled) {
107
+ // clear error message (if exists)
108
+ await settings.clear('webhookErrorFlag');
109
+ }
110
+
111
+ await settings.set(key, request.payload[key]);
112
+ updated.push(key);
113
+ }
114
+
115
+ // Broadcast to all workers (including this one); each reloads its HTTP proxy agent via
116
+ // the 'settings' message handler, so no inline reload is needed here.
117
+ notify('settings', request.payload);
118
+ return { updated };
119
+ },
120
+ options: {
121
+ description: 'Set setting values',
122
+ notes: 'Set setting values for specific keys',
123
+ tags: ['api', 'Settings'],
124
+
125
+ plugins: {
126
+ 'hapi-swagger': {
127
+ responses: errorResponses(400, 401, 403, 429, 500)
128
+ }
129
+ },
130
+
131
+ auth: {
132
+ strategy: 'api-token',
133
+ mode: 'required'
134
+ },
135
+ cors: CORS_CONFIG,
136
+
137
+ validate: {
138
+ options: {
139
+ stripUnknown: false,
140
+ abortEarly: false,
141
+ convert: true
142
+ },
143
+ failAction,
144
+
145
+ payload: Joi.object(settingsSchema).label('Settings')
146
+ },
147
+
148
+ response: {
149
+ schema: Joi.object({
150
+ updated: Joi.array().items(Joi.string().example('notifyHeaders')).description('List of updated setting keys').label('UpdatedSettings')
151
+ }).label('SettingsUpdatedResponse'),
152
+ failAction: 'log'
153
+ }
154
+ }
155
+ });
156
+
157
+ server.route({
158
+ method: 'GET',
159
+ path: '/v1/settings/queue/{queue}',
160
+
161
+ async handler(request) {
162
+ try {
163
+ let queue = request.params.queue;
164
+ let values = {
165
+ queue
166
+ };
167
+
168
+ const [resActive, resDelayed, resPaused, resWaiting, resMeta] = await redis
169
+ .multi()
170
+ .llen(`${REDIS_PREFIX}bull:${queue}:active`)
171
+ .zcard(`${REDIS_PREFIX}bull:${queue}:delayed`)
172
+ .llen(`${REDIS_PREFIX}bull:${queue}:paused`)
173
+ .llen(`${REDIS_PREFIX}bull:${queue}:wait`)
174
+ .hget(`${REDIS_PREFIX}bull:${queue}:meta`, 'paused')
175
+ .exec();
176
+
177
+ if (resActive[0] || resDelayed[0] || resPaused[0] || resWaiting[0]) {
178
+ // counting failed
179
+ let err = new Error('Failed to count queue length');
180
+ err.statusCode = 500;
181
+ throw err;
182
+ }
183
+
184
+ values.jobs = {
185
+ active: Number(resActive[1]) || 0,
186
+ delayed: Number(resDelayed[1]) || 0,
187
+ paused: Number(resPaused[1]) || 0,
188
+ waiting: Number(resWaiting[1]) || 0
189
+ };
190
+
191
+ values.paused = !!Number(resMeta[1]) || false;
192
+
193
+ return values;
194
+ } catch (err) {
195
+ handleError(request, err);
196
+ }
197
+ },
198
+ options: {
199
+ description: 'Show queue information',
200
+ notes: 'Show queue status and current state',
201
+ tags: ['api', 'Settings'],
202
+
203
+ plugins: {
204
+ 'hapi-swagger': {
205
+ responses: errorResponses(400, 401, 403, 429, 500)
206
+ }
207
+ },
208
+
209
+ auth: {
210
+ strategy: 'api-token',
211
+ mode: 'required'
212
+ },
213
+ cors: CORS_CONFIG,
214
+
215
+ validate: {
216
+ options: {
217
+ stripUnknown: false,
218
+ abortEarly: false,
219
+ convert: true
220
+ },
221
+ failAction,
222
+
223
+ params: Joi.object({
224
+ queue: Joi.string()
225
+ .empty('')
226
+ .trim()
227
+ .valid('notify', 'submit', 'documents')
228
+ .required()
229
+ .example('notify')
230
+ .description('Queue ID')
231
+ .label('QueueId')
232
+ })
233
+ },
234
+
235
+ response: {
236
+ schema: Joi.object({
237
+ queue: Joi.string()
238
+ .empty('')
239
+ .trim()
240
+ .valid('notify', 'submit', 'documents')
241
+ .required()
242
+ .example('notify')
243
+ .description('Queue ID')
244
+ .label('QueueIdResponse'),
245
+ jobs: Joi.object({
246
+ active: Joi.number().integer().example(123).description('Jobs that are currently being processed'),
247
+ delayed: Joi.number().integer().example(123).description('Jobs that are processed in the future'),
248
+ paused: Joi.number().integer().example(123).description('Jobs that would be processed once queue processing is resumed'),
249
+ waiting: Joi.number()
250
+ .integer()
251
+ .example(123)
252
+ .description('Jobs that should be processed, but are waiting until there are any free handlers')
253
+ }).label('QueueJobs'),
254
+ paused: Joi.boolean().example(false).description('Is the queue paused or not')
255
+ }).label('SettingsQueueResponse'),
256
+ failAction: 'log'
257
+ }
258
+ }
259
+ });
260
+
261
+ server.route({
262
+ method: 'PUT',
263
+ path: '/v1/settings/queue/{queue}',
264
+
265
+ async handler(request) {
266
+ try {
267
+ let queue = request.params.queue;
268
+
269
+ let queueObj = {
270
+ documents: documentsQueue,
271
+ notify: notifyQueue,
272
+ submit: submitQueue
273
+ }[queue];
274
+
275
+ let values = {
276
+ queue
277
+ };
278
+
279
+ for (let key of Object.keys(request.payload)) {
280
+ switch (key) {
281
+ case 'paused':
282
+ if (request.payload[key]) {
283
+ await queueObj.pause();
284
+ } else {
285
+ await queueObj.resume();
286
+ }
287
+ break;
288
+ }
289
+ }
290
+
291
+ values.paused = await queueObj.isPaused();
292
+
293
+ return values;
294
+ } catch (err) {
295
+ handleError(request, err);
296
+ }
297
+ },
298
+ options: {
299
+ description: 'Set queue settings',
300
+ notes: 'Set queue settings',
301
+ tags: ['api', 'Settings'],
302
+
303
+ plugins: {
304
+ 'hapi-swagger': {
305
+ responses: errorResponses(400, 401, 403, 429, 500)
306
+ }
307
+ },
308
+
309
+ auth: {
310
+ strategy: 'api-token',
311
+ mode: 'required'
312
+ },
313
+ cors: CORS_CONFIG,
314
+
315
+ validate: {
316
+ options: {
317
+ stripUnknown: false,
318
+ abortEarly: false,
319
+ convert: true
320
+ },
321
+ failAction,
322
+
323
+ params: Joi.object({
324
+ queue: Joi.string()
325
+ .empty('')
326
+ .trim()
327
+ .valid('notify', 'submit', 'documents')
328
+ .required()
329
+ .example('notify')
330
+ .description('Queue ID')
331
+ .label('QueueIdParam')
332
+ }),
333
+
334
+ payload: Joi.object({
335
+ paused: Joi.boolean().empty('').example(false).description('Set queue state to paused')
336
+ }).label('SettingsPutQueuePayload')
337
+ },
338
+
339
+ response: {
340
+ schema: Joi.object({
341
+ queue: Joi.string()
342
+ .empty('')
343
+ .trim()
344
+ .valid('notify', 'submit', 'documents')
345
+ .required()
346
+ .example('notify')
347
+ .description('Queue ID')
348
+ .label('QueueIdPutResponse'),
349
+ paused: Joi.boolean().example(false).description('Is the queue paused or not')
350
+ }).label('SettingsPutQueueResponse'),
351
+ failAction: 'log'
352
+ }
353
+ }
354
+ });
355
+ }
356
+
357
+ module.exports = init;