@remit/drizzle-service 0.0.51 → 0.0.53
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/package.json
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AccountConfigDescription,
|
|
3
3
|
AccountConfigItem,
|
|
4
|
-
AccountItem,
|
|
5
|
-
AddressItem,
|
|
6
4
|
CreateAccountConfigInput,
|
|
7
5
|
IAccountConfigRepository,
|
|
8
6
|
ResultList,
|
|
@@ -18,6 +16,8 @@ import {
|
|
|
18
16
|
accountTable,
|
|
19
17
|
} from "../schema/i4-account-config.js";
|
|
20
18
|
import { addressTable } from "../schema/i4-address.js";
|
|
19
|
+
import { rowToAccount } from "./i4-account.js";
|
|
20
|
+
import { rowToAddress } from "./i4-address.js";
|
|
21
21
|
|
|
22
22
|
type DB = Db<Record<string, unknown>>;
|
|
23
23
|
|
|
@@ -36,62 +36,6 @@ function rowToAccountConfig(
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
function rowToAccount(row: typeof accountTable.$inferSelect): AccountItem {
|
|
40
|
-
return {
|
|
41
|
-
accountId: row.accountId,
|
|
42
|
-
accountConfigId: row.accountConfigId,
|
|
43
|
-
username: row.username,
|
|
44
|
-
email: row.email,
|
|
45
|
-
authType: row.authType as AccountItem["authType"],
|
|
46
|
-
passwordHash: row.passwordHash ?? undefined,
|
|
47
|
-
oauthRefreshTokenHash: row.oauthRefreshTokenHash ?? undefined,
|
|
48
|
-
oauthTokenUpdatedAt: row.oauthTokenUpdatedAt ?? undefined,
|
|
49
|
-
imapHost: row.imapHost,
|
|
50
|
-
imapPort: row.imapPort,
|
|
51
|
-
imapTls: row.imapTls,
|
|
52
|
-
imapStartTls: row.imapStartTls,
|
|
53
|
-
smtpEnabled: row.smtpEnabled,
|
|
54
|
-
smtpHost: row.smtpHost,
|
|
55
|
-
smtpPort: row.smtpPort,
|
|
56
|
-
smtpTls: row.smtpTls,
|
|
57
|
-
smtpStartTls: row.smtpStartTls,
|
|
58
|
-
smtpUsername: row.smtpUsername,
|
|
59
|
-
smtpPasswordHash: row.smtpPasswordHash ?? undefined,
|
|
60
|
-
isActive: row.isActive,
|
|
61
|
-
connectionState: row.connectionState as AccountItem["connectionState"],
|
|
62
|
-
lastConnectedAt: row.lastConnectedAt ?? undefined,
|
|
63
|
-
lastSyncAt: row.lastSyncAt ?? undefined,
|
|
64
|
-
lastError: row.lastError ?? undefined,
|
|
65
|
-
syncPhase: (row.syncPhase as AccountItem["syncPhase"]) ?? undefined,
|
|
66
|
-
mailboxCountTotal: row.mailboxCountTotal ?? undefined,
|
|
67
|
-
mailboxCountSynced: row.mailboxCountSynced ?? undefined,
|
|
68
|
-
createdAt: row.createdAt,
|
|
69
|
-
updatedAt: row.updatedAt,
|
|
70
|
-
deletedAt: row.deletedAt ?? undefined,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function rowToAddress(row: typeof addressTable.$inferSelect): AddressItem {
|
|
75
|
-
return {
|
|
76
|
-
addressId: row.addressId,
|
|
77
|
-
accountConfigId: row.accountConfigId,
|
|
78
|
-
displayName: row.displayName ?? undefined,
|
|
79
|
-
localPart: row.localPart,
|
|
80
|
-
domain: row.domain,
|
|
81
|
-
normalizedEmail: row.normalizedEmail,
|
|
82
|
-
normalizedCompound: row.normalizedCompound,
|
|
83
|
-
flags: (row.flags ?? {}) as AddressItem["flags"],
|
|
84
|
-
inboundCount: row.inboundCount,
|
|
85
|
-
outboundCount: row.outboundCount,
|
|
86
|
-
replyCount: row.replyCount,
|
|
87
|
-
lastInboundAt: row.lastInboundAt,
|
|
88
|
-
lastOutboundAt: row.lastOutboundAt ?? undefined,
|
|
89
|
-
lastReplyAt: row.lastReplyAt,
|
|
90
|
-
createdAt: row.createdAt,
|
|
91
|
-
updatedAt: row.updatedAt,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
39
|
export class AccountConfigRepo implements IAccountConfigRepository {
|
|
96
40
|
constructor(private db: DB) {}
|
|
97
41
|
|
package/src/repos/i4-address.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
UpdateAddressInput,
|
|
11
11
|
} from "@remit/data-ports";
|
|
12
12
|
import { BadRequestError } from "@remit/data-ports/errors";
|
|
13
|
+
import { shouldPromoteWellknown } from "@remit/data-ports/wellknown";
|
|
13
14
|
import {
|
|
14
15
|
and,
|
|
15
16
|
asc,
|
|
@@ -39,7 +40,6 @@ import {
|
|
|
39
40
|
addressRecency,
|
|
40
41
|
addressSearchMatch,
|
|
41
42
|
} from "./address-search-predicates.js";
|
|
42
|
-
import { shouldPromoteWellknown } from "./i4-address-wellknown.js";
|
|
43
43
|
|
|
44
44
|
type DB = Db<Record<string, unknown>>;
|
|
45
45
|
|
|
@@ -14,11 +14,15 @@ import { AccountSettingRepo } from "./i4-account-setting.js";
|
|
|
14
14
|
import { MailboxRepo } from "./i4-mailbox.js";
|
|
15
15
|
import { MailboxSpecialUseRepo } from "./i4-mailbox-special-use.js";
|
|
16
16
|
|
|
17
|
-
const makeMailboxInput = (
|
|
17
|
+
const makeMailboxInput = (
|
|
18
|
+
accountId: string,
|
|
19
|
+
fullPath: string,
|
|
20
|
+
hierarchyDelimiter = "/",
|
|
21
|
+
) => ({
|
|
18
22
|
accountId,
|
|
19
23
|
namespaceType: "personal" as const,
|
|
20
24
|
namespacePrefix: "",
|
|
21
|
-
hierarchyDelimiter
|
|
25
|
+
hierarchyDelimiter,
|
|
22
26
|
fullPath,
|
|
23
27
|
uidValidity: 1,
|
|
24
28
|
uidNext: 1,
|
|
@@ -210,6 +214,59 @@ describe("MailboxSpecialUseRepo role lookups (sqlite)", () => {
|
|
|
210
214
|
assert.equal(found?.mailboxId, spam.mailboxId);
|
|
211
215
|
});
|
|
212
216
|
|
|
217
|
+
test("resolves an INBOX-nested Trash folder that advertises no special use", async () => {
|
|
218
|
+
// #837: `findTrashMailbox` carried its own copy of the discarded
|
|
219
|
+
// whole-path rule, so `INBOX/Trash` resolved to nothing and a delete
|
|
220
|
+
// refused on an account that plainly has a Trash folder.
|
|
221
|
+
const { accountId } = await makeAccount();
|
|
222
|
+
await mailboxes.create(makeMailboxInput(accountId, "INBOX"));
|
|
223
|
+
const trash = await mailboxes.create(
|
|
224
|
+
makeMailboxInput(accountId, "INBOX/Trash"),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
assert.equal(
|
|
228
|
+
(await repo.findTrashMailbox(accountId))?.mailboxId,
|
|
229
|
+
trash.mailboxId,
|
|
230
|
+
);
|
|
231
|
+
// Resolving the name widens where a delete FILES mail, never what an
|
|
232
|
+
// Empty Trash may expunge (#846): that still needs the flag or an
|
|
233
|
+
// appointment.
|
|
234
|
+
assert.equal(await repo.findConfirmedTrashMailbox(accountId), null);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("resolves an INBOX-nested Archive folder that advertises no special use", async () => {
|
|
238
|
+
const { accountId } = await makeAccount();
|
|
239
|
+
await mailboxes.create(makeMailboxInput(accountId, "INBOX"));
|
|
240
|
+
const archive = await mailboxes.create(
|
|
241
|
+
makeMailboxInput(accountId, "INBOX/Archive"),
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
assert.equal(
|
|
245
|
+
(await repo.findArchiveMailbox(accountId))?.mailboxId,
|
|
246
|
+
archive.mailboxId,
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("splits the leaf on the account's own delimiter, not on a slash", async () => {
|
|
251
|
+
const { accountId } = await makeAccount();
|
|
252
|
+
await mailboxes.create(makeMailboxInput(accountId, "INBOX", "."));
|
|
253
|
+
const trash = await mailboxes.create(
|
|
254
|
+
makeMailboxInput(accountId, "INBOX.Trash", "."),
|
|
255
|
+
);
|
|
256
|
+
const archive = await mailboxes.create(
|
|
257
|
+
makeMailboxInput(accountId, "INBOX.Archive", "."),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
assert.equal(
|
|
261
|
+
(await repo.findTrashMailbox(accountId))?.mailboxId,
|
|
262
|
+
trash.mailboxId,
|
|
263
|
+
);
|
|
264
|
+
assert.equal(
|
|
265
|
+
(await repo.findArchiveMailbox(accountId))?.mailboxId,
|
|
266
|
+
archive.mailboxId,
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
|
|
213
270
|
test("answers null when the account has no Junk folder at all", async () => {
|
|
214
271
|
const { accountId } = await makeAccount();
|
|
215
272
|
await mailboxes.create(makeMailboxInput(accountId, "INBOX"));
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export const WELLKNOWN_INBOUND_THRESHOLD = 3;
|
|
2
|
-
export const WELLKNOWN_REPLY_THRESHOLD = 1;
|
|
3
|
-
export const WELLKNOWN_INBOUND_WINDOW_MS = 90 * 24 * 60 * 60 * 1000;
|
|
4
|
-
|
|
5
|
-
export interface WellknownEngagementSnapshot {
|
|
6
|
-
inboundCount?: number;
|
|
7
|
-
replyCount?: number;
|
|
8
|
-
lastInboundAt?: number;
|
|
9
|
-
isBulk?: boolean;
|
|
10
|
-
flags?: { wellknown?: { value: boolean } | undefined };
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const shouldPromoteWellknown = (
|
|
14
|
-
snapshot: WellknownEngagementSnapshot,
|
|
15
|
-
now: number,
|
|
16
|
-
): boolean => {
|
|
17
|
-
if (snapshot.flags?.wellknown?.value === true) return false;
|
|
18
|
-
|
|
19
|
-
const replyCount = snapshot.replyCount ?? 0;
|
|
20
|
-
const inboundCount = snapshot.inboundCount ?? 0;
|
|
21
|
-
const replied = replyCount >= WELLKNOWN_REPLY_THRESHOLD;
|
|
22
|
-
const inboundEnough =
|
|
23
|
-
!snapshot.isBulk && inboundCount >= WELLKNOWN_INBOUND_THRESHOLD;
|
|
24
|
-
if (!replied && !inboundEnough) return false;
|
|
25
|
-
|
|
26
|
-
const lastInboundAt = snapshot.lastInboundAt;
|
|
27
|
-
if (lastInboundAt === undefined) return false;
|
|
28
|
-
if (now - lastInboundAt > WELLKNOWN_INBOUND_WINDOW_MS) return false;
|
|
29
|
-
|
|
30
|
-
return true;
|
|
31
|
-
};
|