emailengine-app 2.61.1 → 2.61.3
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.
- package/CHANGELOG.md +17 -0
- package/data/google-crawlers.json +1 -1
- package/lib/account/account-state.js +248 -0
- package/lib/account.js +45 -193
- package/lib/api-routes/account-routes.js +1023 -0
- package/lib/api-routes/message-routes.js +1377 -0
- package/lib/consts.js +12 -2
- package/lib/email-client/base-client.js +282 -771
- package/lib/email-client/gmail/gmail-api.js +243 -0
- package/lib/email-client/gmail-client.js +145 -53
- package/lib/email-client/imap/mailbox.js +24 -698
- package/lib/email-client/imap/sync-operations.js +812 -0
- package/lib/email-client/imap-client.js +1 -1
- package/lib/email-client/message-builder.js +566 -0
- package/lib/email-client/notification-handler.js +314 -0
- package/lib/email-client/outlook/graph-api.js +326 -0
- package/lib/email-client/outlook-client.js +159 -113
- package/lib/email-client/smtp-pool-manager.js +196 -0
- package/lib/imapproxy/imap-server.js +3 -12
- package/lib/oauth/gmail.js +4 -4
- package/lib/oauth/mail-ru.js +30 -5
- package/lib/oauth/outlook.js +57 -3
- package/lib/oauth/pubsub/google.js +30 -11
- package/lib/oauth/scope-checker.js +202 -0
- package/lib/oauth2-apps.js +8 -4
- package/lib/redis-operations.js +484 -0
- package/lib/routes-ui.js +283 -2582
- package/lib/tools.js +4 -196
- package/lib/ui-routes/account-routes.js +1931 -0
- package/lib/ui-routes/admin-config-routes.js +1233 -0
- package/lib/ui-routes/admin-entities-routes.js +2367 -0
- package/lib/ui-routes/oauth-routes.js +992 -0
- package/lib/utils/network.js +237 -0
- package/package.json +10 -10
- package/sbom.json +1 -1
- package/static/js/app.js +5 -5
- package/static/licenses.html +79 -19
- package/translations/de.mo +0 -0
- package/translations/de.po +97 -86
- package/translations/en.mo +0 -0
- package/translations/en.po +80 -75
- package/translations/et.mo +0 -0
- package/translations/et.po +96 -86
- package/translations/fr.mo +0 -0
- package/translations/fr.po +97 -86
- package/translations/ja.mo +0 -0
- package/translations/ja.po +96 -86
- package/translations/messages.pot +105 -91
- package/translations/nl.mo +0 -0
- package/translations/nl.po +98 -86
- package/translations/pl.mo +0 -0
- package/translations/pl.po +96 -86
- package/views/account/security.hbs +4 -4
- package/views/accounts/account.hbs +13 -13
- package/views/accounts/register/imap-server.hbs +12 -12
- package/views/config/document-store/pre-processing/index.hbs +4 -2
- package/views/config/oauth/app.hbs +6 -7
- package/views/config/oauth/index.hbs +2 -2
- package/views/config/service.hbs +3 -4
- package/views/dashboard.hbs +5 -7
- package/views/error.hbs +22 -7
- package/views/gateways/gateway.hbs +2 -2
- package/views/partials/add_account_modal.hbs +7 -10
- package/views/partials/document_store_header.hbs +1 -1
- package/views/partials/editor_scope_info.hbs +0 -1
- package/views/partials/oauth_config_header.hbs +1 -1
- package/views/partials/side_menu.hbs +3 -3
- package/views/partials/webhook_form.hbs +2 -2
- package/views/templates/index.hbs +1 -1
- package/views/templates/template.hbs +8 -8
- package/views/tokens/index.hbs +6 -6
- package/views/tokens/new.hbs +1 -1
- package/views/webhooks/index.hbs +4 -4
- package/views/webhooks/webhook.hbs +7 -7
- package/workers/api.js +148 -2436
- package/workers/smtp.js +2 -1
- package/lib/imapproxy/imap-core/test/client.js +0 -46
- package/lib/imapproxy/imap-core/test/fixtures/append.eml +0 -1196
- package/lib/imapproxy/imap-core/test/fixtures/chunks.js +0 -44
- package/lib/imapproxy/imap-core/test/fixtures/fix1.eml +0 -6
- package/lib/imapproxy/imap-core/test/fixtures/fix2.eml +0 -599
- package/lib/imapproxy/imap-core/test/fixtures/fix3.eml +0 -32
- package/lib/imapproxy/imap-core/test/fixtures/fix4.eml +0 -6
- package/lib/imapproxy/imap-core/test/fixtures/mimetorture.eml +0 -599
- package/lib/imapproxy/imap-core/test/fixtures/mimetorture.js +0 -2740
- package/lib/imapproxy/imap-core/test/fixtures/mimetorture.json +0 -1411
- package/lib/imapproxy/imap-core/test/fixtures/mimetree.js +0 -85
- package/lib/imapproxy/imap-core/test/fixtures/nodemailer.eml +0 -582
- package/lib/imapproxy/imap-core/test/fixtures/ryan_finnie_mime_torture.eml +0 -599
- package/lib/imapproxy/imap-core/test/fixtures/simple.eml +0 -42
- package/lib/imapproxy/imap-core/test/fixtures/simple.json +0 -164
- package/lib/imapproxy/imap-core/test/imap-compile-stream-test.js +0 -671
- package/lib/imapproxy/imap-core/test/imap-compiler-test.js +0 -272
- package/lib/imapproxy/imap-core/test/imap-indexer-test.js +0 -236
- package/lib/imapproxy/imap-core/test/imap-parser-test.js +0 -922
- package/lib/imapproxy/imap-core/test/memory-notifier.js +0 -129
- package/lib/imapproxy/imap-core/test/prepare.sh +0 -74
- package/lib/imapproxy/imap-core/test/protocol-test.js +0 -1756
- package/lib/imapproxy/imap-core/test/search-test.js +0 -1356
- package/lib/imapproxy/imap-core/test/test-client.js +0 -152
- package/lib/imapproxy/imap-core/test/test-server.js +0 -623
- package/lib/imapproxy/imap-core/test/tools-test.js +0 -22
- package/test/api-test.js +0 -899
- package/test/autoreply-test.js +0 -327
- package/test/bounce-test.js +0 -151
- package/test/complaint-test.js +0 -256
- package/test/fixtures/autoreply/LICENSE +0 -27
- package/test/fixtures/autoreply/rfc3834-01.eml +0 -23
- package/test/fixtures/autoreply/rfc3834-02.eml +0 -24
- package/test/fixtures/autoreply/rfc3834-03.eml +0 -26
- package/test/fixtures/autoreply/rfc3834-04.eml +0 -48
- package/test/fixtures/autoreply/rfc3834-05.eml +0 -19
- package/test/fixtures/autoreply/rfc3834-06.eml +0 -59
- package/test/fixtures/bounces/163.eml +0 -2521
- package/test/fixtures/bounces/fastmail.eml +0 -242
- package/test/fixtures/bounces/gmail.eml +0 -252
- package/test/fixtures/bounces/hotmail.eml +0 -655
- package/test/fixtures/bounces/mailru.eml +0 -121
- package/test/fixtures/bounces/outlook.eml +0 -1107
- package/test/fixtures/bounces/postfix.eml +0 -101
- package/test/fixtures/bounces/rambler.eml +0 -116
- package/test/fixtures/bounces/workmail.eml +0 -142
- package/test/fixtures/bounces/yahoo.eml +0 -139
- package/test/fixtures/bounces/zoho.eml +0 -83
- package/test/fixtures/bounces/zonemta.eml +0 -100
- package/test/fixtures/complaints/LICENSE +0 -27
- package/test/fixtures/complaints/amazonses.eml +0 -72
- package/test/fixtures/complaints/dmarc.eml +0 -59
- package/test/fixtures/complaints/hotmail.eml +0 -49
- package/test/fixtures/complaints/optout.eml +0 -40
- package/test/fixtures/complaints/standard-arf.eml +0 -68
- package/test/fixtures/complaints/yahoo.eml +0 -68
- package/test/oauth2-apps-test.js +0 -301
- package/test/sendonly-test.js +0 -160
- package/test/test-config.js +0 -34
- package/test/webhooks-server.js +0 -39
|
@@ -0,0 +1,1377 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { redis } = require('../db');
|
|
4
|
+
const { Account } = require('../account');
|
|
5
|
+
const getSecret = require('../get-secret');
|
|
6
|
+
const settings = require('../settings');
|
|
7
|
+
const Boom = require('@hapi/boom');
|
|
8
|
+
const Joi = require('joi');
|
|
9
|
+
const { failAction } = require('../tools');
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
accountIdSchema,
|
|
13
|
+
messageDetailsSchema,
|
|
14
|
+
messageListSchema,
|
|
15
|
+
documentStoreSchema,
|
|
16
|
+
searchSchema,
|
|
17
|
+
messageUpdateSchema,
|
|
18
|
+
addressSchema,
|
|
19
|
+
fromAddressSchema,
|
|
20
|
+
messageReferenceSchema
|
|
21
|
+
} = require('../schemas');
|
|
22
|
+
|
|
23
|
+
const listMessageFolderPathDescription =
|
|
24
|
+
'Mailbox folder path. Can use special use labels like "\\Sent". Special value "\\All" is available for Gmail IMAP, Gmail API, MS Graph API accounts.';
|
|
25
|
+
|
|
26
|
+
async function init(args) {
|
|
27
|
+
const { server, call, CORS_CONFIG, MAX_ATTACHMENT_SIZE, MAX_BODY_SIZE, MAX_PAYLOAD_TIMEOUT } = args;
|
|
28
|
+
|
|
29
|
+
// GET /v1/account/{account}/message/{message}/source - Download raw message
|
|
30
|
+
server.route({
|
|
31
|
+
method: 'GET',
|
|
32
|
+
path: '/v1/account/{account}/message/{message}/source',
|
|
33
|
+
|
|
34
|
+
async handler(request, h) {
|
|
35
|
+
let accountObject = new Account({
|
|
36
|
+
redis,
|
|
37
|
+
account: request.params.account,
|
|
38
|
+
call,
|
|
39
|
+
secret: await getSecret(),
|
|
40
|
+
timeout: request.headers['x-ee-timeout']
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const response = await accountObject.getRawMessage(request.params.message);
|
|
45
|
+
return h.response(response);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
48
|
+
if (Boom.isBoom(err)) {
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
52
|
+
if (err.code) {
|
|
53
|
+
error.output.payload.code = err.code;
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
options: {
|
|
59
|
+
description: 'Download raw message',
|
|
60
|
+
notes: 'Fetches raw message as a stream',
|
|
61
|
+
tags: ['api', 'Message'],
|
|
62
|
+
|
|
63
|
+
auth: {
|
|
64
|
+
strategy: 'api-token',
|
|
65
|
+
mode: 'required'
|
|
66
|
+
},
|
|
67
|
+
cors: CORS_CONFIG,
|
|
68
|
+
|
|
69
|
+
plugins: {
|
|
70
|
+
'hapi-swagger': {
|
|
71
|
+
produces: ['message/rfc822']
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
validate: {
|
|
76
|
+
options: {
|
|
77
|
+
stripUnknown: false,
|
|
78
|
+
abortEarly: false,
|
|
79
|
+
convert: true
|
|
80
|
+
},
|
|
81
|
+
failAction,
|
|
82
|
+
|
|
83
|
+
params: Joi.object({
|
|
84
|
+
account: accountIdSchema.required(),
|
|
85
|
+
message: Joi.string().base64({ paddingRequired: false, urlSafe: true }).max(256).example('AAAAAQAACnA').required().description('Message ID')
|
|
86
|
+
}).label('RawMessageRequest')
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// GET /v1/account/{account}/message/{message} - Get message information
|
|
92
|
+
server.route({
|
|
93
|
+
method: 'GET',
|
|
94
|
+
path: '/v1/account/{account}/message/{message}',
|
|
95
|
+
|
|
96
|
+
async handler(request, h) {
|
|
97
|
+
let accountObject = new Account({
|
|
98
|
+
redis,
|
|
99
|
+
account: request.params.account,
|
|
100
|
+
call,
|
|
101
|
+
secret: await getSecret(),
|
|
102
|
+
esClient: await h.getESClient(request.logger),
|
|
103
|
+
timeout: request.headers['x-ee-timeout']
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
return await accountObject.getMessage(request.params.message, request.query);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
110
|
+
if (Boom.isBoom(err)) {
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
114
|
+
if (err.code) {
|
|
115
|
+
error.output.payload.code = err.code;
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
options: {
|
|
121
|
+
description: 'Get message information',
|
|
122
|
+
notes: 'Returns details of a specific message. By default text content is not included, use textType value to force retrieving text',
|
|
123
|
+
tags: ['api', 'Message'],
|
|
124
|
+
|
|
125
|
+
auth: {
|
|
126
|
+
strategy: 'api-token',
|
|
127
|
+
mode: 'required'
|
|
128
|
+
},
|
|
129
|
+
cors: CORS_CONFIG,
|
|
130
|
+
|
|
131
|
+
validate: {
|
|
132
|
+
options: {
|
|
133
|
+
stripUnknown: false,
|
|
134
|
+
abortEarly: false,
|
|
135
|
+
convert: true
|
|
136
|
+
},
|
|
137
|
+
failAction,
|
|
138
|
+
|
|
139
|
+
query: Joi.object({
|
|
140
|
+
maxBytes: Joi.number()
|
|
141
|
+
.integer()
|
|
142
|
+
.min(0)
|
|
143
|
+
.max(1024 * 1024 * 1024)
|
|
144
|
+
.example(5 * 1025 * 1024)
|
|
145
|
+
.description('Max length of text content'),
|
|
146
|
+
textType: Joi.string()
|
|
147
|
+
.lowercase()
|
|
148
|
+
.valid('html', 'plain', '*')
|
|
149
|
+
.example('*')
|
|
150
|
+
.description('Which text content to return, use * for all. By default text content is not returned.'),
|
|
151
|
+
|
|
152
|
+
webSafeHtml: Joi.boolean()
|
|
153
|
+
.truthy('Y', 'true', '1')
|
|
154
|
+
.falsy('N', 'false', 0)
|
|
155
|
+
.default(false)
|
|
156
|
+
.description(
|
|
157
|
+
'Shorthand option to fetch and preprocess HTML and inline images. Overrides `textType`, `preProcessHtml`, and `embedAttachedImages` options.'
|
|
158
|
+
)
|
|
159
|
+
.label('WebSafeHtml'),
|
|
160
|
+
|
|
161
|
+
embedAttachedImages: Joi.boolean()
|
|
162
|
+
.truthy('Y', 'true', '1')
|
|
163
|
+
.falsy('N', 'false', 0)
|
|
164
|
+
.default(false)
|
|
165
|
+
.description('If true, then fetches attached images and embeds these in the HTML as data URIs')
|
|
166
|
+
.label('EmbedImages'),
|
|
167
|
+
|
|
168
|
+
preProcessHtml: Joi.boolean()
|
|
169
|
+
.truthy('Y', 'true', '1')
|
|
170
|
+
.falsy('N', 'false', 0)
|
|
171
|
+
.default(false)
|
|
172
|
+
.description('If true, then pre-processes HTML for compatibility')
|
|
173
|
+
.label('PreProcess'),
|
|
174
|
+
|
|
175
|
+
markAsSeen: Joi.boolean()
|
|
176
|
+
.truthy('Y', 'true', '1')
|
|
177
|
+
.falsy('N', 'false', 0)
|
|
178
|
+
.default(false)
|
|
179
|
+
.description('If true, then marks unseen email as seen while returning the message')
|
|
180
|
+
.label('MarkAsSeen'),
|
|
181
|
+
|
|
182
|
+
documentStore: documentStoreSchema.default(false)
|
|
183
|
+
}),
|
|
184
|
+
|
|
185
|
+
params: Joi.object({
|
|
186
|
+
account: accountIdSchema.required(),
|
|
187
|
+
message: Joi.string().base64({ paddingRequired: false, urlSafe: true }).max(256).required().example('AAAAAQAACnA').description('Message ID')
|
|
188
|
+
})
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
response: {
|
|
192
|
+
schema: messageDetailsSchema,
|
|
193
|
+
failAction: 'log'
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// POST /v1/account/{account}/message - Upload message
|
|
199
|
+
server.route({
|
|
200
|
+
method: 'POST',
|
|
201
|
+
path: '/v1/account/{account}/message',
|
|
202
|
+
|
|
203
|
+
async handler(request) {
|
|
204
|
+
let accountObject = new Account({
|
|
205
|
+
redis,
|
|
206
|
+
account: request.params.account,
|
|
207
|
+
call,
|
|
208
|
+
secret: await getSecret(),
|
|
209
|
+
timeout: request.headers['x-ee-timeout']
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
return await accountObject.uploadMessage(request.payload);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
216
|
+
if (Boom.isBoom(err)) {
|
|
217
|
+
throw err;
|
|
218
|
+
}
|
|
219
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
220
|
+
if (err.code) {
|
|
221
|
+
error.output.payload.code = err.code;
|
|
222
|
+
}
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
options: {
|
|
227
|
+
payload: {
|
|
228
|
+
maxBytes: MAX_BODY_SIZE,
|
|
229
|
+
timeout: MAX_PAYLOAD_TIMEOUT
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
description: 'Upload message',
|
|
233
|
+
notes: 'Upload a message structure, compile it into an EML file and store it into selected mailbox.',
|
|
234
|
+
tags: ['api', 'Message'],
|
|
235
|
+
|
|
236
|
+
plugins: {},
|
|
237
|
+
|
|
238
|
+
auth: {
|
|
239
|
+
strategy: 'api-token',
|
|
240
|
+
mode: 'required'
|
|
241
|
+
},
|
|
242
|
+
cors: CORS_CONFIG,
|
|
243
|
+
|
|
244
|
+
validate: {
|
|
245
|
+
options: {
|
|
246
|
+
stripUnknown: false,
|
|
247
|
+
abortEarly: false,
|
|
248
|
+
convert: true
|
|
249
|
+
},
|
|
250
|
+
failAction,
|
|
251
|
+
|
|
252
|
+
params: Joi.object({
|
|
253
|
+
account: accountIdSchema.required()
|
|
254
|
+
}),
|
|
255
|
+
|
|
256
|
+
payload: Joi.object({
|
|
257
|
+
path: Joi.string().required().example('INBOX').description('Target mailbox folder path'),
|
|
258
|
+
|
|
259
|
+
flags: Joi.array().items(Joi.string().max(128)).example(['\\Seen', '\\Draft']).default([]).description('Message flags').label('Flags'),
|
|
260
|
+
internalDate: Joi.date().iso().example('2021-07-08T07:06:34.336Z').description('Sets the internal date for this message'),
|
|
261
|
+
|
|
262
|
+
reference: messageReferenceSchema,
|
|
263
|
+
|
|
264
|
+
raw: Joi.string()
|
|
265
|
+
.base64()
|
|
266
|
+
.max(MAX_ATTACHMENT_SIZE)
|
|
267
|
+
.example('TUlNRS1WZXJzaW9uOiAxLjANClN1YmplY3Q6IGhlbGxvIHdvcmxkDQoNCkhlbGxvIQ0K')
|
|
268
|
+
.description(
|
|
269
|
+
'A Base64-encoded email message in RFC 822 format. If you provide other fields along with raw, those fields will override the corresponding values in the raw message.'
|
|
270
|
+
)
|
|
271
|
+
.label('RFC822Raw'),
|
|
272
|
+
|
|
273
|
+
from: fromAddressSchema,
|
|
274
|
+
|
|
275
|
+
to: Joi.array()
|
|
276
|
+
.items(addressSchema)
|
|
277
|
+
.single()
|
|
278
|
+
.description('List of addresses')
|
|
279
|
+
.example([{ address: 'recipient@example.com' }])
|
|
280
|
+
.label('AddressList'),
|
|
281
|
+
|
|
282
|
+
cc: Joi.array().items(addressSchema).single().description('List of addresses').label('AddressList'),
|
|
283
|
+
|
|
284
|
+
bcc: Joi.array().items(addressSchema).single().description('List of addresses').label('AddressList'),
|
|
285
|
+
|
|
286
|
+
subject: Joi.string()
|
|
287
|
+
.allow('')
|
|
288
|
+
.max(10 * 1024)
|
|
289
|
+
.example('What a wonderful message')
|
|
290
|
+
.description('Message subject'),
|
|
291
|
+
|
|
292
|
+
text: Joi.string().max(MAX_ATTACHMENT_SIZE).example('Hello from myself!').description('Message Text'),
|
|
293
|
+
|
|
294
|
+
html: Joi.string().max(MAX_ATTACHMENT_SIZE).example('<p>Hello from myself!</p>').description('Message HTML'),
|
|
295
|
+
|
|
296
|
+
attachments: Joi.array()
|
|
297
|
+
.items(
|
|
298
|
+
Joi.object({
|
|
299
|
+
filename: Joi.string().max(256).example('transparent.gif'),
|
|
300
|
+
content: Joi.string()
|
|
301
|
+
.base64()
|
|
302
|
+
.max(MAX_ATTACHMENT_SIZE)
|
|
303
|
+
.required()
|
|
304
|
+
.example('R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=')
|
|
305
|
+
.description('Base64 formatted attachment file')
|
|
306
|
+
.when('reference', {
|
|
307
|
+
is: Joi.exist().not(false, null),
|
|
308
|
+
then: Joi.forbidden(),
|
|
309
|
+
otherwise: Joi.required()
|
|
310
|
+
}),
|
|
311
|
+
|
|
312
|
+
contentType: Joi.string().lowercase().max(256).example('image/gif'),
|
|
313
|
+
contentDisposition: Joi.string().lowercase().valid('inline', 'attachment'),
|
|
314
|
+
cid: Joi.string().max(256).example('unique-image-id@localhost').description('Content-ID value for embedded images'),
|
|
315
|
+
encoding: Joi.string().valid('base64').default('base64'),
|
|
316
|
+
|
|
317
|
+
reference: Joi.string()
|
|
318
|
+
.base64({ paddingRequired: false, urlSafe: true })
|
|
319
|
+
.max(256)
|
|
320
|
+
.allow(false, null)
|
|
321
|
+
.example('AAAAAQAACnAcde')
|
|
322
|
+
.description(
|
|
323
|
+
'References an existing attachment by its ID instead of providing new attachment content. If this field is set, the `content` field must not be included. If not set, the `content` field is required.'
|
|
324
|
+
)
|
|
325
|
+
}).label('UploadAttachment')
|
|
326
|
+
)
|
|
327
|
+
.description('List of attachments')
|
|
328
|
+
.label('UploadAttachmentList'),
|
|
329
|
+
|
|
330
|
+
messageId: Joi.string().max(996).example('<test123@example.com>').description('Message ID'),
|
|
331
|
+
headers: Joi.object().label('CustomHeaders').description('Custom Headers').unknown().example({
|
|
332
|
+
'X-My-Custom-Header': 'Custom header value'
|
|
333
|
+
}),
|
|
334
|
+
|
|
335
|
+
locale: Joi.string().empty('').max(100).example('fr').description('Optional locale'),
|
|
336
|
+
tz: Joi.string().empty('').max(100).example('Europe/Tallinn').description('Optional timezone')
|
|
337
|
+
}).label('MessageUpload')
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
response: {
|
|
341
|
+
schema: Joi.object({
|
|
342
|
+
id: Joi.string()
|
|
343
|
+
.example('AAAAAgAACrI')
|
|
344
|
+
.description(
|
|
345
|
+
'Unique identifier for the message. NB! This and other fields might not be present if server did not provide enough information'
|
|
346
|
+
)
|
|
347
|
+
.label('MessageAppendId'),
|
|
348
|
+
path: Joi.string().example('INBOX').description('Folder this message was uploaded to').label('MessageAppendPath'),
|
|
349
|
+
uid: Joi.number().integer().example(12345).description('UID of uploaded message'),
|
|
350
|
+
uidValidity: Joi.string().example('12345').description('UIDVALIDITY of the target folder. Numeric value cast as string.'),
|
|
351
|
+
seq: Joi.number().integer().example(12345).description('Sequence number of uploaded message'),
|
|
352
|
+
|
|
353
|
+
messageId: Joi.string().max(996).example('<test123@example.com>').description('Message ID'),
|
|
354
|
+
|
|
355
|
+
reference: Joi.object({
|
|
356
|
+
message: Joi.string()
|
|
357
|
+
.base64({ paddingRequired: false, urlSafe: true })
|
|
358
|
+
.max(256)
|
|
359
|
+
.required()
|
|
360
|
+
.example('AAAAAQAACnA')
|
|
361
|
+
.description('Referenced message ID'),
|
|
362
|
+
success: Joi.boolean().example(true).description('Was the referenced message processed').label('ResponseReferenceSuccess'),
|
|
363
|
+
documentStore: documentStoreSchema.default(false),
|
|
364
|
+
error: Joi.string().example('Referenced message was not found').description('An error message if referenced message processing failed')
|
|
365
|
+
})
|
|
366
|
+
.description('Reference info if referencing was requested')
|
|
367
|
+
.label('ResponseReference')
|
|
368
|
+
}).label('MessageUploadResponse'),
|
|
369
|
+
failAction: 'log'
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// PUT /v1/account/{account}/message/{message} - Update message
|
|
375
|
+
server.route({
|
|
376
|
+
method: 'PUT',
|
|
377
|
+
path: '/v1/account/{account}/message/{message}',
|
|
378
|
+
|
|
379
|
+
async handler(request) {
|
|
380
|
+
let accountObject = new Account({
|
|
381
|
+
redis,
|
|
382
|
+
account: request.params.account,
|
|
383
|
+
call,
|
|
384
|
+
secret: await getSecret(),
|
|
385
|
+
timeout: request.headers['x-ee-timeout']
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
return await accountObject.updateMessage(request.params.message, request.payload);
|
|
390
|
+
} catch (err) {
|
|
391
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
392
|
+
if (Boom.isBoom(err)) {
|
|
393
|
+
throw err;
|
|
394
|
+
}
|
|
395
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
396
|
+
if (err.code) {
|
|
397
|
+
error.output.payload.code = err.code;
|
|
398
|
+
}
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
options: {
|
|
403
|
+
description: 'Update message',
|
|
404
|
+
notes: 'Update message information. Mainly this means changing message flag values',
|
|
405
|
+
tags: ['api', 'Message'],
|
|
406
|
+
|
|
407
|
+
plugins: {},
|
|
408
|
+
|
|
409
|
+
auth: {
|
|
410
|
+
strategy: 'api-token',
|
|
411
|
+
mode: 'required'
|
|
412
|
+
},
|
|
413
|
+
cors: CORS_CONFIG,
|
|
414
|
+
|
|
415
|
+
validate: {
|
|
416
|
+
options: {
|
|
417
|
+
stripUnknown: false,
|
|
418
|
+
abortEarly: false,
|
|
419
|
+
convert: true
|
|
420
|
+
},
|
|
421
|
+
failAction,
|
|
422
|
+
|
|
423
|
+
params: Joi.object({
|
|
424
|
+
account: accountIdSchema.required(),
|
|
425
|
+
message: Joi.string().max(256).required().example('AAAAAQAACnA').description('Message ID')
|
|
426
|
+
}),
|
|
427
|
+
|
|
428
|
+
payload: messageUpdateSchema
|
|
429
|
+
},
|
|
430
|
+
response: {
|
|
431
|
+
schema: Joi.object({
|
|
432
|
+
flags: Joi.object({
|
|
433
|
+
add: Joi.array().items(Joi.string()).example(['\\Seen', '\\Flagged']),
|
|
434
|
+
delete: Joi.array().items(Joi.string()).example(['\\Draft']),
|
|
435
|
+
set: Joi.array().items(Joi.string()).example(['\\Seen'])
|
|
436
|
+
}).label('FlagResponse'),
|
|
437
|
+
labels: Joi.object({
|
|
438
|
+
add: Joi.array().items(Joi.string()).example(['Label1', 'Label2']),
|
|
439
|
+
delete: Joi.array().items(Joi.string()).example(['Label3']),
|
|
440
|
+
set: Joi.array().items(Joi.string()).example(['Label1'])
|
|
441
|
+
}).label('FlagResponse')
|
|
442
|
+
}).label('MessageUpdateResponse'),
|
|
443
|
+
failAction: 'log'
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// PUT /v1/account/{account}/messages - Update multiple messages
|
|
449
|
+
server.route({
|
|
450
|
+
method: 'PUT',
|
|
451
|
+
path: '/v1/account/{account}/messages',
|
|
452
|
+
|
|
453
|
+
async handler(request) {
|
|
454
|
+
let accountObject = new Account({
|
|
455
|
+
redis,
|
|
456
|
+
account: request.params.account,
|
|
457
|
+
call,
|
|
458
|
+
secret: await getSecret(),
|
|
459
|
+
timeout: request.headers['x-ee-timeout']
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
try {
|
|
463
|
+
return await accountObject.updateMessages(request.query.path, request.payload.search, request.payload.update);
|
|
464
|
+
} catch (err) {
|
|
465
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
466
|
+
if (Boom.isBoom(err)) {
|
|
467
|
+
throw err;
|
|
468
|
+
}
|
|
469
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
470
|
+
if (err.code) {
|
|
471
|
+
error.output.payload.code = err.code;
|
|
472
|
+
}
|
|
473
|
+
throw error;
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
options: {
|
|
477
|
+
description: 'Update messages',
|
|
478
|
+
notes: 'Update message information for matching emails',
|
|
479
|
+
tags: ['api', 'Multi Message Actions'],
|
|
480
|
+
|
|
481
|
+
plugins: {},
|
|
482
|
+
|
|
483
|
+
auth: {
|
|
484
|
+
strategy: 'api-token',
|
|
485
|
+
mode: 'required'
|
|
486
|
+
},
|
|
487
|
+
cors: CORS_CONFIG,
|
|
488
|
+
|
|
489
|
+
validate: {
|
|
490
|
+
options: {
|
|
491
|
+
stripUnknown: false,
|
|
492
|
+
abortEarly: false,
|
|
493
|
+
convert: true
|
|
494
|
+
},
|
|
495
|
+
failAction,
|
|
496
|
+
|
|
497
|
+
params: Joi.object({
|
|
498
|
+
account: accountIdSchema.required()
|
|
499
|
+
}),
|
|
500
|
+
|
|
501
|
+
query: Joi.object({
|
|
502
|
+
path: Joi.string().empty('').required().example('INBOX').description(listMessageFolderPathDescription)
|
|
503
|
+
}).label('MessagesUpdateQuery'),
|
|
504
|
+
|
|
505
|
+
payload: Joi.object({
|
|
506
|
+
search: searchSchema,
|
|
507
|
+
update: messageUpdateSchema
|
|
508
|
+
}).label('MessagesUpdateRequest')
|
|
509
|
+
},
|
|
510
|
+
response: {
|
|
511
|
+
schema: Joi.object({
|
|
512
|
+
flags: Joi.object({
|
|
513
|
+
add: Joi.array().items(Joi.string()).example(['\\Seen', '\\Flagged']),
|
|
514
|
+
delete: Joi.array().items(Joi.string()).example(['\\Draft']),
|
|
515
|
+
set: Joi.array().items(Joi.string()).example(['\\Seen'])
|
|
516
|
+
}).label('FlagResponse'),
|
|
517
|
+
labels: Joi.object({
|
|
518
|
+
add: Joi.array().items(Joi.string()).example(['Label1', 'Label2']),
|
|
519
|
+
delete: Joi.array().items(Joi.string()).example(['Label3']),
|
|
520
|
+
set: Joi.array().items(Joi.string()).example(['Label1'])
|
|
521
|
+
}).label('FlagResponse')
|
|
522
|
+
}).label('MessageUpdateResponse'),
|
|
523
|
+
failAction: 'log'
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// PUT /v1/account/{account}/message/{message}/move - Move a message
|
|
529
|
+
server.route({
|
|
530
|
+
method: 'PUT',
|
|
531
|
+
path: '/v1/account/{account}/message/{message}/move',
|
|
532
|
+
|
|
533
|
+
async handler(request) {
|
|
534
|
+
let accountObject = new Account({
|
|
535
|
+
redis,
|
|
536
|
+
account: request.params.account,
|
|
537
|
+
call,
|
|
538
|
+
secret: await getSecret(),
|
|
539
|
+
timeout: request.headers['x-ee-timeout']
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
try {
|
|
543
|
+
let sourceOption = null;
|
|
544
|
+
if (request.payload.source) {
|
|
545
|
+
sourceOption = { path: request.payload.source };
|
|
546
|
+
}
|
|
547
|
+
return await accountObject.moveMessage(request.params.message, { path: request.payload.path }, { source: sourceOption });
|
|
548
|
+
} catch (err) {
|
|
549
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
550
|
+
if (Boom.isBoom(err)) {
|
|
551
|
+
throw err;
|
|
552
|
+
}
|
|
553
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
554
|
+
if (err.code) {
|
|
555
|
+
error.output.payload.code = err.code;
|
|
556
|
+
}
|
|
557
|
+
throw error;
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
options: {
|
|
561
|
+
description: 'Move a message to a specified folder',
|
|
562
|
+
notes: 'Moves a message to a target folder',
|
|
563
|
+
tags: ['api', 'Message'],
|
|
564
|
+
|
|
565
|
+
plugins: {},
|
|
566
|
+
|
|
567
|
+
auth: {
|
|
568
|
+
strategy: 'api-token',
|
|
569
|
+
mode: 'required'
|
|
570
|
+
},
|
|
571
|
+
cors: CORS_CONFIG,
|
|
572
|
+
|
|
573
|
+
validate: {
|
|
574
|
+
options: {
|
|
575
|
+
stripUnknown: false,
|
|
576
|
+
abortEarly: false,
|
|
577
|
+
convert: true
|
|
578
|
+
},
|
|
579
|
+
failAction,
|
|
580
|
+
|
|
581
|
+
params: Joi.object({
|
|
582
|
+
account: accountIdSchema.required(),
|
|
583
|
+
message: Joi.string().max(256).required().example('AAAAAQAACnA').description('Message ID')
|
|
584
|
+
}),
|
|
585
|
+
|
|
586
|
+
payload: Joi.object({
|
|
587
|
+
path: Joi.string().required().example('INBOX').description('Destination mailbox folder path'),
|
|
588
|
+
source: Joi.string()
|
|
589
|
+
.example('INBOX')
|
|
590
|
+
.description('Source mailbox folder path (Gmail API only). Needed to remove the label from the message.')
|
|
591
|
+
})
|
|
592
|
+
.example({ path: 'Target/Folder' })
|
|
593
|
+
.label('MessageMove')
|
|
594
|
+
},
|
|
595
|
+
|
|
596
|
+
response: {
|
|
597
|
+
schema: Joi.object({
|
|
598
|
+
path: Joi.string().required().example('INBOX').description('Destination mailbox folder path'),
|
|
599
|
+
id: Joi.string().max(256).example('AAAAAQAACnA').description('ID of the moved message. Only included if the server provides it.'),
|
|
600
|
+
uid: Joi.number()
|
|
601
|
+
.integer()
|
|
602
|
+
.example(12345)
|
|
603
|
+
.description('UID of the moved message, applies only to IMAP accounts. Only included if the server provides it.')
|
|
604
|
+
}).label('MessageMoveResponse'),
|
|
605
|
+
failAction: 'log'
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
// PUT /v1/account/{account}/messages/move - Move multiple messages
|
|
611
|
+
server.route({
|
|
612
|
+
method: 'PUT',
|
|
613
|
+
path: '/v1/account/{account}/messages/move',
|
|
614
|
+
|
|
615
|
+
async handler(request) {
|
|
616
|
+
let accountObject = new Account({
|
|
617
|
+
redis,
|
|
618
|
+
account: request.params.account,
|
|
619
|
+
call,
|
|
620
|
+
secret: await getSecret(),
|
|
621
|
+
timeout: request.headers['x-ee-timeout']
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
try {
|
|
625
|
+
return await accountObject.moveMessages(request.query.path, request.payload.search, { path: request.payload.path });
|
|
626
|
+
} catch (err) {
|
|
627
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
628
|
+
if (Boom.isBoom(err)) {
|
|
629
|
+
throw err;
|
|
630
|
+
}
|
|
631
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
632
|
+
if (err.code) {
|
|
633
|
+
error.output.payload.code = err.code;
|
|
634
|
+
}
|
|
635
|
+
throw error;
|
|
636
|
+
}
|
|
637
|
+
},
|
|
638
|
+
options: {
|
|
639
|
+
description: 'Move messages',
|
|
640
|
+
notes: 'Move messages matching to a search query to another folder',
|
|
641
|
+
tags: ['api', 'Multi Message Actions'],
|
|
642
|
+
|
|
643
|
+
plugins: {},
|
|
644
|
+
|
|
645
|
+
auth: {
|
|
646
|
+
strategy: 'api-token',
|
|
647
|
+
mode: 'required'
|
|
648
|
+
},
|
|
649
|
+
cors: CORS_CONFIG,
|
|
650
|
+
|
|
651
|
+
validate: {
|
|
652
|
+
options: {
|
|
653
|
+
stripUnknown: false,
|
|
654
|
+
abortEarly: false,
|
|
655
|
+
convert: true
|
|
656
|
+
},
|
|
657
|
+
failAction,
|
|
658
|
+
|
|
659
|
+
params: Joi.object({
|
|
660
|
+
account: accountIdSchema.required()
|
|
661
|
+
}),
|
|
662
|
+
|
|
663
|
+
query: Joi.object({
|
|
664
|
+
path: Joi.string().empty('').required().example('INBOX').description(listMessageFolderPathDescription)
|
|
665
|
+
}).label('MessagesMoveQuery'),
|
|
666
|
+
|
|
667
|
+
payload: Joi.object({
|
|
668
|
+
search: searchSchema,
|
|
669
|
+
path: Joi.string().required().example('INBOX').description('Target mailbox folder path')
|
|
670
|
+
}).label('MessagesMoveRequest')
|
|
671
|
+
},
|
|
672
|
+
|
|
673
|
+
response: {
|
|
674
|
+
schema: Joi.object({
|
|
675
|
+
path: Joi.string().required().example('INBOX').description('Target mailbox folder path'),
|
|
676
|
+
|
|
677
|
+
idMap: Joi.array()
|
|
678
|
+
.items(Joi.array().length(2).items(Joi.string().max(256).required().description('Message ID')).label('IdMapTuple'))
|
|
679
|
+
.example([['AAAAAQAACnA', 'AAAAAwAAAD4']])
|
|
680
|
+
.description('An optional map of source and target ID values, if the server provided this info')
|
|
681
|
+
.label('IdMapArray'),
|
|
682
|
+
|
|
683
|
+
emailIds: Joi.array()
|
|
684
|
+
.items(Joi.string().example('1278455344230334865'))
|
|
685
|
+
.description('An optional list of emailId values, if the server supports unique email IDs')
|
|
686
|
+
.label('EmailIdsArray')
|
|
687
|
+
}).label('MessagesMoveResponse'),
|
|
688
|
+
failAction: 'log'
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
// DELETE /v1/account/{account}/message/{message} - Delete message
|
|
694
|
+
server.route({
|
|
695
|
+
method: 'DELETE',
|
|
696
|
+
path: '/v1/account/{account}/message/{message}',
|
|
697
|
+
|
|
698
|
+
async handler(request) {
|
|
699
|
+
let accountObject = new Account({
|
|
700
|
+
redis,
|
|
701
|
+
account: request.params.account,
|
|
702
|
+
call,
|
|
703
|
+
secret: await getSecret(),
|
|
704
|
+
timeout: request.headers['x-ee-timeout']
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
try {
|
|
708
|
+
return await accountObject.deleteMessage(request.params.message, request.query.force);
|
|
709
|
+
} catch (err) {
|
|
710
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
711
|
+
if (Boom.isBoom(err)) {
|
|
712
|
+
throw err;
|
|
713
|
+
}
|
|
714
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
715
|
+
if (err.code) {
|
|
716
|
+
error.output.payload.code = err.code;
|
|
717
|
+
}
|
|
718
|
+
throw error;
|
|
719
|
+
}
|
|
720
|
+
},
|
|
721
|
+
options: {
|
|
722
|
+
description: 'Delete message',
|
|
723
|
+
notes: 'Move message to Trash or delete it if already in Trash',
|
|
724
|
+
tags: ['api', 'Message'],
|
|
725
|
+
|
|
726
|
+
plugins: {},
|
|
727
|
+
|
|
728
|
+
auth: {
|
|
729
|
+
strategy: 'api-token',
|
|
730
|
+
mode: 'required'
|
|
731
|
+
},
|
|
732
|
+
cors: CORS_CONFIG,
|
|
733
|
+
|
|
734
|
+
validate: {
|
|
735
|
+
options: {
|
|
736
|
+
stripUnknown: false,
|
|
737
|
+
abortEarly: false,
|
|
738
|
+
convert: true
|
|
739
|
+
},
|
|
740
|
+
failAction,
|
|
741
|
+
|
|
742
|
+
query: Joi.object({
|
|
743
|
+
force: Joi.boolean()
|
|
744
|
+
.truthy('Y', 'true', '1')
|
|
745
|
+
.falsy('N', 'false', 0)
|
|
746
|
+
.default(false)
|
|
747
|
+
.description('Delete message even if not in Trash. Not supported for Gmail API accounts.')
|
|
748
|
+
.label('ForceDelete')
|
|
749
|
+
}).label('MessageDeleteQuery'),
|
|
750
|
+
|
|
751
|
+
params: Joi.object({
|
|
752
|
+
account: accountIdSchema.required(),
|
|
753
|
+
message: Joi.string().max(256).required().example('AAAAAQAACnA').description('Message ID')
|
|
754
|
+
}).label('MessageDelete')
|
|
755
|
+
},
|
|
756
|
+
response: {
|
|
757
|
+
schema: Joi.object({
|
|
758
|
+
deleted: Joi.boolean().example(false).description('Was the delete action executed'),
|
|
759
|
+
moved: Joi.object({
|
|
760
|
+
destination: Joi.string().required().example('Trash').description('Trash folder path').label('TrashPath'),
|
|
761
|
+
message: Joi.string().required().example('AAAAAwAAAWg').description('Message ID in Trash').label('TrashMessageId')
|
|
762
|
+
}).description('Present if message was moved to Trash')
|
|
763
|
+
}).label('MessageDeleteResponse'),
|
|
764
|
+
failAction: 'log'
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
// PUT /v1/account/{account}/messages/delete - Delete multiple messages
|
|
770
|
+
server.route({
|
|
771
|
+
method: 'PUT',
|
|
772
|
+
path: '/v1/account/{account}/messages/delete',
|
|
773
|
+
|
|
774
|
+
async handler(request) {
|
|
775
|
+
let accountObject = new Account({
|
|
776
|
+
redis,
|
|
777
|
+
account: request.params.account,
|
|
778
|
+
call,
|
|
779
|
+
secret: await getSecret(),
|
|
780
|
+
timeout: request.headers['x-ee-timeout']
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
try {
|
|
784
|
+
return await accountObject.deleteMessages(request.query.path, request.payload.search, request.query.force);
|
|
785
|
+
} catch (err) {
|
|
786
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
787
|
+
if (Boom.isBoom(err)) {
|
|
788
|
+
throw err;
|
|
789
|
+
}
|
|
790
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
791
|
+
if (err.code) {
|
|
792
|
+
error.output.payload.code = err.code;
|
|
793
|
+
}
|
|
794
|
+
throw error;
|
|
795
|
+
}
|
|
796
|
+
},
|
|
797
|
+
options: {
|
|
798
|
+
description: 'Delete messages',
|
|
799
|
+
notes: 'Move messages to Trash or delete these if already in Trash',
|
|
800
|
+
tags: ['api', 'Multi Message Actions'],
|
|
801
|
+
|
|
802
|
+
plugins: {},
|
|
803
|
+
|
|
804
|
+
auth: {
|
|
805
|
+
strategy: 'api-token',
|
|
806
|
+
mode: 'required'
|
|
807
|
+
},
|
|
808
|
+
cors: CORS_CONFIG,
|
|
809
|
+
|
|
810
|
+
validate: {
|
|
811
|
+
options: {
|
|
812
|
+
stripUnknown: false,
|
|
813
|
+
abortEarly: false,
|
|
814
|
+
convert: true
|
|
815
|
+
},
|
|
816
|
+
failAction,
|
|
817
|
+
|
|
818
|
+
params: Joi.object({
|
|
819
|
+
account: accountIdSchema.required()
|
|
820
|
+
}),
|
|
821
|
+
|
|
822
|
+
query: Joi.object({
|
|
823
|
+
path: Joi.string().empty('').required().example('INBOX').description(listMessageFolderPathDescription),
|
|
824
|
+
force: Joi.boolean()
|
|
825
|
+
.truthy('Y', 'true', '1')
|
|
826
|
+
.falsy('N', 'false', 0)
|
|
827
|
+
.default(false)
|
|
828
|
+
.description('Delete messages even if not in Trash')
|
|
829
|
+
.label('ForceDelete')
|
|
830
|
+
}).label('MessagesDeleteQuery'),
|
|
831
|
+
|
|
832
|
+
payload: Joi.object({
|
|
833
|
+
search: searchSchema
|
|
834
|
+
}).label('MessagesDeleteRequest')
|
|
835
|
+
},
|
|
836
|
+
|
|
837
|
+
response: {
|
|
838
|
+
schema: Joi.object({
|
|
839
|
+
deleted: Joi.boolean().example(false).description('Was the delete action executed'),
|
|
840
|
+
moved: Joi.object({
|
|
841
|
+
destination: Joi.string().required().example('Trash').description('Trash folder path').label('TrashPath'),
|
|
842
|
+
|
|
843
|
+
idMap: Joi.array()
|
|
844
|
+
.items(Joi.array().length(2).items(Joi.string().max(256).required().description('Message ID')).label('IdMapTuple'))
|
|
845
|
+
.example([['AAAAAQAACnA', 'AAAAAwAAAD4']])
|
|
846
|
+
.description('An optional map of source and target ID values, if the server provided this info')
|
|
847
|
+
.label('IdMapArray'),
|
|
848
|
+
|
|
849
|
+
emailIds: Joi.array()
|
|
850
|
+
.items(Joi.string().example('1278455344230334865'))
|
|
851
|
+
.description('An optional list of emailId values, if the server supports unique email IDs')
|
|
852
|
+
.label('EmailIdsArray')
|
|
853
|
+
})
|
|
854
|
+
.label('MessagesMovedToTrash')
|
|
855
|
+
.description('Value is present if messages were moved to Trash')
|
|
856
|
+
}).label('MessagesDeleteResponse'),
|
|
857
|
+
failAction: 'log'
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
// GET /v1/account/{account}/messages - List messages in a folder
|
|
863
|
+
server.route({
|
|
864
|
+
method: 'GET',
|
|
865
|
+
path: '/v1/account/{account}/messages',
|
|
866
|
+
|
|
867
|
+
async handler(request, h) {
|
|
868
|
+
let accountObject = new Account({
|
|
869
|
+
redis,
|
|
870
|
+
account: request.params.account,
|
|
871
|
+
call,
|
|
872
|
+
secret: await getSecret(),
|
|
873
|
+
esClient: await h.getESClient(request.logger),
|
|
874
|
+
timeout: request.headers['x-ee-timeout']
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
try {
|
|
878
|
+
return await accountObject.listMessages(request.query);
|
|
879
|
+
} catch (err) {
|
|
880
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
881
|
+
if (Boom.isBoom(err)) {
|
|
882
|
+
throw err;
|
|
883
|
+
}
|
|
884
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
885
|
+
if (err.code) {
|
|
886
|
+
error.output.payload.code = err.code;
|
|
887
|
+
}
|
|
888
|
+
throw error;
|
|
889
|
+
}
|
|
890
|
+
},
|
|
891
|
+
options: {
|
|
892
|
+
description: 'List messages in a folder',
|
|
893
|
+
notes: 'Lists messages in a mailbox folder',
|
|
894
|
+
tags: ['api', 'Message'],
|
|
895
|
+
|
|
896
|
+
auth: {
|
|
897
|
+
strategy: 'api-token',
|
|
898
|
+
mode: 'required'
|
|
899
|
+
},
|
|
900
|
+
cors: CORS_CONFIG,
|
|
901
|
+
|
|
902
|
+
validate: {
|
|
903
|
+
options: {
|
|
904
|
+
stripUnknown: false,
|
|
905
|
+
abortEarly: false,
|
|
906
|
+
convert: true
|
|
907
|
+
},
|
|
908
|
+
failAction,
|
|
909
|
+
|
|
910
|
+
params: Joi.object({
|
|
911
|
+
account: accountIdSchema.required().label('AccountId')
|
|
912
|
+
}),
|
|
913
|
+
|
|
914
|
+
query: Joi.object({
|
|
915
|
+
path: Joi.string().required().example('INBOX').description(listMessageFolderPathDescription).label('SpecialPath'),
|
|
916
|
+
|
|
917
|
+
cursor: Joi.string()
|
|
918
|
+
.trim()
|
|
919
|
+
.empty('')
|
|
920
|
+
.max(1024 * 1024)
|
|
921
|
+
.example('imap_kcQIji3UobDDTxc')
|
|
922
|
+
.description('Paging cursor from `nextPageCursor` or `prevPageCursor` value')
|
|
923
|
+
.label('PageCursor'),
|
|
924
|
+
page: Joi.number()
|
|
925
|
+
.integer()
|
|
926
|
+
.min(0)
|
|
927
|
+
.max(1024 * 1024)
|
|
928
|
+
.default(0)
|
|
929
|
+
.example(0)
|
|
930
|
+
.description(
|
|
931
|
+
'Page number (zero-indexed, so use 0 for the first page). Only supported for IMAP accounts. Deprecated; use the paging cursor instead. If the page cursor value is provided, then the page number value is ignored.'
|
|
932
|
+
)
|
|
933
|
+
.label('PageNumber'),
|
|
934
|
+
|
|
935
|
+
pageSize: Joi.number().integer().min(1).max(1000).default(20).example(20).description('How many entries per page').label('PageSize'),
|
|
936
|
+
documentStore: documentStoreSchema.default(false)
|
|
937
|
+
}).label('MessageQuery')
|
|
938
|
+
},
|
|
939
|
+
|
|
940
|
+
response: {
|
|
941
|
+
schema: messageListSchema,
|
|
942
|
+
failAction: 'log'
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
// POST /v1/account/{account}/search - Search for messages
|
|
948
|
+
server.route({
|
|
949
|
+
method: 'POST',
|
|
950
|
+
path: '/v1/account/{account}/search',
|
|
951
|
+
|
|
952
|
+
async handler(request, h) {
|
|
953
|
+
let accountObject = new Account({
|
|
954
|
+
redis,
|
|
955
|
+
account: request.params.account,
|
|
956
|
+
call,
|
|
957
|
+
secret: await getSecret(),
|
|
958
|
+
esClient: await h.getESClient(request.logger),
|
|
959
|
+
timeout: request.headers['x-ee-timeout']
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
let extraValidationErrors = [];
|
|
963
|
+
|
|
964
|
+
if (request.query.documentStore) {
|
|
965
|
+
for (let key of ['seq', 'modseq']) {
|
|
966
|
+
if (request.payload.search && key in request.payload.search) {
|
|
967
|
+
extraValidationErrors.push({ message: 'Not available when using Document Store', context: { key } });
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
} else {
|
|
971
|
+
for (let key of ['documentQuery']) {
|
|
972
|
+
if (key in request.payload) {
|
|
973
|
+
extraValidationErrors.push({ message: 'Requires Document Store to be enabled', context: { key } });
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
if (extraValidationErrors.length) {
|
|
979
|
+
let error = new Error('Input validation failed');
|
|
980
|
+
error.details = extraValidationErrors;
|
|
981
|
+
return failAction(request, h, error);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
try {
|
|
985
|
+
return await accountObject.searchMessages(Object.assign(request.query, request.payload));
|
|
986
|
+
} catch (err) {
|
|
987
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
988
|
+
if (Boom.isBoom(err)) {
|
|
989
|
+
throw err;
|
|
990
|
+
}
|
|
991
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
992
|
+
if (err.code) {
|
|
993
|
+
error.output.payload.code = err.code;
|
|
994
|
+
}
|
|
995
|
+
throw error;
|
|
996
|
+
}
|
|
997
|
+
},
|
|
998
|
+
options: {
|
|
999
|
+
description: 'Search for messages',
|
|
1000
|
+
notes: 'Filter messages from a mailbox folder by search options. Search is performed against a specific folder and not for the entire account.',
|
|
1001
|
+
tags: ['api', 'Message'],
|
|
1002
|
+
|
|
1003
|
+
plugins: {},
|
|
1004
|
+
|
|
1005
|
+
auth: {
|
|
1006
|
+
strategy: 'api-token',
|
|
1007
|
+
mode: 'required'
|
|
1008
|
+
},
|
|
1009
|
+
cors: CORS_CONFIG,
|
|
1010
|
+
|
|
1011
|
+
validate: {
|
|
1012
|
+
options: {
|
|
1013
|
+
stripUnknown: false,
|
|
1014
|
+
abortEarly: false,
|
|
1015
|
+
convert: true
|
|
1016
|
+
},
|
|
1017
|
+
failAction,
|
|
1018
|
+
|
|
1019
|
+
params: Joi.object({
|
|
1020
|
+
account: accountIdSchema.required()
|
|
1021
|
+
}),
|
|
1022
|
+
|
|
1023
|
+
query: Joi.object({
|
|
1024
|
+
path: Joi.string()
|
|
1025
|
+
.when('documentStore', {
|
|
1026
|
+
is: true,
|
|
1027
|
+
then: Joi.optional(),
|
|
1028
|
+
otherwise: Joi.required()
|
|
1029
|
+
})
|
|
1030
|
+
.example('INBOX')
|
|
1031
|
+
.description(listMessageFolderPathDescription)
|
|
1032
|
+
.label('Path'),
|
|
1033
|
+
|
|
1034
|
+
cursor: Joi.string()
|
|
1035
|
+
.trim()
|
|
1036
|
+
.empty('')
|
|
1037
|
+
.max(1024 * 1024)
|
|
1038
|
+
.example('imap_kcQIji3UobDDTxc')
|
|
1039
|
+
.description('Paging cursor from `nextPageCursor` or `prevPageCursor` value')
|
|
1040
|
+
.label('PageCursor'),
|
|
1041
|
+
page: Joi.number()
|
|
1042
|
+
.integer()
|
|
1043
|
+
.min(0)
|
|
1044
|
+
.max(1024 * 1024)
|
|
1045
|
+
.default(0)
|
|
1046
|
+
.example(0)
|
|
1047
|
+
.description(
|
|
1048
|
+
'Page number (zero-indexed, so use 0 for the first page). Only supported for IMAP accounts. Deprecated; use the paging cursor instead. If the page cursor value is provided, then the page number value is ignored.'
|
|
1049
|
+
)
|
|
1050
|
+
.label('PageNumber'),
|
|
1051
|
+
|
|
1052
|
+
pageSize: Joi.number().integer().min(1).max(1000).default(20).example(20).description('How many entries per page'),
|
|
1053
|
+
|
|
1054
|
+
useOutlookSearch: Joi.boolean()
|
|
1055
|
+
.truthy('Y', 'true', '1')
|
|
1056
|
+
.falsy('N', 'false', 0)
|
|
1057
|
+
.description(
|
|
1058
|
+
'MS Graph only. If enabled, uses the $search parameter for MS Graph search queries instead of $filter. This allows searching the "to", "cc", "bcc", "larger", "smaller", "body", "before", "sentBefore", "since", and the "sentSince" fields. Note that $search returns up to 1,000 results, does not indicate the total number of matching results or pages, and returns results sorted by relevance rather than date.'
|
|
1059
|
+
)
|
|
1060
|
+
.label('useOutlookSearch')
|
|
1061
|
+
.optional(),
|
|
1062
|
+
|
|
1063
|
+
documentStore: documentStoreSchema.default(false).meta({ swaggerHidden: true }),
|
|
1064
|
+
exposeQuery: Joi.boolean()
|
|
1065
|
+
.truthy('Y', 'true', '1')
|
|
1066
|
+
.falsy('N', 'false', 0)
|
|
1067
|
+
.description('If enabled then returns the ElasticSearch query for debugging as part of the response')
|
|
1068
|
+
.label('exposeQuery')
|
|
1069
|
+
.when('documentStore', {
|
|
1070
|
+
is: true,
|
|
1071
|
+
then: Joi.optional(),
|
|
1072
|
+
otherwise: Joi.forbidden()
|
|
1073
|
+
})
|
|
1074
|
+
.meta({ swaggerHidden: true })
|
|
1075
|
+
}),
|
|
1076
|
+
|
|
1077
|
+
payload: Joi.object({
|
|
1078
|
+
search: searchSchema,
|
|
1079
|
+
documentQuery: Joi.object()
|
|
1080
|
+
.min(1)
|
|
1081
|
+
.description('Document Store query. Only allowed with `documentStore`.')
|
|
1082
|
+
.label('DocumentQuery')
|
|
1083
|
+
.unknown()
|
|
1084
|
+
.meta({ swaggerHidden: true })
|
|
1085
|
+
})
|
|
1086
|
+
.label('SearchQuery')
|
|
1087
|
+
.example({
|
|
1088
|
+
search: {
|
|
1089
|
+
unseen: true,
|
|
1090
|
+
flagged: true,
|
|
1091
|
+
from: 'nyan.cat@example.com',
|
|
1092
|
+
body: 'Hello world',
|
|
1093
|
+
subject: 'Hello world',
|
|
1094
|
+
sentBefore: '2024-08-09',
|
|
1095
|
+
sentSince: '2022-08-09',
|
|
1096
|
+
emailId: '1278455344230334865',
|
|
1097
|
+
threadId: '1266894439832287888',
|
|
1098
|
+
header: {
|
|
1099
|
+
'Message-ID': '<12345@example.com>'
|
|
1100
|
+
},
|
|
1101
|
+
gmailRaw: 'has:attachment in:unread'
|
|
1102
|
+
}
|
|
1103
|
+
})
|
|
1104
|
+
},
|
|
1105
|
+
|
|
1106
|
+
response: {
|
|
1107
|
+
schema: messageListSchema,
|
|
1108
|
+
failAction: 'log'
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
// POST /v1/unified/search - Unified search for messages
|
|
1114
|
+
server.route({
|
|
1115
|
+
method: 'POST',
|
|
1116
|
+
path: '/v1/unified/search',
|
|
1117
|
+
|
|
1118
|
+
async handler(request, h) {
|
|
1119
|
+
let accountObject = new Account({
|
|
1120
|
+
redis,
|
|
1121
|
+
call,
|
|
1122
|
+
secret: await getSecret(),
|
|
1123
|
+
esClient: await h.getESClient(request.logger),
|
|
1124
|
+
timeout: request.headers['x-ee-timeout']
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
let extraValidationErrors = [];
|
|
1128
|
+
|
|
1129
|
+
for (let key of ['seq', 'modseq']) {
|
|
1130
|
+
if (request.payload.search && key in request.payload.search) {
|
|
1131
|
+
extraValidationErrors.push({ message: 'Not available when using Document Store', context: { key } });
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
if (extraValidationErrors.length) {
|
|
1136
|
+
let error = new Error('Input validation failed');
|
|
1137
|
+
error.details = extraValidationErrors;
|
|
1138
|
+
return failAction(request, h, error);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
let documentStoreEnabled = await settings.get('documentStoreEnabled');
|
|
1142
|
+
if (!documentStoreEnabled) {
|
|
1143
|
+
let error = new Error('Document store not enabled');
|
|
1144
|
+
error.details = extraValidationErrors;
|
|
1145
|
+
return failAction(request, h, error);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
try {
|
|
1149
|
+
return await accountObject.searchMessages(Object.assign({ documentStore: true }, request.query, request.payload), { unified: true });
|
|
1150
|
+
} catch (err) {
|
|
1151
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
1152
|
+
if (Boom.isBoom(err)) {
|
|
1153
|
+
throw err;
|
|
1154
|
+
}
|
|
1155
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
1156
|
+
if (err.code) {
|
|
1157
|
+
error.output.payload.code = err.code;
|
|
1158
|
+
}
|
|
1159
|
+
throw error;
|
|
1160
|
+
}
|
|
1161
|
+
},
|
|
1162
|
+
options: {
|
|
1163
|
+
description: 'Unified search for messages',
|
|
1164
|
+
notes: 'Filter messages from the Document Store for multiple accounts or paths. Document Store must be enabled for the unified search to work.',
|
|
1165
|
+
tags: ['Deprecated endpoints (Document Store)'],
|
|
1166
|
+
|
|
1167
|
+
plugins: {},
|
|
1168
|
+
|
|
1169
|
+
auth: {
|
|
1170
|
+
strategy: 'api-token',
|
|
1171
|
+
mode: 'required'
|
|
1172
|
+
},
|
|
1173
|
+
cors: CORS_CONFIG,
|
|
1174
|
+
|
|
1175
|
+
validate: {
|
|
1176
|
+
options: {
|
|
1177
|
+
stripUnknown: false,
|
|
1178
|
+
abortEarly: false,
|
|
1179
|
+
convert: true
|
|
1180
|
+
},
|
|
1181
|
+
failAction,
|
|
1182
|
+
|
|
1183
|
+
query: Joi.object({
|
|
1184
|
+
page: Joi.number()
|
|
1185
|
+
.integer()
|
|
1186
|
+
.min(0)
|
|
1187
|
+
.max(1024 * 1024)
|
|
1188
|
+
.default(0)
|
|
1189
|
+
.example(0)
|
|
1190
|
+
.description('Page number (zero indexed, so use 0 for first page)'),
|
|
1191
|
+
pageSize: Joi.number().integer().min(1).max(1000).default(20).example(20).description('How many entries per page'),
|
|
1192
|
+
exposeQuery: Joi.boolean()
|
|
1193
|
+
.truthy('Y', 'true', '1')
|
|
1194
|
+
.falsy('N', 'false', 0)
|
|
1195
|
+
.description('If enabled then returns the ElasticSearch query for debugging as part of the response')
|
|
1196
|
+
.label('exposeQuery')
|
|
1197
|
+
.optional()
|
|
1198
|
+
.meta({ swaggerHidden: true })
|
|
1199
|
+
}),
|
|
1200
|
+
|
|
1201
|
+
payload: Joi.object({
|
|
1202
|
+
accounts: Joi.array()
|
|
1203
|
+
.items(Joi.string().empty('').trim().max(256).example('example'))
|
|
1204
|
+
.single()
|
|
1205
|
+
.description('Optional list of account ID values')
|
|
1206
|
+
.label('UnifiedSearchAccounts'),
|
|
1207
|
+
paths: Joi.array()
|
|
1208
|
+
.items(Joi.string().optional().example('INBOX'))
|
|
1209
|
+
.single()
|
|
1210
|
+
.description('Optional list of mailbox folder paths or specialUse flags')
|
|
1211
|
+
.label('UnifiedSearchPaths'),
|
|
1212
|
+
search: searchSchema,
|
|
1213
|
+
documentQuery: Joi.object().min(1).description('Document Store query').label('DocumentQuery').unknown().meta({ swaggerHidden: true })
|
|
1214
|
+
}).label('UnifiedSearchQuery')
|
|
1215
|
+
},
|
|
1216
|
+
|
|
1217
|
+
response: {
|
|
1218
|
+
schema: messageListSchema,
|
|
1219
|
+
failAction: 'log'
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
// GET /v1/account/{account}/text/{text} - Retrieve message text
|
|
1225
|
+
server.route({
|
|
1226
|
+
method: 'GET',
|
|
1227
|
+
path: '/v1/account/{account}/text/{text}',
|
|
1228
|
+
|
|
1229
|
+
async handler(request, h) {
|
|
1230
|
+
let accountObject = new Account({
|
|
1231
|
+
redis,
|
|
1232
|
+
account: request.params.account,
|
|
1233
|
+
call,
|
|
1234
|
+
secret: await getSecret(),
|
|
1235
|
+
esClient: await h.getESClient(request.logger),
|
|
1236
|
+
timeout: request.headers['x-ee-timeout']
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
try {
|
|
1240
|
+
return await accountObject.getText(request.params.text, request.query);
|
|
1241
|
+
} catch (err) {
|
|
1242
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
1243
|
+
if (Boom.isBoom(err)) {
|
|
1244
|
+
throw err;
|
|
1245
|
+
}
|
|
1246
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
1247
|
+
if (err.code) {
|
|
1248
|
+
error.output.payload.code = err.code;
|
|
1249
|
+
}
|
|
1250
|
+
throw error;
|
|
1251
|
+
}
|
|
1252
|
+
},
|
|
1253
|
+
options: {
|
|
1254
|
+
description: 'Retrieve message text',
|
|
1255
|
+
notes: 'Retrieves message text',
|
|
1256
|
+
tags: ['api', 'Message'],
|
|
1257
|
+
|
|
1258
|
+
auth: {
|
|
1259
|
+
strategy: 'api-token',
|
|
1260
|
+
mode: 'required'
|
|
1261
|
+
},
|
|
1262
|
+
cors: CORS_CONFIG,
|
|
1263
|
+
|
|
1264
|
+
validate: {
|
|
1265
|
+
options: {
|
|
1266
|
+
stripUnknown: false,
|
|
1267
|
+
abortEarly: false,
|
|
1268
|
+
convert: true
|
|
1269
|
+
},
|
|
1270
|
+
failAction,
|
|
1271
|
+
|
|
1272
|
+
query: Joi.object({
|
|
1273
|
+
maxBytes: Joi.number()
|
|
1274
|
+
.integer()
|
|
1275
|
+
.min(0)
|
|
1276
|
+
.max(1024 * 1024 * 1024)
|
|
1277
|
+
.example(MAX_ATTACHMENT_SIZE)
|
|
1278
|
+
.description('Max length of text content'),
|
|
1279
|
+
textType: Joi.string()
|
|
1280
|
+
.lowercase()
|
|
1281
|
+
.valid('html', 'plain', '*')
|
|
1282
|
+
.default('*')
|
|
1283
|
+
.example('*')
|
|
1284
|
+
.description('Which text content to return, use * for all. By default all contents are returned.'),
|
|
1285
|
+
documentStore: documentStoreSchema.default(false)
|
|
1286
|
+
}),
|
|
1287
|
+
|
|
1288
|
+
params: Joi.object({
|
|
1289
|
+
account: accountIdSchema.required(),
|
|
1290
|
+
text: Joi.string()
|
|
1291
|
+
.base64({ paddingRequired: false, urlSafe: true })
|
|
1292
|
+
.max(10 * 1024)
|
|
1293
|
+
.required()
|
|
1294
|
+
.example('AAAAAQAACnAcdfaaN')
|
|
1295
|
+
.description('Message text ID')
|
|
1296
|
+
}).label('Text')
|
|
1297
|
+
},
|
|
1298
|
+
|
|
1299
|
+
response: {
|
|
1300
|
+
schema: Joi.object({
|
|
1301
|
+
plain: Joi.string().example('Hello world').description('Plaintext content'),
|
|
1302
|
+
html: Joi.string().example('<p>Hello world</p>').description('HTML content'),
|
|
1303
|
+
hasMore: Joi.boolean().example(false).description('Is the current text output capped or not')
|
|
1304
|
+
}).label('TextResponse'),
|
|
1305
|
+
failAction: 'log'
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
// GET /v1/account/{account}/attachment/{attachment} - Download attachment
|
|
1311
|
+
server.route({
|
|
1312
|
+
method: 'GET',
|
|
1313
|
+
path: '/v1/account/{account}/attachment/{attachment}',
|
|
1314
|
+
|
|
1315
|
+
async handler(request) {
|
|
1316
|
+
let accountObject = new Account({
|
|
1317
|
+
redis,
|
|
1318
|
+
account: request.params.account,
|
|
1319
|
+
call,
|
|
1320
|
+
secret: await getSecret(),
|
|
1321
|
+
timeout: request.headers['x-ee-timeout']
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
try {
|
|
1325
|
+
return await accountObject.getAttachment(request.params.attachment);
|
|
1326
|
+
} catch (err) {
|
|
1327
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
1328
|
+
if (Boom.isBoom(err)) {
|
|
1329
|
+
throw err;
|
|
1330
|
+
}
|
|
1331
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
1332
|
+
if (err.code) {
|
|
1333
|
+
error.output.payload.code = err.code;
|
|
1334
|
+
}
|
|
1335
|
+
throw error;
|
|
1336
|
+
}
|
|
1337
|
+
},
|
|
1338
|
+
options: {
|
|
1339
|
+
description: 'Download attachment',
|
|
1340
|
+
notes: 'Fetches attachment file as a binary stream',
|
|
1341
|
+
tags: ['api', 'Message'],
|
|
1342
|
+
|
|
1343
|
+
auth: {
|
|
1344
|
+
strategy: 'api-token',
|
|
1345
|
+
mode: 'required'
|
|
1346
|
+
},
|
|
1347
|
+
cors: CORS_CONFIG,
|
|
1348
|
+
|
|
1349
|
+
plugins: {
|
|
1350
|
+
'hapi-swagger': {
|
|
1351
|
+
produces: ['application/octet-stream']
|
|
1352
|
+
}
|
|
1353
|
+
},
|
|
1354
|
+
|
|
1355
|
+
validate: {
|
|
1356
|
+
options: {
|
|
1357
|
+
stripUnknown: false,
|
|
1358
|
+
abortEarly: false,
|
|
1359
|
+
convert: true
|
|
1360
|
+
},
|
|
1361
|
+
failAction,
|
|
1362
|
+
|
|
1363
|
+
params: Joi.object({
|
|
1364
|
+
account: accountIdSchema.required(),
|
|
1365
|
+
attachment: Joi.string()
|
|
1366
|
+
.base64({ paddingRequired: false, urlSafe: true })
|
|
1367
|
+
.max(2 * 1024)
|
|
1368
|
+
.required()
|
|
1369
|
+
.example('AAAAAQAACnAcde')
|
|
1370
|
+
.description('Attachment ID')
|
|
1371
|
+
})
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
module.exports = init;
|