fluxmail 0.8.1 → 0.9.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.
- package/dist/accounts/googleAuth.d.ts +5 -1
- package/dist/accounts/googleAuth.d.ts.map +1 -1
- package/dist/accounts/googleAuth.js +5 -1
- package/dist/accounts/googleAuth.js.map +1 -1
- package/dist/accounts/registry.d.ts +1 -0
- package/dist/accounts/registry.d.ts.map +1 -1
- package/dist/accounts/registry.js +11 -0
- package/dist/accounts/registry.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +69 -0
- package/dist/cli.js.map +1 -1
- package/dist/cliMail.d.ts +1 -0
- package/dist/cliMail.d.ts.map +1 -1
- package/dist/cliMail.js +71 -2
- package/dist/cliMail.js.map +1 -1
- package/dist/http/rest.d.ts.map +1 -1
- package/dist/http/rest.js +241 -12
- package/dist/http/rest.js.map +1 -1
- package/dist/mcp/buildServer.d.ts +1 -0
- package/dist/mcp/buildServer.d.ts.map +1 -1
- package/dist/mcp/buildServer.js +73 -3
- package/dist/mcp/buildServer.js.map +1 -1
- package/dist/permissions.js +1 -1
- package/dist/permissions.js.map +1 -1
- package/dist/scheduler/sendScheduler.js +1 -1
- package/dist/scheduler/sendScheduler.js.map +1 -1
- package/dist/service/emailService.d.ts +35 -2
- package/dist/service/emailService.d.ts.map +1 -1
- package/dist/service/emailService.js +270 -35
- package/dist/service/emailService.js.map +1 -1
- package/dist/service/searchCursor.d.ts +2 -0
- package/dist/service/searchCursor.d.ts.map +1 -1
- package/dist/service/searchCursor.js +4 -1
- package/dist/service/searchCursor.js.map +1 -1
- package/dist/storage/db.d.ts +66 -2
- package/dist/storage/db.d.ts.map +1 -1
- package/dist/storage/db.js +25 -1
- package/dist/storage/db.js.map +1 -1
- package/dist/storage/sendAs.d.ts +9 -0
- package/dist/storage/sendAs.d.ts.map +1 -0
- package/dist/storage/sendAs.js +26 -0
- package/dist/storage/sendAs.js.map +1 -0
- package/package.json +5 -5
package/dist/http/rest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest.d.ts","sourceRoot":"","sources":["../../src/http/rest.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,WAAW,EAAK,MAAM,mBAAmB,CAAC;AAGhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"rest.d.ts","sourceRoot":"","sources":["../../src/http/rest.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,WAAW,EAAK,MAAM,mBAAmB,CAAC;AAGhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAYtD,OAAO,KAAK,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAa,MAAM,4BAA4B,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAA2C,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAIrF,OAAO,EAA2C,KAAK,SAAS,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAMpH,OAAO,EAA+B,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAEzE,UAAU,aAAa;IACrB,QAAQ,EAAE,SAAS,CAAC;IACpB,WAAW,EAAE,YAAY,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,mBAAmB,CAAC;CACrC;AAED,KAAK,OAAO,GAAG;IAAE,QAAQ,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,aAAa,CAAA;CAAE,CAAC;AAGpE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,cAAc,CAAC;IACvB,aAAa,EAAE,oBAAoB,CAAC;IACpC,EAAE,EAAE,UAAU,CAAC;IACf,OAAO,EAAE,YAAY,CAAC;IACtB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC;AAkSD,eAAO,MAAM,qBAAqB;;;;;;;CAOjC,CAAC;AAgkBF,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,CA43BrE"}
|
package/dist/http/rest.js
CHANGED
|
@@ -18,7 +18,7 @@ const messageId = id.openapi({ example: 'msg_123' });
|
|
|
18
18
|
const threadId = id.openapi({ example: 'thread_123' });
|
|
19
19
|
const draftId = id.openapi({ example: 'draft_123' });
|
|
20
20
|
const scheduleId = id.openapi({ example: 'schedule_123' });
|
|
21
|
-
const attachmentId = id.openapi({ example: '
|
|
21
|
+
const attachmentId = id.describe('Opaque attachment ID returned by message metadata').openapi({ example: 'part:1.2' });
|
|
22
22
|
const isoDate = z
|
|
23
23
|
.string()
|
|
24
24
|
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Expected an ISO date in YYYY-MM-DD format')
|
|
@@ -91,6 +91,24 @@ const LabelSchema = z
|
|
|
91
91
|
})
|
|
92
92
|
.strict()
|
|
93
93
|
.openapi('Label');
|
|
94
|
+
const SendAsIdentitySchema = z
|
|
95
|
+
.object({
|
|
96
|
+
email: z.string().email(),
|
|
97
|
+
name: z.string().optional(),
|
|
98
|
+
replyTo: z.string().email().optional(),
|
|
99
|
+
isPrimary: z.boolean(),
|
|
100
|
+
source: z.enum(['provider', 'configured']),
|
|
101
|
+
})
|
|
102
|
+
.strict()
|
|
103
|
+
.openapi('SendAsIdentity');
|
|
104
|
+
const ConfiguredSendAsSchema = z
|
|
105
|
+
.object({ email: z.string().email(), name: z.string().optional() })
|
|
106
|
+
.strict()
|
|
107
|
+
.openapi('ConfiguredSendAs');
|
|
108
|
+
const ReplaceSendAsRequestSchema = z
|
|
109
|
+
.object({ identities: z.array(ConfiguredSendAsSchema) })
|
|
110
|
+
.strict()
|
|
111
|
+
.openapi('ReplaceSendAsRequest');
|
|
94
112
|
const MessageSchema = z
|
|
95
113
|
.object({
|
|
96
114
|
id: z.string(),
|
|
@@ -214,23 +232,49 @@ const ErrorSchema = z
|
|
|
214
232
|
function dataEnvelope(schema) {
|
|
215
233
|
return z.object({ data: schema, warnings: z.array(z.string()).optional() }).strict();
|
|
216
234
|
}
|
|
235
|
+
const SearchPageMetaSchema = z
|
|
236
|
+
.object({
|
|
237
|
+
nextPageToken: z.string().optional(),
|
|
238
|
+
exhausted: z.boolean(),
|
|
239
|
+
diagnostics: z.array(SearchDiagnosticSchema).optional(),
|
|
240
|
+
incomplete: z.literal(true).optional(),
|
|
241
|
+
incompleteReason: z.enum(['scan_limit', 'provider_limit', 'time_limit']).optional(),
|
|
242
|
+
inspectedCandidates: z.number().int().nonnegative().optional(),
|
|
243
|
+
})
|
|
244
|
+
.strict()
|
|
245
|
+
.openapi('SearchPageMeta');
|
|
217
246
|
function pagedEnvelope(schema) {
|
|
218
247
|
return z
|
|
219
248
|
.object({
|
|
220
249
|
data: z.array(schema),
|
|
221
|
-
meta:
|
|
222
|
-
.object({
|
|
223
|
-
nextPageToken: z.string().optional(),
|
|
224
|
-
diagnostics: z.array(SearchDiagnosticSchema).optional(),
|
|
225
|
-
incomplete: z.literal(true).optional(),
|
|
226
|
-
incompleteReason: z.enum(['scan_limit', 'provider_limit']).optional(),
|
|
227
|
-
inspectedCandidates: z.number().int().nonnegative().optional(),
|
|
228
|
-
})
|
|
229
|
-
.strict(),
|
|
250
|
+
meta: SearchPageMetaSchema,
|
|
230
251
|
warnings: z.array(z.string()).optional(),
|
|
231
252
|
})
|
|
232
253
|
.strict();
|
|
233
254
|
}
|
|
255
|
+
const BatchSearchResponseSchema = z
|
|
256
|
+
.object({
|
|
257
|
+
data: z.array(z.union([
|
|
258
|
+
z.object({ accountId: z.string(), data: z.array(MessageSchema), meta: SearchPageMetaSchema }).strict(),
|
|
259
|
+
z
|
|
260
|
+
.object({
|
|
261
|
+
accountId: z.string(),
|
|
262
|
+
error: z
|
|
263
|
+
.object({
|
|
264
|
+
code: z.string(),
|
|
265
|
+
message: z.string(),
|
|
266
|
+
data: z.record(z.unknown()).optional(),
|
|
267
|
+
exhausted: z.literal(false),
|
|
268
|
+
})
|
|
269
|
+
.strict(),
|
|
270
|
+
})
|
|
271
|
+
.strict(),
|
|
272
|
+
])),
|
|
273
|
+
meta: z.object({ exhausted: z.boolean() }).strict(),
|
|
274
|
+
warnings: z.array(z.string()).optional(),
|
|
275
|
+
})
|
|
276
|
+
.strict()
|
|
277
|
+
.openapi('BatchSearchResponse');
|
|
234
278
|
const errorResponses = {
|
|
235
279
|
400: { content: { 'application/json': { schema: ErrorSchema } }, description: 'Invalid request' },
|
|
236
280
|
401: { content: { 'application/json': { schema: ErrorSchema } }, description: 'Authentication required' },
|
|
@@ -262,6 +306,7 @@ const idempotencyHeaders = z.object({
|
|
|
262
306
|
.describe('A unique key for one intended delivery. Reuse it when retrying the same request.'),
|
|
263
307
|
});
|
|
264
308
|
const draftShape = {
|
|
309
|
+
from: z.string().email().optional(),
|
|
265
310
|
to: z.array(EmailAddressSchema).optional(),
|
|
266
311
|
cc: z.array(EmailAddressSchema).optional(),
|
|
267
312
|
bcc: z.array(EmailAddressSchema).optional(),
|
|
@@ -285,6 +330,7 @@ const SendDraftSchema = z.object({ draftId, sendAt: z.string().datetime({ offset
|
|
|
285
330
|
const SendRequestSchema = z.union([SendDraftSchema, SendContentSchema]).openapi('SendRequest');
|
|
286
331
|
const ForwardRequestSchema = z
|
|
287
332
|
.object({
|
|
333
|
+
from: z.string().email().optional(),
|
|
288
334
|
to: z.array(EmailAddressSchema).min(1),
|
|
289
335
|
cc: z.array(EmailAddressSchema).optional(),
|
|
290
336
|
comment: z.string().optional(),
|
|
@@ -360,8 +406,32 @@ const messageQuerySchema = z
|
|
|
360
406
|
.regex(/^(?:[1-9]|[1-9][0-9]|100)$/)
|
|
361
407
|
.optional(),
|
|
362
408
|
pageToken: z.string().min(1).optional(),
|
|
409
|
+
includeSnippet: z.enum(['true', 'false']).optional(),
|
|
363
410
|
})
|
|
364
411
|
.strict();
|
|
412
|
+
const portableFolder = z.enum(['inbox', 'sent', 'drafts', 'archive', 'spam', 'trash', 'all']);
|
|
413
|
+
const BatchSearchRequestSchema = z
|
|
414
|
+
.object({
|
|
415
|
+
accounts: z
|
|
416
|
+
.array(z.object({ accountId, pageToken: z.string().min(1).optional() }).strict())
|
|
417
|
+
.min(1)
|
|
418
|
+
.max(20),
|
|
419
|
+
query: z.string().min(1),
|
|
420
|
+
folder: portableFolder.optional(),
|
|
421
|
+
text: z.string().optional(),
|
|
422
|
+
from: z.string().optional(),
|
|
423
|
+
to: z.string().optional(),
|
|
424
|
+
subject: z.string().optional(),
|
|
425
|
+
read: z.boolean().optional(),
|
|
426
|
+
starred: z.boolean().optional(),
|
|
427
|
+
hasAttachment: z.boolean().optional(),
|
|
428
|
+
after: isoDate.optional(),
|
|
429
|
+
before: isoDate.optional(),
|
|
430
|
+
pageSize: z.number().int().min(1).max(100).optional(),
|
|
431
|
+
includeSnippet: z.boolean().optional(),
|
|
432
|
+
})
|
|
433
|
+
.strict()
|
|
434
|
+
.openapi('BatchSearchRequest');
|
|
365
435
|
const MODIFY_CAPABILITIES = {
|
|
366
436
|
markRead: 'mail.organize',
|
|
367
437
|
markUnread: 'mail.organize',
|
|
@@ -538,7 +608,9 @@ async function runJson(c, deps, options, fn) {
|
|
|
538
608
|
if (reservation) {
|
|
539
609
|
completeIdempotencyKey(deps.db, { ...reservation, responseStatus: status, responseBody: body });
|
|
540
610
|
}
|
|
541
|
-
outcome = 'success';
|
|
611
|
+
outcome = result.operationFailed ? 'error' : 'success';
|
|
612
|
+
if (result.operationFailed)
|
|
613
|
+
errorCode = 'account_failure';
|
|
542
614
|
return new Response(body, {
|
|
543
615
|
status,
|
|
544
616
|
headers: { 'content-type': 'application/json; charset=UTF-8', 'cache-control': 'no-store' },
|
|
@@ -643,6 +715,7 @@ async function runAttachment(c, deps, accountId, messageId, attachmentId) {
|
|
|
643
715
|
function toSendInput(input) {
|
|
644
716
|
return {
|
|
645
717
|
body: input.body,
|
|
718
|
+
...(input.from ? { from: input.from } : {}),
|
|
646
719
|
...(input.to ? { to: input.to } : {}),
|
|
647
720
|
...(input.cc ? { cc: input.cc } : {}),
|
|
648
721
|
...(input.bcc ? { bcc: input.bcc } : {}),
|
|
@@ -686,6 +759,7 @@ function toEmailQuery(input) {
|
|
|
686
759
|
page: {
|
|
687
760
|
...(input.pageSize ? { pageSize: Number(input.pageSize) } : {}),
|
|
688
761
|
...(input.pageToken ? { pageToken: input.pageToken } : {}),
|
|
762
|
+
...(input.includeSnippet !== undefined ? { includeSnippet: input.includeSnippet === 'true' } : {}),
|
|
689
763
|
},
|
|
690
764
|
};
|
|
691
765
|
}
|
|
@@ -832,6 +906,40 @@ export function createRestApi(deps) {
|
|
|
832
906
|
// Auditing must not replace the API response.
|
|
833
907
|
}
|
|
834
908
|
});
|
|
909
|
+
app.use('/api/v1/accounts/:accountId/send-as', async (c, next) => {
|
|
910
|
+
await next();
|
|
911
|
+
if (c.req.method !== 'PUT')
|
|
912
|
+
return;
|
|
913
|
+
const auth = c.get('restAuth');
|
|
914
|
+
if (!auth)
|
|
915
|
+
return;
|
|
916
|
+
let errorCode;
|
|
917
|
+
if (c.res.status >= 400) {
|
|
918
|
+
try {
|
|
919
|
+
const payload = (await c.res.clone().json());
|
|
920
|
+
if (typeof payload.error?.code === 'string')
|
|
921
|
+
errorCode = payload.error.code;
|
|
922
|
+
}
|
|
923
|
+
catch {
|
|
924
|
+
errorCode = 'internal';
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
try {
|
|
928
|
+
recordAdminAuditEvent(deps.db, {
|
|
929
|
+
operation: 'put /api/v1/accounts/:id/send-as',
|
|
930
|
+
outcome: c.res.status < 400 ? 'success' : 'error',
|
|
931
|
+
actorKeyId: auth.kind === 'api_key' ? auth.keyId : undefined,
|
|
932
|
+
actorSessionId: auth.kind === 'session' ? auth.sessionId : undefined,
|
|
933
|
+
actorMemberId: auth.memberId,
|
|
934
|
+
resourceType: 'account',
|
|
935
|
+
resourceId: c.req.param('accountId'),
|
|
936
|
+
...(errorCode ? { errorCode } : {}),
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
catch {
|
|
940
|
+
// Auditing must not replace the API response.
|
|
941
|
+
}
|
|
942
|
+
});
|
|
835
943
|
app.use('/api/v1/*', async (c, next) => {
|
|
836
944
|
c.set('restLogger', deps.logger);
|
|
837
945
|
if (c.req.path.startsWith('/api/v1/auth/') ||
|
|
@@ -859,7 +967,8 @@ export function createRestApi(deps) {
|
|
|
859
967
|
}
|
|
860
968
|
if (publicPaths.has(c.req.path))
|
|
861
969
|
return next();
|
|
862
|
-
const administrative = c.req.path.startsWith('/api/v1/admin/')
|
|
970
|
+
const administrative = c.req.path.startsWith('/api/v1/admin/') ||
|
|
971
|
+
(c.req.method === 'PUT' && /^\/api\/v1\/accounts\/[^/]+\/send-as$/.test(c.req.path));
|
|
863
972
|
if (administrative) {
|
|
864
973
|
c.header('cache-control', 'no-store');
|
|
865
974
|
c.header('x-content-type-options', 'nosniff');
|
|
@@ -1005,6 +1114,54 @@ export function createRestApi(deps) {
|
|
|
1005
1114
|
data: await c.get('restService').listLabels(accountId),
|
|
1006
1115
|
}));
|
|
1007
1116
|
});
|
|
1117
|
+
const listSendAsRoute = createRoute({
|
|
1118
|
+
method: 'get',
|
|
1119
|
+
path: '/api/v1/accounts/{accountId}/send-as',
|
|
1120
|
+
operationId: 'listSendAs',
|
|
1121
|
+
summary: 'List sender addresses',
|
|
1122
|
+
description: 'List sender addresses available for an email account.',
|
|
1123
|
+
request: { params: accountParams },
|
|
1124
|
+
...protectedRoute,
|
|
1125
|
+
responses: {
|
|
1126
|
+
200: {
|
|
1127
|
+
content: { 'application/json': { schema: dataEnvelope(z.array(SendAsIdentitySchema)) } },
|
|
1128
|
+
description: 'Sender addresses',
|
|
1129
|
+
},
|
|
1130
|
+
...errorResponses,
|
|
1131
|
+
},
|
|
1132
|
+
});
|
|
1133
|
+
app.openapi(listSendAsRoute, (c) => {
|
|
1134
|
+
const { accountId } = c.req.valid('param');
|
|
1135
|
+
return runJson(c, deps, { operation: 'listSendAs', capabilities: ['mail.read'] }, async () => ({
|
|
1136
|
+
data: await c.get('restService').listSendAs(accountId),
|
|
1137
|
+
}));
|
|
1138
|
+
});
|
|
1139
|
+
const replaceSendAsRoute = createRoute({
|
|
1140
|
+
method: 'put',
|
|
1141
|
+
path: '/api/v1/accounts/{accountId}/send-as',
|
|
1142
|
+
operationId: 'replaceSendAs',
|
|
1143
|
+
summary: 'Replace configured sender addresses',
|
|
1144
|
+
description: 'Replace the configured Outlook or IMAP aliases for an email account.',
|
|
1145
|
+
request: {
|
|
1146
|
+
params: accountParams,
|
|
1147
|
+
body: { required: true, content: { 'application/json': { schema: ReplaceSendAsRequestSchema } } },
|
|
1148
|
+
},
|
|
1149
|
+
...protectedRoute,
|
|
1150
|
+
responses: {
|
|
1151
|
+
200: {
|
|
1152
|
+
content: { 'application/json': { schema: dataEnvelope(z.array(SendAsIdentitySchema)) } },
|
|
1153
|
+
description: 'Sender addresses replaced',
|
|
1154
|
+
},
|
|
1155
|
+
...errorResponses,
|
|
1156
|
+
},
|
|
1157
|
+
});
|
|
1158
|
+
app.openapi(replaceSendAsRoute, (c) => {
|
|
1159
|
+
const { accountId } = c.req.valid('param');
|
|
1160
|
+
const { identities } = c.req.valid('json');
|
|
1161
|
+
return runJson(c, deps, { operation: 'replaceSendAs', capabilities: ['admin.accounts'], quota: false }, async () => ({
|
|
1162
|
+
data: await c.get('restService').replaceSendAs(accountId, identities),
|
|
1163
|
+
}));
|
|
1164
|
+
});
|
|
1008
1165
|
const listMessagesRoute = createRoute({
|
|
1009
1166
|
method: 'get',
|
|
1010
1167
|
path: '/api/v1/accounts/{accountId}/messages',
|
|
@@ -1027,6 +1184,7 @@ export function createRestApi(deps) {
|
|
|
1027
1184
|
data: result.items,
|
|
1028
1185
|
meta: {
|
|
1029
1186
|
...(result.nextPageToken ? { nextPageToken: result.nextPageToken } : {}),
|
|
1187
|
+
exhausted: result.exhausted,
|
|
1030
1188
|
...([...diagnostics, ...(result.diagnostics ?? [])].length
|
|
1031
1189
|
? { diagnostics: [...diagnostics, ...(result.diagnostics ?? [])] }
|
|
1032
1190
|
: {}),
|
|
@@ -1037,6 +1195,77 @@ export function createRestApi(deps) {
|
|
|
1037
1195
|
};
|
|
1038
1196
|
});
|
|
1039
1197
|
});
|
|
1198
|
+
const batchSearchRoute = createRoute({
|
|
1199
|
+
method: 'post',
|
|
1200
|
+
path: '/api/v1/messages/search',
|
|
1201
|
+
operationId: 'searchMessages',
|
|
1202
|
+
summary: 'Search multiple accounts',
|
|
1203
|
+
description: 'Search up to 20 email accounts with one portable query.',
|
|
1204
|
+
request: {
|
|
1205
|
+
body: { content: { 'application/json': { schema: BatchSearchRequestSchema } }, required: true },
|
|
1206
|
+
},
|
|
1207
|
+
...protectedRoute,
|
|
1208
|
+
responses: {
|
|
1209
|
+
200: {
|
|
1210
|
+
content: { 'application/json': { schema: BatchSearchResponseSchema } },
|
|
1211
|
+
description: 'Grouped search results',
|
|
1212
|
+
},
|
|
1213
|
+
...errorResponses,
|
|
1214
|
+
},
|
|
1215
|
+
});
|
|
1216
|
+
app.openapi(batchSearchRoute, (c) => runJson(c, deps, { operation: 'searchMessages', capabilities: ['mail.read'] }, async () => {
|
|
1217
|
+
const input = c.req.valid('json');
|
|
1218
|
+
const parsed = parseEmailSearch(input.query);
|
|
1219
|
+
if (!parsed.valid) {
|
|
1220
|
+
throw new EmailError('invalid_request', parsed.diagnostics.map((item) => item.message).join(' '), {
|
|
1221
|
+
diagnostics: parsed.diagnostics,
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
const structured = {};
|
|
1225
|
+
for (const key of ['folder', 'text', 'from', 'to', 'subject', 'after', 'before']) {
|
|
1226
|
+
if (input[key] !== undefined)
|
|
1227
|
+
structured[key] = input[key];
|
|
1228
|
+
}
|
|
1229
|
+
for (const key of ['read', 'starred', 'hasAttachment']) {
|
|
1230
|
+
if (input[key] !== undefined)
|
|
1231
|
+
structured[key] = input[key];
|
|
1232
|
+
}
|
|
1233
|
+
const merged = mergeEmailQueries(parsed.query, structured);
|
|
1234
|
+
if (!merged.success) {
|
|
1235
|
+
throw new EmailError('invalid_request', merged.diagnostics.map((item) => item.message).join(' '), {
|
|
1236
|
+
diagnostics: merged.diagnostics,
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
const result = await c.get('restService').searchMessagesBatch({
|
|
1240
|
+
accounts: input.accounts,
|
|
1241
|
+
query: merged.query,
|
|
1242
|
+
...(input.pageSize !== undefined ? { pageSize: input.pageSize } : {}),
|
|
1243
|
+
...(input.includeSnippet !== undefined ? { includeSnippet: input.includeSnippet } : {}),
|
|
1244
|
+
});
|
|
1245
|
+
return {
|
|
1246
|
+
data: result.groups.map((group) => {
|
|
1247
|
+
if (!group.page)
|
|
1248
|
+
return { accountId: group.accountId, error: group.error };
|
|
1249
|
+
const diagnostics = [...parsed.diagnostics, ...(group.page.diagnostics ?? [])];
|
|
1250
|
+
return {
|
|
1251
|
+
accountId: group.accountId,
|
|
1252
|
+
data: group.page.items,
|
|
1253
|
+
meta: {
|
|
1254
|
+
exhausted: group.page.exhausted,
|
|
1255
|
+
...(group.page.nextPageToken ? { nextPageToken: group.page.nextPageToken } : {}),
|
|
1256
|
+
...(diagnostics.length ? { diagnostics } : {}),
|
|
1257
|
+
...(group.page.incomplete ? { incomplete: true } : {}),
|
|
1258
|
+
...(group.page.incompleteReason ? { incompleteReason: group.page.incompleteReason } : {}),
|
|
1259
|
+
...(group.page.inspectedCandidates !== undefined
|
|
1260
|
+
? { inspectedCandidates: group.page.inspectedCandidates }
|
|
1261
|
+
: {}),
|
|
1262
|
+
},
|
|
1263
|
+
};
|
|
1264
|
+
}),
|
|
1265
|
+
meta: { exhausted: result.exhausted },
|
|
1266
|
+
operationFailed: result.groups.some((group) => group.error !== undefined),
|
|
1267
|
+
};
|
|
1268
|
+
}));
|
|
1040
1269
|
const getMessageRoute = createRoute({
|
|
1041
1270
|
method: 'get',
|
|
1042
1271
|
path: '/api/v1/accounts/{accountId}/messages/{messageId}',
|