@happyvertical/smrt-messages 0.30.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/AGENTS.md +31 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +103 -0
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/collections/AccountCollection.d.ts +42 -0
- package/dist/collections/AccountCollection.d.ts.map +1 -0
- package/dist/collections/AttachmentCollection.d.ts +67 -0
- package/dist/collections/AttachmentCollection.d.ts.map +1 -0
- package/dist/collections/BlacklistCollection.d.ts +14 -0
- package/dist/collections/BlacklistCollection.d.ts.map +1 -0
- package/dist/collections/EmailAccountCollection.d.ts +74 -0
- package/dist/collections/EmailAccountCollection.d.ts.map +1 -0
- package/dist/collections/EmailAttachmentCollection.d.ts +38 -0
- package/dist/collections/EmailAttachmentCollection.d.ts.map +1 -0
- package/dist/collections/EmailCollection.d.ts +81 -0
- package/dist/collections/EmailCollection.d.ts.map +1 -0
- package/dist/collections/EmailFolderCollection.d.ts +85 -0
- package/dist/collections/EmailFolderCollection.d.ts.map +1 -0
- package/dist/collections/MessageCollection.d.ts +74 -0
- package/dist/collections/MessageCollection.d.ts.map +1 -0
- package/dist/collections/WhitelistCollection.d.ts +18 -0
- package/dist/collections/WhitelistCollection.d.ts.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3068 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.json +10576 -0
- package/dist/models/Account.d.ts +47 -0
- package/dist/models/Account.d.ts.map +1 -0
- package/dist/models/Attachment.d.ts +48 -0
- package/dist/models/Attachment.d.ts.map +1 -0
- package/dist/models/Blacklist.d.ts +21 -0
- package/dist/models/Blacklist.d.ts.map +1 -0
- package/dist/models/Email.d.ts +98 -0
- package/dist/models/Email.d.ts.map +1 -0
- package/dist/models/EmailAccount.d.ts +65 -0
- package/dist/models/EmailAccount.d.ts.map +1 -0
- package/dist/models/EmailAttachment.d.ts +19 -0
- package/dist/models/EmailAttachment.d.ts.map +1 -0
- package/dist/models/EmailFolder.d.ts +65 -0
- package/dist/models/EmailFolder.d.ts.map +1 -0
- package/dist/models/Message.d.ts +105 -0
- package/dist/models/Message.d.ts.map +1 -0
- package/dist/models/SlackAccount.d.ts +13 -0
- package/dist/models/SlackAccount.d.ts.map +1 -0
- package/dist/models/SlackMessage.d.ts +34 -0
- package/dist/models/SlackMessage.d.ts.map +1 -0
- package/dist/models/Tweet.d.ts +31 -0
- package/dist/models/Tweet.d.ts.map +1 -0
- package/dist/models/TwitterAccount.d.ts +12 -0
- package/dist/models/TwitterAccount.d.ts.map +1 -0
- package/dist/models/Whitelist.d.ts +21 -0
- package/dist/models/Whitelist.d.ts.map +1 -0
- package/dist/playground.d.ts +2 -0
- package/dist/playground.d.ts.map +1 -0
- package/dist/playground.js +176 -0
- package/dist/playground.js.map +1 -0
- package/dist/senders/EmailSender.d.ts +13 -0
- package/dist/senders/EmailSender.d.ts.map +1 -0
- package/dist/senders/SlackSender.d.ts +11 -0
- package/dist/senders/SlackSender.d.ts.map +1 -0
- package/dist/senders/TweetSender.d.ts +11 -0
- package/dist/senders/TweetSender.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +4234 -0
- package/dist/svelte/components/AccountAvatar.svelte +107 -0
- package/dist/svelte/components/AccountAvatar.svelte.d.ts +12 -0
- package/dist/svelte/components/AccountAvatar.svelte.d.ts.map +1 -0
- package/dist/svelte/components/AccountCard.svelte +173 -0
- package/dist/svelte/components/AccountCard.svelte.d.ts +12 -0
- package/dist/svelte/components/AccountCard.svelte.d.ts.map +1 -0
- package/dist/svelte/components/AccountList.svelte +90 -0
- package/dist/svelte/components/AccountList.svelte.d.ts +12 -0
- package/dist/svelte/components/AccountList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/AttachmentChip.svelte +99 -0
- package/dist/svelte/components/AttachmentChip.svelte.d.ts +12 -0
- package/dist/svelte/components/AttachmentChip.svelte.d.ts.map +1 -0
- package/dist/svelte/components/AttachmentUpload.svelte +160 -0
- package/dist/svelte/components/AttachmentUpload.svelte.d.ts +11 -0
- package/dist/svelte/components/AttachmentUpload.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ComposeForm.svelte +387 -0
- package/dist/svelte/components/ComposeForm.svelte.d.ts +13 -0
- package/dist/svelte/components/ComposeForm.svelte.d.ts.map +1 -0
- package/dist/svelte/components/EmailAccountManager.svelte +690 -0
- package/dist/svelte/components/EmailAccountManager.svelte.d.ts +15 -0
- package/dist/svelte/components/EmailAccountManager.svelte.d.ts.map +1 -0
- package/dist/svelte/components/EmailFilterManager.svelte +687 -0
- package/dist/svelte/components/EmailFilterManager.svelte.d.ts +14 -0
- package/dist/svelte/components/EmailFilterManager.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FolderNav.svelte +171 -0
- package/dist/svelte/components/FolderNav.svelte.d.ts +11 -0
- package/dist/svelte/components/FolderNav.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ForwardForm.svelte +166 -0
- package/dist/svelte/components/ForwardForm.svelte.d.ts +10 -0
- package/dist/svelte/components/ForwardForm.svelte.d.ts.map +1 -0
- package/dist/svelte/components/MessageCard.svelte +336 -0
- package/dist/svelte/components/MessageCard.svelte.d.ts +20 -0
- package/dist/svelte/components/MessageCard.svelte.d.ts.map +1 -0
- package/dist/svelte/components/MessageDetail.svelte +309 -0
- package/dist/svelte/components/MessageDetail.svelte.d.ts +18 -0
- package/dist/svelte/components/MessageDetail.svelte.d.ts.map +1 -0
- package/dist/svelte/components/MessageFilters.svelte +228 -0
- package/dist/svelte/components/MessageFilters.svelte.d.ts +13 -0
- package/dist/svelte/components/MessageFilters.svelte.d.ts.map +1 -0
- package/dist/svelte/components/MessageList.svelte +101 -0
- package/dist/svelte/components/MessageList.svelte.d.ts +23 -0
- package/dist/svelte/components/MessageList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/MessageStatusIndicator.svelte +82 -0
- package/dist/svelte/components/MessageStatusIndicator.svelte.d.ts +11 -0
- package/dist/svelte/components/MessageStatusIndicator.svelte.d.ts.map +1 -0
- package/dist/svelte/components/MessageToolbar.svelte +131 -0
- package/dist/svelte/components/MessageToolbar.svelte.d.ts +14 -0
- package/dist/svelte/components/MessageToolbar.svelte.d.ts.map +1 -0
- package/dist/svelte/components/MessageTypeBadge.svelte +59 -0
- package/dist/svelte/components/MessageTypeBadge.svelte.d.ts +9 -0
- package/dist/svelte/components/MessageTypeBadge.svelte.d.ts.map +1 -0
- package/dist/svelte/components/RecipientInput.svelte +150 -0
- package/dist/svelte/components/RecipientInput.svelte.d.ts +11 -0
- package/dist/svelte/components/RecipientInput.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ReplyForm.svelte +159 -0
- package/dist/svelte/components/ReplyForm.svelte.d.ts +11 -0
- package/dist/svelte/components/ReplyForm.svelte.d.ts.map +1 -0
- package/dist/svelte/components/SendStatusBadge.svelte +64 -0
- package/dist/svelte/components/SendStatusBadge.svelte.d.ts +8 -0
- package/dist/svelte/components/SendStatusBadge.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ThreadView.svelte +240 -0
- package/dist/svelte/components/ThreadView.svelte.d.ts +12 -0
- package/dist/svelte/components/ThreadView.svelte.d.ts.map +1 -0
- package/dist/svelte/i18n.d.ts +42 -0
- package/dist/svelte/i18n.d.ts.map +1 -0
- package/dist/svelte/i18n.js +60 -0
- package/dist/svelte/i18n.messages.d.ts +32 -0
- package/dist/svelte/i18n.messages.d.ts.map +1 -0
- package/dist/svelte/i18n.messages.js +46 -0
- package/dist/svelte/index.d.ts +54 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +44 -0
- package/dist/svelte/playground.d.ts +341 -0
- package/dist/svelte/playground.d.ts.map +1 -0
- package/dist/svelte/playground.js +171 -0
- package/dist/svelte/types.d.ts +195 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +6 -0
- package/dist/types.d.ts +316 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ui.d.ts +4 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +103 -0
- package/dist/ui.js.map +1 -0
- package/package.json +104 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3068 @@
|
|
|
1
|
+
import { ObjectRegistry, field, smrt, SmrtObject, SmrtCollection, foreignKey } from "@happyvertical/smrt-core";
|
|
2
|
+
import { tenantId, TenantScoped } from "@happyvertical/smrt-tenancy";
|
|
3
|
+
ObjectRegistry.registerPackageManifest(
|
|
4
|
+
new URL("./manifest.json", import.meta.url)
|
|
5
|
+
);
|
|
6
|
+
var __defProp$3 = Object.defineProperty;
|
|
7
|
+
var __getOwnPropDesc$b = Object.getOwnPropertyDescriptor;
|
|
8
|
+
var __decorateClass$b = (decorators, target, key, kind) => {
|
|
9
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$b(target, key) : target;
|
|
10
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
11
|
+
if (decorator = decorators[i])
|
|
12
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
13
|
+
if (kind && result) __defProp$3(target, key, result);
|
|
14
|
+
return result;
|
|
15
|
+
};
|
|
16
|
+
let Account = class extends SmrtObject {
|
|
17
|
+
tenantId = null;
|
|
18
|
+
name = "";
|
|
19
|
+
providerType = "";
|
|
20
|
+
// credentialSecretId stores the Secret's NAME (keyed by name + tenant
|
|
21
|
+
// context in smrt-secrets), NOT its primary-key id — see setCredentials()/
|
|
22
|
+
// getCredentials() which call secretService.store/retrieve by name. So it is
|
|
23
|
+
// deliberately NOT a @crossPackageRef id FK.
|
|
24
|
+
credentialSecretId = null;
|
|
25
|
+
isActive = true;
|
|
26
|
+
lastSyncAt = null;
|
|
27
|
+
settings = "";
|
|
28
|
+
// JSON
|
|
29
|
+
// Timestamps
|
|
30
|
+
createdAt = /* @__PURE__ */ new Date();
|
|
31
|
+
updatedAt = /* @__PURE__ */ new Date();
|
|
32
|
+
constructor(options = {}) {
|
|
33
|
+
super(options);
|
|
34
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
35
|
+
if (options.name !== void 0) this.name = options.name;
|
|
36
|
+
if (options.providerType !== void 0)
|
|
37
|
+
this.providerType = options.providerType;
|
|
38
|
+
if (options.credentialSecretId !== void 0)
|
|
39
|
+
this.credentialSecretId = options.credentialSecretId || null;
|
|
40
|
+
if (options.isActive !== void 0) this.isActive = options.isActive;
|
|
41
|
+
if (options.lastSyncAt !== void 0)
|
|
42
|
+
this.lastSyncAt = options.lastSyncAt || null;
|
|
43
|
+
if (options.settings !== void 0) this.settings = options.settings;
|
|
44
|
+
if (options.createdAt) this.createdAt = options.createdAt;
|
|
45
|
+
if (options.updatedAt) this.updatedAt = options.updatedAt;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get settings as parsed object
|
|
49
|
+
*/
|
|
50
|
+
getSettings() {
|
|
51
|
+
if (!this.settings) return {};
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(this.settings);
|
|
54
|
+
} catch {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Set settings from object
|
|
60
|
+
*/
|
|
61
|
+
setSettings(settings) {
|
|
62
|
+
this.settings = JSON.stringify(settings);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Activate account
|
|
66
|
+
*/
|
|
67
|
+
async activate() {
|
|
68
|
+
this.isActive = true;
|
|
69
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
70
|
+
await this.save();
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Deactivate account
|
|
74
|
+
*/
|
|
75
|
+
async deactivate() {
|
|
76
|
+
this.isActive = false;
|
|
77
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
78
|
+
await this.save();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Store credentials securely using smrt-secrets
|
|
82
|
+
*/
|
|
83
|
+
async setCredentials(credentials, options = {}) {
|
|
84
|
+
const { SecretService } = await import("@happyvertical/smrt-secrets");
|
|
85
|
+
const secretService = await SecretService.create({ db: this.db });
|
|
86
|
+
const secretName = `account-${this.id}`;
|
|
87
|
+
const secretValue = JSON.stringify(credentials);
|
|
88
|
+
if (this.credentialSecretId) {
|
|
89
|
+
await secretService.store(this.credentialSecretId, secretValue, {
|
|
90
|
+
description: options.description || `Credentials for ${this.name}`,
|
|
91
|
+
category: options.category || "messaging"
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
await secretService.store(secretName, secretValue, {
|
|
95
|
+
description: options.description || `Credentials for ${this.name}`,
|
|
96
|
+
category: options.category || "messaging"
|
|
97
|
+
});
|
|
98
|
+
this.credentialSecretId = secretName;
|
|
99
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
100
|
+
await this.save();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Create a sender for this account.
|
|
105
|
+
* Subclasses must override to return a concrete sender.
|
|
106
|
+
*/
|
|
107
|
+
async createSender() {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`createSender() not implemented for account type '${this.providerType}'`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Retrieve stored credentials
|
|
114
|
+
*/
|
|
115
|
+
async getCredentials() {
|
|
116
|
+
if (!this.credentialSecretId) {
|
|
117
|
+
return this.getSettings();
|
|
118
|
+
}
|
|
119
|
+
const { SecretService } = await import("@happyvertical/smrt-secrets");
|
|
120
|
+
const secretService = await SecretService.create({ db: this.db });
|
|
121
|
+
try {
|
|
122
|
+
const secret = await secretService.retrieve(this.credentialSecretId);
|
|
123
|
+
return JSON.parse(secret.value);
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
__decorateClass$b([
|
|
130
|
+
tenantId({ nullable: true })
|
|
131
|
+
], Account.prototype, "tenantId", 2);
|
|
132
|
+
__decorateClass$b([
|
|
133
|
+
field({ sensitive: true })
|
|
134
|
+
], Account.prototype, "settings", 2);
|
|
135
|
+
Account = __decorateClass$b([
|
|
136
|
+
TenantScoped({ mode: "optional" }),
|
|
137
|
+
smrt({
|
|
138
|
+
tableStrategy: "sti",
|
|
139
|
+
api: { include: ["list", "get", "create", "update", "delete"] },
|
|
140
|
+
mcp: { include: ["list", "get"] },
|
|
141
|
+
cli: true
|
|
142
|
+
})
|
|
143
|
+
], Account);
|
|
144
|
+
class AccountCollection extends SmrtCollection {
|
|
145
|
+
static _itemClass = Account;
|
|
146
|
+
/**
|
|
147
|
+
* Get active accounts
|
|
148
|
+
*/
|
|
149
|
+
async getActive() {
|
|
150
|
+
return await this.list({ where: { isActive: true } });
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get inactive accounts
|
|
154
|
+
*/
|
|
155
|
+
async getInactive() {
|
|
156
|
+
return await this.list({ where: { isActive: false } });
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get accounts by provider type
|
|
160
|
+
*/
|
|
161
|
+
async getByProviderType(providerType) {
|
|
162
|
+
return await this.list({ where: { providerType } });
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get accounts by STI type.
|
|
166
|
+
*
|
|
167
|
+
* Accepts either the full discriminator (e.g. "@happyvertical/smrt-messages:EmailAccount")
|
|
168
|
+
* or the short type name (e.g. "EmailAccount").
|
|
169
|
+
*/
|
|
170
|
+
async getByType(accountType) {
|
|
171
|
+
if (accountType.includes(":") || accountType.startsWith("@")) {
|
|
172
|
+
return await this.list({ where: { _meta_type: accountType } });
|
|
173
|
+
}
|
|
174
|
+
const allAccounts = await this.list({});
|
|
175
|
+
return allAccounts.filter((a) => {
|
|
176
|
+
const metaType = a._meta_type || "";
|
|
177
|
+
return metaType.endsWith(`:${accountType}`);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Search accounts with filters
|
|
182
|
+
*/
|
|
183
|
+
async search(query, filters) {
|
|
184
|
+
let accounts = await this.list({});
|
|
185
|
+
if (query) {
|
|
186
|
+
const lowerQuery = query.toLowerCase();
|
|
187
|
+
accounts = accounts.filter(
|
|
188
|
+
(a) => a.name?.toLowerCase().includes(lowerQuery)
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (filters) {
|
|
192
|
+
if (filters.providerType) {
|
|
193
|
+
accounts = accounts.filter(
|
|
194
|
+
(a) => a.providerType === filters.providerType
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
if (filters.isActive !== void 0) {
|
|
198
|
+
accounts = accounts.filter((a) => a.isActive === filters.isActive);
|
|
199
|
+
}
|
|
200
|
+
if (filters.accountType) {
|
|
201
|
+
accounts = accounts.filter((a) => {
|
|
202
|
+
const metaType = a._meta_type || "";
|
|
203
|
+
return metaType.includes(filters.accountType);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return accounts;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Get account statistics
|
|
211
|
+
*/
|
|
212
|
+
async getStats() {
|
|
213
|
+
const accounts = await this.list({});
|
|
214
|
+
const byType = {};
|
|
215
|
+
for (const account of accounts) {
|
|
216
|
+
const metaType = account._meta_type || "Unknown";
|
|
217
|
+
const shortType = metaType.split(":").pop() || metaType;
|
|
218
|
+
byType[shortType] = (byType[shortType] || 0) + 1;
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
total: accounts.length,
|
|
222
|
+
active: accounts.filter((a) => a.isActive).length,
|
|
223
|
+
inactive: accounts.filter((a) => !a.isActive).length,
|
|
224
|
+
byType
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
228
|
+
// Tenant Helper Methods
|
|
229
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
230
|
+
async findByTenant(tenantId2) {
|
|
231
|
+
return this.list({ where: { tenantId: tenantId2 } });
|
|
232
|
+
}
|
|
233
|
+
async findGlobal() {
|
|
234
|
+
return this.list({ where: { tenantId: null } });
|
|
235
|
+
}
|
|
236
|
+
async findWithGlobals(tenantId2) {
|
|
237
|
+
return this.query(
|
|
238
|
+
`SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,
|
|
239
|
+
[tenantId2]
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const AccountCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
244
|
+
__proto__: null,
|
|
245
|
+
AccountCollection
|
|
246
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
247
|
+
var __defProp$2 = Object.defineProperty;
|
|
248
|
+
var __getOwnPropDesc$a = Object.getOwnPropertyDescriptor;
|
|
249
|
+
var __decorateClass$a = (decorators, target, key, kind) => {
|
|
250
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$a(target, key) : target;
|
|
251
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
252
|
+
if (decorator = decorators[i])
|
|
253
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
254
|
+
if (kind && result) __defProp$2(target, key, result);
|
|
255
|
+
return result;
|
|
256
|
+
};
|
|
257
|
+
let Attachment = class extends SmrtObject {
|
|
258
|
+
tenantId = null;
|
|
259
|
+
messageId = "";
|
|
260
|
+
filename = "";
|
|
261
|
+
contentType = "";
|
|
262
|
+
size = 0;
|
|
263
|
+
contentId = "";
|
|
264
|
+
// For inline images (<img src="cid:...">)
|
|
265
|
+
contentDisposition = "attachment";
|
|
266
|
+
filePath = "";
|
|
267
|
+
// External file storage path
|
|
268
|
+
sourceUrl = "";
|
|
269
|
+
// For non-email attachments (media URLs, etc.)
|
|
270
|
+
// Timestamps
|
|
271
|
+
createdAt = /* @__PURE__ */ new Date();
|
|
272
|
+
constructor(options = {}) {
|
|
273
|
+
super(options);
|
|
274
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
275
|
+
if (options.messageId !== void 0) this.messageId = options.messageId;
|
|
276
|
+
if (options.filename !== void 0) this.filename = options.filename;
|
|
277
|
+
if (options.contentType !== void 0)
|
|
278
|
+
this.contentType = options.contentType;
|
|
279
|
+
if (options.size !== void 0) this.size = options.size;
|
|
280
|
+
if (options.contentId !== void 0) this.contentId = options.contentId;
|
|
281
|
+
if (options.contentDisposition !== void 0)
|
|
282
|
+
this.contentDisposition = options.contentDisposition;
|
|
283
|
+
if (options.filePath !== void 0) this.filePath = options.filePath;
|
|
284
|
+
if (options.sourceUrl !== void 0) this.sourceUrl = options.sourceUrl;
|
|
285
|
+
if (options.createdAt) this.createdAt = options.createdAt;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Get the message this attachment belongs to
|
|
289
|
+
*/
|
|
290
|
+
async getMessage() {
|
|
291
|
+
if (!this.messageId) return null;
|
|
292
|
+
const { MessageCollection: MessageCollection2 } = await Promise.resolve().then(() => MessageCollection$1);
|
|
293
|
+
const collection = await MessageCollection2.create(this.options);
|
|
294
|
+
return await collection.get({ id: this.messageId });
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Check if attachment is an image
|
|
298
|
+
*/
|
|
299
|
+
isImage() {
|
|
300
|
+
return this.contentType.startsWith("image/");
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Check if attachment is a PDF
|
|
304
|
+
*/
|
|
305
|
+
isPdf() {
|
|
306
|
+
return this.contentType === "application/pdf";
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Check if attachment is inline (embedded in message body)
|
|
310
|
+
*/
|
|
311
|
+
isInline() {
|
|
312
|
+
return this.contentDisposition === "inline";
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Get file extension from filename
|
|
316
|
+
*/
|
|
317
|
+
getExtension() {
|
|
318
|
+
if (!this.filename) return "";
|
|
319
|
+
const parts = this.filename.split(".");
|
|
320
|
+
return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : "";
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Get human-readable file size
|
|
324
|
+
*/
|
|
325
|
+
getFormattedSize() {
|
|
326
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
327
|
+
let size = this.size;
|
|
328
|
+
let unitIndex = 0;
|
|
329
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
330
|
+
size /= 1024;
|
|
331
|
+
unitIndex++;
|
|
332
|
+
}
|
|
333
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Check if file is stored externally
|
|
337
|
+
*/
|
|
338
|
+
hasExternalFile() {
|
|
339
|
+
return !!this.filePath;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Read file content (if stored externally)
|
|
343
|
+
*/
|
|
344
|
+
async readContent() {
|
|
345
|
+
if (!this.filePath) return null;
|
|
346
|
+
try {
|
|
347
|
+
const { getFilesystem } = await import("@happyvertical/files");
|
|
348
|
+
const files = await getFilesystem({ type: "local" });
|
|
349
|
+
const data = await files.read(this.filePath);
|
|
350
|
+
return data instanceof Buffer ? data : Buffer.from(data);
|
|
351
|
+
} catch {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
__decorateClass$a([
|
|
357
|
+
tenantId({ nullable: true })
|
|
358
|
+
], Attachment.prototype, "tenantId", 2);
|
|
359
|
+
__decorateClass$a([
|
|
360
|
+
foreignKey("Message")
|
|
361
|
+
], Attachment.prototype, "messageId", 2);
|
|
362
|
+
Attachment = __decorateClass$a([
|
|
363
|
+
TenantScoped({ mode: "optional" }),
|
|
364
|
+
smrt({
|
|
365
|
+
api: { include: ["list", "get", "create", "delete"] },
|
|
366
|
+
mcp: { include: ["list", "get"] },
|
|
367
|
+
cli: true
|
|
368
|
+
})
|
|
369
|
+
], Attachment);
|
|
370
|
+
class AttachmentCollection extends SmrtCollection {
|
|
371
|
+
static _itemClass = Attachment;
|
|
372
|
+
/**
|
|
373
|
+
* Get attachments for a message
|
|
374
|
+
*/
|
|
375
|
+
async getByMessage(messageId) {
|
|
376
|
+
return await this.list({ where: { messageId } });
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Get attachments by content type
|
|
380
|
+
*/
|
|
381
|
+
async getByContentType(contentType) {
|
|
382
|
+
return await this.list({ where: { contentType } });
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Get image attachments
|
|
386
|
+
*/
|
|
387
|
+
async getImages(messageId) {
|
|
388
|
+
const attachments = messageId ? await this.getByMessage(messageId) : await this.list({});
|
|
389
|
+
return attachments.filter((a) => a.isImage());
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Get PDF attachments
|
|
393
|
+
*/
|
|
394
|
+
async getPdfs(messageId) {
|
|
395
|
+
const attachments = messageId ? await this.getByMessage(messageId) : await this.list({});
|
|
396
|
+
return attachments.filter((a) => a.isPdf());
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get inline attachments (embedded in message body)
|
|
400
|
+
*/
|
|
401
|
+
async getInline(messageId) {
|
|
402
|
+
const attachments = await this.getByMessage(messageId);
|
|
403
|
+
return attachments.filter((a) => a.isInline());
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Get regular attachments (not inline)
|
|
407
|
+
*/
|
|
408
|
+
async getRegular(messageId) {
|
|
409
|
+
const attachments = await this.getByMessage(messageId);
|
|
410
|
+
return attachments.filter((a) => !a.isInline());
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Get attachments with external files
|
|
414
|
+
*/
|
|
415
|
+
async getWithExternalFiles() {
|
|
416
|
+
const attachments = await this.list({});
|
|
417
|
+
return attachments.filter((a) => a.hasExternalFile());
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Get total size of attachments for a message
|
|
421
|
+
*/
|
|
422
|
+
async getTotalSize(messageId) {
|
|
423
|
+
const attachments = await this.getByMessage(messageId);
|
|
424
|
+
return attachments.reduce((sum, a) => sum + a.size, 0);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Get largest attachments
|
|
428
|
+
*/
|
|
429
|
+
async getLargest(limit = 10) {
|
|
430
|
+
const attachments = await this.list({});
|
|
431
|
+
return attachments.sort((a, b) => b.size - a.size).slice(0, limit);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Search attachments by filename
|
|
435
|
+
*/
|
|
436
|
+
async searchByFilename(query) {
|
|
437
|
+
const attachments = await this.list({});
|
|
438
|
+
const lowerQuery = query.toLowerCase();
|
|
439
|
+
return attachments.filter(
|
|
440
|
+
(a) => a.filename?.toLowerCase().includes(lowerQuery)
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Get attachments by extension
|
|
445
|
+
*/
|
|
446
|
+
async getByExtension(extension) {
|
|
447
|
+
const attachments = await this.list({});
|
|
448
|
+
const lowerExt = extension.toLowerCase().replace(/^\./, "");
|
|
449
|
+
return attachments.filter((a) => a.getExtension() === lowerExt);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Get attachment statistics
|
|
453
|
+
*/
|
|
454
|
+
async getStats() {
|
|
455
|
+
const attachments = await this.list({});
|
|
456
|
+
const byType = {};
|
|
457
|
+
for (const attachment of attachments) {
|
|
458
|
+
const type = attachment.contentType.split("/")[0] || "other";
|
|
459
|
+
byType[type] = (byType[type] || 0) + 1;
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
total: attachments.length,
|
|
463
|
+
totalSize: attachments.reduce((sum, a) => sum + a.size, 0),
|
|
464
|
+
byType,
|
|
465
|
+
inline: attachments.filter((a) => a.isInline()).length,
|
|
466
|
+
regular: attachments.filter((a) => !a.isInline()).length
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Delete all attachments for a message
|
|
471
|
+
*/
|
|
472
|
+
async deleteByMessage(messageId) {
|
|
473
|
+
const attachments = await this.getByMessage(messageId);
|
|
474
|
+
let count = 0;
|
|
475
|
+
for (const attachment of attachments) {
|
|
476
|
+
await attachment.delete();
|
|
477
|
+
count++;
|
|
478
|
+
}
|
|
479
|
+
return count;
|
|
480
|
+
}
|
|
481
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
482
|
+
// Tenant Helper Methods
|
|
483
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
484
|
+
async findByTenant(tenantId2) {
|
|
485
|
+
return this.list({ where: { tenantId: tenantId2 } });
|
|
486
|
+
}
|
|
487
|
+
async findGlobal() {
|
|
488
|
+
return this.list({ where: { tenantId: null } });
|
|
489
|
+
}
|
|
490
|
+
async findWithGlobals(tenantId2) {
|
|
491
|
+
return this.query(
|
|
492
|
+
`SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,
|
|
493
|
+
[tenantId2]
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
const AttachmentCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
498
|
+
__proto__: null,
|
|
499
|
+
AttachmentCollection
|
|
500
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
501
|
+
var __defProp$1 = Object.defineProperty;
|
|
502
|
+
var __getOwnPropDesc$9 = Object.getOwnPropertyDescriptor;
|
|
503
|
+
var __decorateClass$9 = (decorators, target, key, kind) => {
|
|
504
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$9(target, key) : target;
|
|
505
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
506
|
+
if (decorator = decorators[i])
|
|
507
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
508
|
+
if (kind && result) __defProp$1(target, key, result);
|
|
509
|
+
return result;
|
|
510
|
+
};
|
|
511
|
+
let Message = class extends SmrtObject {
|
|
512
|
+
tenantId = null;
|
|
513
|
+
accountId = "";
|
|
514
|
+
threadId = "";
|
|
515
|
+
subject = "";
|
|
516
|
+
body = "";
|
|
517
|
+
// Normalized plain text
|
|
518
|
+
fromAddress = "";
|
|
519
|
+
fromName = "";
|
|
520
|
+
toAddresses = "";
|
|
521
|
+
// JSON array of {address, name}
|
|
522
|
+
date = null;
|
|
523
|
+
isRead = false;
|
|
524
|
+
isFlagged = false;
|
|
525
|
+
hasAttachments = false;
|
|
526
|
+
size = 0;
|
|
527
|
+
metadata = "";
|
|
528
|
+
// JSON extension bag
|
|
529
|
+
// Send lifecycle fields
|
|
530
|
+
sendStatus = "draft";
|
|
531
|
+
sentAt = null;
|
|
532
|
+
sendError = "";
|
|
533
|
+
retryCount = 0;
|
|
534
|
+
maxRetries = 3;
|
|
535
|
+
scheduledSendAt = null;
|
|
536
|
+
inReplyToMessageId = "";
|
|
537
|
+
// Timestamps
|
|
538
|
+
createdAt = /* @__PURE__ */ new Date();
|
|
539
|
+
updatedAt = /* @__PURE__ */ new Date();
|
|
540
|
+
constructor(options = {}) {
|
|
541
|
+
super(options);
|
|
542
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
543
|
+
if (options.accountId !== void 0) this.accountId = options.accountId;
|
|
544
|
+
if (options.threadId !== void 0) this.threadId = options.threadId;
|
|
545
|
+
if (options.subject !== void 0) this.subject = options.subject;
|
|
546
|
+
if (options.body !== void 0) this.body = options.body;
|
|
547
|
+
if (options.fromAddress !== void 0)
|
|
548
|
+
this.fromAddress = options.fromAddress;
|
|
549
|
+
if (options.fromName !== void 0) this.fromName = options.fromName;
|
|
550
|
+
if (options.toAddresses !== void 0)
|
|
551
|
+
this.toAddresses = options.toAddresses;
|
|
552
|
+
if (options.date !== void 0) this.date = options.date || null;
|
|
553
|
+
if (options.isRead !== void 0) this.isRead = options.isRead;
|
|
554
|
+
if (options.isFlagged !== void 0) this.isFlagged = options.isFlagged;
|
|
555
|
+
if (options.hasAttachments !== void 0)
|
|
556
|
+
this.hasAttachments = options.hasAttachments;
|
|
557
|
+
if (options.size !== void 0) this.size = options.size;
|
|
558
|
+
if (options.metadata !== void 0) this.metadata = options.metadata;
|
|
559
|
+
if (options.sendStatus !== void 0) this.sendStatus = options.sendStatus;
|
|
560
|
+
if (options.sentAt !== void 0) this.sentAt = options.sentAt || null;
|
|
561
|
+
if (options.sendError !== void 0) this.sendError = options.sendError;
|
|
562
|
+
if (options.retryCount !== void 0) this.retryCount = options.retryCount;
|
|
563
|
+
if (options.maxRetries !== void 0) this.maxRetries = options.maxRetries;
|
|
564
|
+
if (options.scheduledSendAt !== void 0)
|
|
565
|
+
this.scheduledSendAt = options.scheduledSendAt || null;
|
|
566
|
+
if (options.inReplyToMessageId !== void 0)
|
|
567
|
+
this.inReplyToMessageId = options.inReplyToMessageId;
|
|
568
|
+
if (options.createdAt) this.createdAt = options.createdAt;
|
|
569
|
+
if (options.updatedAt) this.updatedAt = options.updatedAt;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Get to addresses as parsed array
|
|
573
|
+
*/
|
|
574
|
+
getToAddresses() {
|
|
575
|
+
if (!this.toAddresses) return [];
|
|
576
|
+
try {
|
|
577
|
+
return JSON.parse(this.toAddresses);
|
|
578
|
+
} catch {
|
|
579
|
+
return [];
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Set to addresses from array
|
|
584
|
+
*/
|
|
585
|
+
setToAddresses(addresses) {
|
|
586
|
+
this.toAddresses = JSON.stringify(addresses);
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Get metadata as parsed object
|
|
590
|
+
*/
|
|
591
|
+
getMetadata() {
|
|
592
|
+
if (!this.metadata) return {};
|
|
593
|
+
try {
|
|
594
|
+
return JSON.parse(this.metadata);
|
|
595
|
+
} catch {
|
|
596
|
+
return {};
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Set metadata from object
|
|
601
|
+
*/
|
|
602
|
+
setMetadata(data) {
|
|
603
|
+
this.metadata = JSON.stringify(data);
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Mark message as read
|
|
607
|
+
*/
|
|
608
|
+
async markRead() {
|
|
609
|
+
this.isRead = true;
|
|
610
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
611
|
+
await this.save();
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Mark message as unread
|
|
615
|
+
*/
|
|
616
|
+
async markUnread() {
|
|
617
|
+
this.isRead = false;
|
|
618
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
619
|
+
await this.save();
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Toggle flagged status
|
|
623
|
+
*/
|
|
624
|
+
async toggleFlagged() {
|
|
625
|
+
this.isFlagged = !this.isFlagged;
|
|
626
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
627
|
+
await this.save();
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Check if message is unread
|
|
631
|
+
*/
|
|
632
|
+
isUnread() {
|
|
633
|
+
return !this.isRead;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Get a short preview of the message body
|
|
637
|
+
*/
|
|
638
|
+
getPreview(maxLength = 200) {
|
|
639
|
+
const text = this.body || "";
|
|
640
|
+
if (text.length <= maxLength) return text;
|
|
641
|
+
return `${text.slice(0, maxLength)}...`;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Get the account for this message
|
|
645
|
+
*/
|
|
646
|
+
async getAccount() {
|
|
647
|
+
if (!this.accountId) return null;
|
|
648
|
+
const { AccountCollection: AccountCollection2 } = await Promise.resolve().then(() => AccountCollection$1);
|
|
649
|
+
const collection = await AccountCollection2.create(this.options);
|
|
650
|
+
return await collection.get({ id: this.accountId });
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Get messages in the same thread
|
|
654
|
+
*/
|
|
655
|
+
async getThreadMessages() {
|
|
656
|
+
if (!this.threadId) return [this];
|
|
657
|
+
const { MessageCollection: MessageCollection2 } = await Promise.resolve().then(() => MessageCollection$1);
|
|
658
|
+
const collection = await MessageCollection2.create(this.options);
|
|
659
|
+
return await collection.list({ where: { threadId: this.threadId } });
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Get attachments for this message
|
|
663
|
+
*/
|
|
664
|
+
async getAttachments() {
|
|
665
|
+
const { AttachmentCollection: AttachmentCollection2 } = await Promise.resolve().then(() => AttachmentCollection$1);
|
|
666
|
+
const collection = await AttachmentCollection2.create(this.options);
|
|
667
|
+
return await collection.list({ where: { messageId: this.id } });
|
|
668
|
+
}
|
|
669
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
670
|
+
// Send Lifecycle
|
|
671
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
672
|
+
/**
|
|
673
|
+
* Send this message via its account's sender
|
|
674
|
+
*/
|
|
675
|
+
async send(options) {
|
|
676
|
+
const account = await this.getAccount();
|
|
677
|
+
if (!account) {
|
|
678
|
+
const result = {
|
|
679
|
+
success: false,
|
|
680
|
+
error: "No account associated with this message",
|
|
681
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
682
|
+
};
|
|
683
|
+
this.sendStatus = "failed";
|
|
684
|
+
this.sendError = result.error ?? "";
|
|
685
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
686
|
+
await this.save();
|
|
687
|
+
return result;
|
|
688
|
+
}
|
|
689
|
+
const sender = await account.createSender();
|
|
690
|
+
this.sendStatus = "sending";
|
|
691
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
692
|
+
await this.save();
|
|
693
|
+
try {
|
|
694
|
+
const result = await sender.send(this, options);
|
|
695
|
+
if (result.success) {
|
|
696
|
+
this.sendStatus = "sent";
|
|
697
|
+
this.sentAt = result.sentAt;
|
|
698
|
+
this.sendError = "";
|
|
699
|
+
} else {
|
|
700
|
+
this.sendStatus = "failed";
|
|
701
|
+
this.sendError = result.error ?? "Send failed";
|
|
702
|
+
}
|
|
703
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
704
|
+
await this.save();
|
|
705
|
+
return result;
|
|
706
|
+
} catch (error) {
|
|
707
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
708
|
+
this.sendStatus = "failed";
|
|
709
|
+
this.sendError = errorMessage;
|
|
710
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
711
|
+
await this.save();
|
|
712
|
+
return {
|
|
713
|
+
success: false,
|
|
714
|
+
error: errorMessage,
|
|
715
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Retry sending a failed message
|
|
721
|
+
*/
|
|
722
|
+
async retrySend(options) {
|
|
723
|
+
if (this.sendStatus !== "failed") {
|
|
724
|
+
return {
|
|
725
|
+
success: false,
|
|
726
|
+
error: `Cannot retry: message status is '${this.sendStatus}', expected 'failed'`,
|
|
727
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
if (this.retryCount >= this.maxRetries) {
|
|
731
|
+
return {
|
|
732
|
+
success: false,
|
|
733
|
+
error: `Retry budget exhausted (${this.retryCount}/${this.maxRetries})`,
|
|
734
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
this.retryCount++;
|
|
738
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
739
|
+
await this.save();
|
|
740
|
+
return this.send(options);
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Create a reply to this message (returns unsaved draft)
|
|
744
|
+
*/
|
|
745
|
+
createReply(_options) {
|
|
746
|
+
const reply = new this.constructor({
|
|
747
|
+
...this.options,
|
|
748
|
+
id: void 0,
|
|
749
|
+
accountId: this.accountId,
|
|
750
|
+
threadId: this.threadId || this.id || "",
|
|
751
|
+
subject: this.subject.startsWith("Re:") ? this.subject : `Re: ${this.subject}`,
|
|
752
|
+
toAddresses: JSON.stringify([
|
|
753
|
+
{ address: this.fromAddress, name: this.fromName }
|
|
754
|
+
]),
|
|
755
|
+
fromAddress: "",
|
|
756
|
+
fromName: "",
|
|
757
|
+
body: this.buildQuotedBody(),
|
|
758
|
+
inReplyToMessageId: this.id || "",
|
|
759
|
+
sendStatus: "draft",
|
|
760
|
+
isRead: true,
|
|
761
|
+
date: null,
|
|
762
|
+
createdAt: void 0,
|
|
763
|
+
updatedAt: void 0
|
|
764
|
+
});
|
|
765
|
+
return reply;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Create a forward of this message (returns unsaved draft)
|
|
769
|
+
*/
|
|
770
|
+
createForward() {
|
|
771
|
+
const forward = new this.constructor({
|
|
772
|
+
...this.options,
|
|
773
|
+
id: void 0,
|
|
774
|
+
accountId: this.accountId,
|
|
775
|
+
threadId: "",
|
|
776
|
+
subject: this.subject.startsWith("Fwd:") ? this.subject : `Fwd: ${this.subject}`,
|
|
777
|
+
toAddresses: "[]",
|
|
778
|
+
fromAddress: "",
|
|
779
|
+
fromName: "",
|
|
780
|
+
body: this.buildQuotedBody(),
|
|
781
|
+
hasAttachments: this.hasAttachments,
|
|
782
|
+
inReplyToMessageId: "",
|
|
783
|
+
sendStatus: "draft",
|
|
784
|
+
isRead: true,
|
|
785
|
+
date: null,
|
|
786
|
+
createdAt: void 0,
|
|
787
|
+
updatedAt: void 0
|
|
788
|
+
});
|
|
789
|
+
return forward;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Build quoted body for reply/forward
|
|
793
|
+
*/
|
|
794
|
+
buildQuotedBody() {
|
|
795
|
+
const dateStr = this.date ? this.date.toLocaleString() : "unknown date";
|
|
796
|
+
const from = this.fromName ? `${this.fromName} <${this.fromAddress}>` : this.fromAddress;
|
|
797
|
+
const quotedLines = (this.body || "").split("\n").map((line) => `> ${line}`).join("\n");
|
|
798
|
+
return `
|
|
799
|
+
|
|
800
|
+
On ${dateStr}, ${from} wrote:
|
|
801
|
+
${quotedLines}`;
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
__decorateClass$9([
|
|
805
|
+
tenantId({ nullable: true })
|
|
806
|
+
], Message.prototype, "tenantId", 2);
|
|
807
|
+
__decorateClass$9([
|
|
808
|
+
foreignKey("Account")
|
|
809
|
+
], Message.prototype, "accountId", 2);
|
|
810
|
+
__decorateClass$9([
|
|
811
|
+
foreignKey("Message")
|
|
812
|
+
], Message.prototype, "inReplyToMessageId", 2);
|
|
813
|
+
Message = __decorateClass$9([
|
|
814
|
+
TenantScoped({ mode: "optional" }),
|
|
815
|
+
smrt({
|
|
816
|
+
tableStrategy: "sti",
|
|
817
|
+
api: { include: ["list", "get"] },
|
|
818
|
+
mcp: { include: ["list", "get"] },
|
|
819
|
+
cli: true
|
|
820
|
+
})
|
|
821
|
+
], Message);
|
|
822
|
+
class MessageCollection extends SmrtCollection {
|
|
823
|
+
static _itemClass = Message;
|
|
824
|
+
/**
|
|
825
|
+
* Search messages with filters
|
|
826
|
+
*/
|
|
827
|
+
async search(query, filters) {
|
|
828
|
+
let messages = await this.list({});
|
|
829
|
+
if (query) {
|
|
830
|
+
const lowerQuery = query.toLowerCase();
|
|
831
|
+
messages = messages.filter(
|
|
832
|
+
(m) => m.subject?.toLowerCase().includes(lowerQuery) || m.body?.toLowerCase().includes(lowerQuery) || m.fromAddress?.toLowerCase().includes(lowerQuery) || m.fromName?.toLowerCase().includes(lowerQuery)
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
if (filters) {
|
|
836
|
+
if (filters.accountIds && filters.accountIds.length > 0) {
|
|
837
|
+
messages = messages.filter(
|
|
838
|
+
(m) => filters.accountIds?.includes(m.accountId)
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
if (filters.messageType) {
|
|
842
|
+
messages = messages.filter((m) => {
|
|
843
|
+
const metaType = m._meta_type || "";
|
|
844
|
+
return metaType.includes(filters.messageType);
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
if (filters.from) {
|
|
848
|
+
const fromLower = filters.from.toLowerCase();
|
|
849
|
+
messages = messages.filter(
|
|
850
|
+
(m) => m.fromAddress?.toLowerCase().includes(fromLower) || m.fromName?.toLowerCase().includes(fromLower)
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
if (filters.to) {
|
|
854
|
+
const toLower = filters.to.toLowerCase();
|
|
855
|
+
messages = messages.filter(
|
|
856
|
+
(m) => m.toAddresses?.toLowerCase().includes(toLower)
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
if (filters.isRead !== void 0) {
|
|
860
|
+
messages = messages.filter((m) => m.isRead === filters.isRead);
|
|
861
|
+
}
|
|
862
|
+
if (filters.isFlagged !== void 0) {
|
|
863
|
+
messages = messages.filter((m) => m.isFlagged === filters.isFlagged);
|
|
864
|
+
}
|
|
865
|
+
if (filters.sinceDate) {
|
|
866
|
+
messages = messages.filter(
|
|
867
|
+
(m) => m.date && m.date >= filters.sinceDate
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
if (filters.beforeDate) {
|
|
871
|
+
messages = messages.filter(
|
|
872
|
+
(m) => m.date && m.date < filters.beforeDate
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
if (filters.query) {
|
|
876
|
+
const q = filters.query.toLowerCase();
|
|
877
|
+
messages = messages.filter(
|
|
878
|
+
(m) => m.subject?.toLowerCase().includes(q) || m.body?.toLowerCase().includes(q)
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return messages;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Get messages by multiple accounts
|
|
886
|
+
*/
|
|
887
|
+
async getByAccounts(accountIds) {
|
|
888
|
+
const allMessages = await this.list({});
|
|
889
|
+
return allMessages.filter((m) => accountIds.includes(m.accountId));
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Get messages by STI type.
|
|
893
|
+
*
|
|
894
|
+
* Accepts either the full discriminator (e.g. "@happyvertical/smrt-messages:Email")
|
|
895
|
+
* or the short type name (e.g. "Email").
|
|
896
|
+
*/
|
|
897
|
+
async getByType(messageType) {
|
|
898
|
+
if (messageType.includes(":") || messageType.startsWith("@")) {
|
|
899
|
+
return await this.list({ where: { _meta_type: messageType } });
|
|
900
|
+
}
|
|
901
|
+
const allMessages = await this.list({});
|
|
902
|
+
return allMessages.filter((m) => {
|
|
903
|
+
const metaType = m._meta_type || "";
|
|
904
|
+
return metaType.endsWith(`:${messageType}`);
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Get unread messages
|
|
909
|
+
*/
|
|
910
|
+
async getUnread(accountId) {
|
|
911
|
+
const where = { isRead: false };
|
|
912
|
+
if (accountId) {
|
|
913
|
+
where.accountId = accountId;
|
|
914
|
+
}
|
|
915
|
+
return await this.list({ where });
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Get flagged messages
|
|
919
|
+
*/
|
|
920
|
+
async getFlagged(accountId) {
|
|
921
|
+
const where = { isFlagged: true };
|
|
922
|
+
if (accountId) {
|
|
923
|
+
where.accountId = accountId;
|
|
924
|
+
}
|
|
925
|
+
return await this.list({ where });
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Get recent messages
|
|
929
|
+
*/
|
|
930
|
+
async getRecent(limit = 20, accountId) {
|
|
931
|
+
const allMessages = await this.list({
|
|
932
|
+
where: accountId ? { accountId } : void 0
|
|
933
|
+
});
|
|
934
|
+
return allMessages.sort((a, b) => {
|
|
935
|
+
const dateA = a.date?.getTime() || 0;
|
|
936
|
+
const dateB = b.date?.getTime() || 0;
|
|
937
|
+
return dateB - dateA;
|
|
938
|
+
}).slice(0, limit);
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Get messages by thread
|
|
942
|
+
*/
|
|
943
|
+
async getByThread(threadId) {
|
|
944
|
+
return await this.list({ where: { threadId } });
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Mark multiple messages as read
|
|
948
|
+
*/
|
|
949
|
+
async markAllRead(messageIds) {
|
|
950
|
+
for (const id of messageIds) {
|
|
951
|
+
const message = await this.get({ id });
|
|
952
|
+
if (message) {
|
|
953
|
+
await message.markRead();
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Get message statistics for an account
|
|
959
|
+
*/
|
|
960
|
+
async getAccountStats(accountId) {
|
|
961
|
+
const messages = await this.list({ where: { accountId } });
|
|
962
|
+
const byType = {};
|
|
963
|
+
for (const msg of messages) {
|
|
964
|
+
const metaType = msg._meta_type || "Unknown";
|
|
965
|
+
const shortType = metaType.split(":").pop() || metaType;
|
|
966
|
+
byType[shortType] = (byType[shortType] || 0) + 1;
|
|
967
|
+
}
|
|
968
|
+
return {
|
|
969
|
+
total: messages.length,
|
|
970
|
+
unread: messages.filter((m) => !m.isRead).length,
|
|
971
|
+
flagged: messages.filter((m) => m.isFlagged).length,
|
|
972
|
+
byType
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
976
|
+
// Send / Draft Queries
|
|
977
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
978
|
+
/**
|
|
979
|
+
* Get draft messages
|
|
980
|
+
*/
|
|
981
|
+
async getDrafts(accountId) {
|
|
982
|
+
const where = { sendStatus: "draft" };
|
|
983
|
+
if (accountId) where.accountId = accountId;
|
|
984
|
+
return await this.list({ where });
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Get sent messages
|
|
988
|
+
*/
|
|
989
|
+
async getSent(accountId) {
|
|
990
|
+
const where = { sendStatus: "sent" };
|
|
991
|
+
if (accountId) where.accountId = accountId;
|
|
992
|
+
return await this.list({ where });
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Get scheduled messages
|
|
996
|
+
*/
|
|
997
|
+
async getScheduled(accountId) {
|
|
998
|
+
const where = { sendStatus: "scheduled" };
|
|
999
|
+
if (accountId) where.accountId = accountId;
|
|
1000
|
+
return await this.list({ where });
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Get messages that failed to send
|
|
1004
|
+
*/
|
|
1005
|
+
async getFailedSends(accountId) {
|
|
1006
|
+
const where = { sendStatus: "failed" };
|
|
1007
|
+
if (accountId) where.accountId = accountId;
|
|
1008
|
+
return await this.list({ where });
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Get outbox (pending + sending + scheduled)
|
|
1012
|
+
*/
|
|
1013
|
+
async getOutbox(accountId) {
|
|
1014
|
+
const allMessages = await this.list({
|
|
1015
|
+
where: accountId ? { accountId } : void 0
|
|
1016
|
+
});
|
|
1017
|
+
return allMessages.filter(
|
|
1018
|
+
(m) => m.sendStatus === "pending" || m.sendStatus === "sending" || m.sendStatus === "scheduled"
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1022
|
+
// Tenant Helper Methods
|
|
1023
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1024
|
+
async findByTenant(tenantId2) {
|
|
1025
|
+
return this.list({ where: { tenantId: tenantId2 } });
|
|
1026
|
+
}
|
|
1027
|
+
async findGlobal() {
|
|
1028
|
+
return this.list({ where: { tenantId: null } });
|
|
1029
|
+
}
|
|
1030
|
+
async findWithGlobals(tenantId2) {
|
|
1031
|
+
return this.query(
|
|
1032
|
+
`SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,
|
|
1033
|
+
[tenantId2]
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
const MessageCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1038
|
+
__proto__: null,
|
|
1039
|
+
MessageCollection
|
|
1040
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1041
|
+
var __getOwnPropDesc$8 = Object.getOwnPropertyDescriptor;
|
|
1042
|
+
var __decorateClass$8 = (decorators, target, key, kind) => {
|
|
1043
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$8(target, key) : target;
|
|
1044
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
1045
|
+
if (decorator = decorators[i])
|
|
1046
|
+
result = decorator(result) || result;
|
|
1047
|
+
return result;
|
|
1048
|
+
};
|
|
1049
|
+
let EmailAccount = class extends Account {
|
|
1050
|
+
email = "";
|
|
1051
|
+
syncIntervalMinutes = 60;
|
|
1052
|
+
constructor(options = {}) {
|
|
1053
|
+
super(options);
|
|
1054
|
+
if (options.email !== void 0) this.email = options.email;
|
|
1055
|
+
if (options.providerType !== void 0)
|
|
1056
|
+
this.providerType = options.providerType;
|
|
1057
|
+
if (options.syncIntervalMinutes !== void 0)
|
|
1058
|
+
this.syncIntervalMinutes = options.syncIntervalMinutes;
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Options for child rows (folders/emails) created during sync: carries the DB
|
|
1062
|
+
* connection + tenant context from this account, but strips this account's own
|
|
1063
|
+
* identity fields. When this account was hydrated from the DB, `this.options`
|
|
1064
|
+
* holds the account row's `id`/`slug`/`_skipLoad`; spreading those into a new
|
|
1065
|
+
* child would make every synced row inherit the account's primary key and
|
|
1066
|
+
* upsert over each other.
|
|
1067
|
+
*/
|
|
1068
|
+
childOptions() {
|
|
1069
|
+
const rest = { ...this.options };
|
|
1070
|
+
delete rest.id;
|
|
1071
|
+
delete rest.slug;
|
|
1072
|
+
delete rest._skipLoad;
|
|
1073
|
+
return rest;
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Create a sender for this email account
|
|
1077
|
+
*/
|
|
1078
|
+
async createSender() {
|
|
1079
|
+
const client = await this.createClient();
|
|
1080
|
+
await client.connect();
|
|
1081
|
+
const { EmailSender: EmailSender2 } = await Promise.resolve().then(() => EmailSender$1);
|
|
1082
|
+
return new EmailSender2(client, this);
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Create an EmailClient from stored settings
|
|
1086
|
+
* Retrieves credentials from smrt-secrets if credentialSecretId is set
|
|
1087
|
+
*/
|
|
1088
|
+
async createClient() {
|
|
1089
|
+
const { getEmailClient } = await import("@happyvertical/email");
|
|
1090
|
+
let settings;
|
|
1091
|
+
if (this.credentialSecretId) {
|
|
1092
|
+
const { SecretService } = await import("@happyvertical/smrt-secrets");
|
|
1093
|
+
const secretService = await SecretService.create({ db: this.db });
|
|
1094
|
+
const secret = await secretService.retrieve(this.credentialSecretId);
|
|
1095
|
+
settings = JSON.parse(secret.value);
|
|
1096
|
+
} else {
|
|
1097
|
+
settings = this.getSettings();
|
|
1098
|
+
}
|
|
1099
|
+
return await getEmailClient({
|
|
1100
|
+
type: this.providerType,
|
|
1101
|
+
...settings
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Sync emails from the email server to the database
|
|
1106
|
+
*/
|
|
1107
|
+
async syncFrom(options = {}) {
|
|
1108
|
+
const startTime = Date.now();
|
|
1109
|
+
const result = {
|
|
1110
|
+
folders: [],
|
|
1111
|
+
messagesProcessed: 0,
|
|
1112
|
+
messagesDownloaded: 0,
|
|
1113
|
+
messagesSkipped: 0,
|
|
1114
|
+
errors: [],
|
|
1115
|
+
duration: 0
|
|
1116
|
+
};
|
|
1117
|
+
try {
|
|
1118
|
+
const client = await this.createClient();
|
|
1119
|
+
await client.connect();
|
|
1120
|
+
const foldersToSync = options.folders || ["INBOX"];
|
|
1121
|
+
result.folders = foldersToSync;
|
|
1122
|
+
const { EmailCollection: EmailCollection2 } = await Promise.resolve().then(() => EmailCollection$1);
|
|
1123
|
+
const { EmailFolderCollection: EmailFolderCollection2 } = await Promise.resolve().then(() => EmailFolderCollection$1);
|
|
1124
|
+
const emailCollection = await EmailCollection2.create(
|
|
1125
|
+
this.options
|
|
1126
|
+
);
|
|
1127
|
+
const folderCollection = await EmailFolderCollection2.create(
|
|
1128
|
+
this.options
|
|
1129
|
+
);
|
|
1130
|
+
for (const folderName of foldersToSync) {
|
|
1131
|
+
try {
|
|
1132
|
+
const accountId = this.id ?? "";
|
|
1133
|
+
let folder = await folderCollection.getByPath(accountId, folderName);
|
|
1134
|
+
if (!folder) {
|
|
1135
|
+
const { EmailFolder: EmailFolder2 } = await Promise.resolve().then(() => EmailFolder$1);
|
|
1136
|
+
folder = new EmailFolder2({
|
|
1137
|
+
...this.childOptions(),
|
|
1138
|
+
accountId,
|
|
1139
|
+
name: folderName,
|
|
1140
|
+
path: folderName
|
|
1141
|
+
});
|
|
1142
|
+
await folder.initialize();
|
|
1143
|
+
await folder.save();
|
|
1144
|
+
}
|
|
1145
|
+
const fetchOptions = {
|
|
1146
|
+
folder: folderName,
|
|
1147
|
+
limit: options.batchSize || 100
|
|
1148
|
+
};
|
|
1149
|
+
if (options.since) {
|
|
1150
|
+
fetchOptions.since = options.since;
|
|
1151
|
+
}
|
|
1152
|
+
if (options.before) {
|
|
1153
|
+
fetchOptions.before = options.before;
|
|
1154
|
+
}
|
|
1155
|
+
const messages = await client.fetch(fetchOptions);
|
|
1156
|
+
for (const msg of messages) {
|
|
1157
|
+
result.messagesProcessed++;
|
|
1158
|
+
try {
|
|
1159
|
+
const existingEmail = await emailCollection.getByMessageId(
|
|
1160
|
+
this.id,
|
|
1161
|
+
msg.messageId || ""
|
|
1162
|
+
);
|
|
1163
|
+
if (existingEmail && !options.fullSync) {
|
|
1164
|
+
result.messagesSkipped++;
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1167
|
+
const { Email: Email2 } = await Promise.resolve().then(() => Email$1);
|
|
1168
|
+
let email = existingEmail;
|
|
1169
|
+
if (!email) {
|
|
1170
|
+
email = new Email2({
|
|
1171
|
+
...this.childOptions(),
|
|
1172
|
+
accountId
|
|
1173
|
+
});
|
|
1174
|
+
await email.initialize();
|
|
1175
|
+
}
|
|
1176
|
+
email.messageId = msg.messageId || "";
|
|
1177
|
+
email.threadId = msg.threadId || "";
|
|
1178
|
+
email.inReplyTo = msg.inReplyTo || "";
|
|
1179
|
+
email.fromAddress = msg.from?.address || "";
|
|
1180
|
+
email.fromName = msg.from?.name || "";
|
|
1181
|
+
email.toAddresses = JSON.stringify(msg.to || []);
|
|
1182
|
+
email.ccAddresses = JSON.stringify(msg.cc || []);
|
|
1183
|
+
email.bccAddresses = JSON.stringify(msg.bcc || []);
|
|
1184
|
+
email.replyToAddress = msg.replyTo?.address || "";
|
|
1185
|
+
email.replyToName = msg.replyTo?.name || "";
|
|
1186
|
+
email.subject = msg.subject || "";
|
|
1187
|
+
email.date = msg.date || null;
|
|
1188
|
+
email.textBody = msg.text || "";
|
|
1189
|
+
email.htmlBody = msg.html || "";
|
|
1190
|
+
email.body = msg.text || "";
|
|
1191
|
+
email.folderId = folder.id;
|
|
1192
|
+
email.folderPath = folderName;
|
|
1193
|
+
email.labels = JSON.stringify(msg.labels || []);
|
|
1194
|
+
email.flags = JSON.stringify(msg.flags || []);
|
|
1195
|
+
email.hasAttachments = msg.attachments && msg.attachments.length > 0 || false;
|
|
1196
|
+
email.size = msg.size || 0;
|
|
1197
|
+
email.headers = JSON.stringify(msg.headers || {});
|
|
1198
|
+
email.updatedAt = /* @__PURE__ */ new Date();
|
|
1199
|
+
await email.save();
|
|
1200
|
+
result.messagesDownloaded++;
|
|
1201
|
+
if (options.onProgress) {
|
|
1202
|
+
options.onProgress({
|
|
1203
|
+
folder: folderName,
|
|
1204
|
+
processed: result.messagesProcessed,
|
|
1205
|
+
total: messages.length,
|
|
1206
|
+
downloaded: result.messagesDownloaded,
|
|
1207
|
+
skipped: result.messagesSkipped,
|
|
1208
|
+
errors: result.errors.length
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1213
|
+
result.errors.push(err);
|
|
1214
|
+
if (options.onError) {
|
|
1215
|
+
options.onError(err, msg);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
folder.messageCount = await emailCollection.countByFolder(folder.id);
|
|
1220
|
+
folder.unreadCount = await emailCollection.countUnreadByFolder(
|
|
1221
|
+
folder.id
|
|
1222
|
+
);
|
|
1223
|
+
await folder.save();
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1226
|
+
result.errors.push(err);
|
|
1227
|
+
if (options.onError) {
|
|
1228
|
+
options.onError(err);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
this.lastSyncAt = /* @__PURE__ */ new Date();
|
|
1233
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
1234
|
+
await this.save();
|
|
1235
|
+
await client.disconnect();
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1238
|
+
result.errors.push(err);
|
|
1239
|
+
if (options.onError) {
|
|
1240
|
+
options.onError(err);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
result.duration = Date.now() - startTime;
|
|
1244
|
+
return result;
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Get all folders for this account
|
|
1248
|
+
*/
|
|
1249
|
+
async getFolders() {
|
|
1250
|
+
const { EmailFolderCollection: EmailFolderCollection2 } = await Promise.resolve().then(() => EmailFolderCollection$1);
|
|
1251
|
+
const collection = await EmailFolderCollection2.create(
|
|
1252
|
+
this.options
|
|
1253
|
+
);
|
|
1254
|
+
return await collection.list({ where: { accountId: this.id } });
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Get all emails for this account
|
|
1258
|
+
*/
|
|
1259
|
+
async getEmails(limit) {
|
|
1260
|
+
const { EmailCollection: EmailCollection2 } = await Promise.resolve().then(() => EmailCollection$1);
|
|
1261
|
+
const collection = await EmailCollection2.create(this.options);
|
|
1262
|
+
const options = { where: { accountId: this.id } };
|
|
1263
|
+
if (limit) {
|
|
1264
|
+
options.limit = limit;
|
|
1265
|
+
}
|
|
1266
|
+
return await collection.list(options);
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Get unread email count
|
|
1270
|
+
*/
|
|
1271
|
+
async getUnreadCount() {
|
|
1272
|
+
const { EmailCollection: EmailCollection2 } = await Promise.resolve().then(() => EmailCollection$1);
|
|
1273
|
+
const collection = await EmailCollection2.create(this.options);
|
|
1274
|
+
return await collection.countUnreadByAccount(this.id);
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Store credentials securely using smrt-secrets (email-specific)
|
|
1278
|
+
*/
|
|
1279
|
+
async setCredentials(credentials, options = {}) {
|
|
1280
|
+
const { SecretService } = await import("@happyvertical/smrt-secrets");
|
|
1281
|
+
const secretService = await SecretService.create({ db: this.db });
|
|
1282
|
+
const secretName = `email-account-${this.id}`;
|
|
1283
|
+
const secretValue = JSON.stringify(credentials);
|
|
1284
|
+
if (this.credentialSecretId) {
|
|
1285
|
+
await secretService.store(this.credentialSecretId, secretValue, {
|
|
1286
|
+
description: options.description || `IMAP credentials for ${this.name}`,
|
|
1287
|
+
category: options.category || "email"
|
|
1288
|
+
});
|
|
1289
|
+
} else {
|
|
1290
|
+
await secretService.store(secretName, secretValue, {
|
|
1291
|
+
description: options.description || `IMAP credentials for ${this.name}`,
|
|
1292
|
+
category: options.category || "email"
|
|
1293
|
+
});
|
|
1294
|
+
this.credentialSecretId = secretName;
|
|
1295
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
1296
|
+
await this.save();
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Sync all active email accounts (for job runner)
|
|
1301
|
+
*
|
|
1302
|
+
* NOTE: This is a class-wide operation, not per-instance. It syncs ALL active
|
|
1303
|
+
* accounts, ignoring `this` instance. It exists as an instance method because
|
|
1304
|
+
* the TaskRunner dispatches via `objectType: 'EmailAccount', method: 'syncAll'`
|
|
1305
|
+
* which requires an instance method signature.
|
|
1306
|
+
*/
|
|
1307
|
+
async syncAll(args) {
|
|
1308
|
+
const { EmailAccountCollection: EmailAccountCollection2 } = await Promise.resolve().then(() => EmailAccountCollection$1);
|
|
1309
|
+
const collection = await EmailAccountCollection2.create({
|
|
1310
|
+
db: this.db
|
|
1311
|
+
});
|
|
1312
|
+
return collection.syncAll(args);
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Migrate from plain-text settings to secrets
|
|
1316
|
+
*/
|
|
1317
|
+
async migrateToSecrets() {
|
|
1318
|
+
if (this.credentialSecretId) {
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
const settings = this.getSettings();
|
|
1322
|
+
if (Object.keys(settings).length === 0) {
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
await this.setCredentials(settings, {
|
|
1326
|
+
description: `Migrated IMAP credentials for ${this.name}`
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
EmailAccount = __decorateClass$8([
|
|
1331
|
+
smrt({
|
|
1332
|
+
tableStrategy: "sti",
|
|
1333
|
+
api: { include: ["list", "get", "create", "update", "delete"] },
|
|
1334
|
+
mcp: { include: ["list", "get"] },
|
|
1335
|
+
cli: true
|
|
1336
|
+
})
|
|
1337
|
+
], EmailAccount);
|
|
1338
|
+
class EmailAccountCollection extends AccountCollection {
|
|
1339
|
+
static _itemClass = EmailAccount;
|
|
1340
|
+
/**
|
|
1341
|
+
* Get account by email address
|
|
1342
|
+
*/
|
|
1343
|
+
async getByEmail(email) {
|
|
1344
|
+
const accounts = await this.list({ where: { email } });
|
|
1345
|
+
return accounts[0] || null;
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Get accounts by email provider type
|
|
1349
|
+
*/
|
|
1350
|
+
async getByEmailProviderType(providerType) {
|
|
1351
|
+
return await this.list({ where: { providerType } });
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Get active email accounts
|
|
1355
|
+
*/
|
|
1356
|
+
async getActive() {
|
|
1357
|
+
return await this.list({ where: { isActive: true } });
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Get inactive email accounts
|
|
1361
|
+
*/
|
|
1362
|
+
async getInactive() {
|
|
1363
|
+
return await this.list({ where: { isActive: false } });
|
|
1364
|
+
}
|
|
1365
|
+
/**
|
|
1366
|
+
* Get accounts that need syncing
|
|
1367
|
+
*/
|
|
1368
|
+
async getNeedingSync(maxAgeMinutes = 60) {
|
|
1369
|
+
const allAccounts = await this.getActive();
|
|
1370
|
+
const cutoffTime = new Date(Date.now() - maxAgeMinutes * 60 * 1e3);
|
|
1371
|
+
return allAccounts.filter(
|
|
1372
|
+
(account) => !account.lastSyncAt || account.lastSyncAt < cutoffTime
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Search email accounts with filters.
|
|
1377
|
+
* Alias: `search()` for backward compatibility.
|
|
1378
|
+
*/
|
|
1379
|
+
async search(query, filters) {
|
|
1380
|
+
return this.searchEmailAccounts(query, filters);
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Get accounts by email provider type.
|
|
1384
|
+
* Alias: `getByProviderType()` for backward compatibility.
|
|
1385
|
+
*/
|
|
1386
|
+
async getByProviderType(providerType) {
|
|
1387
|
+
return this.getByEmailProviderType(providerType);
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Get email account statistics.
|
|
1391
|
+
* Alias: `getStats()` for backward compatibility.
|
|
1392
|
+
*/
|
|
1393
|
+
async getStats() {
|
|
1394
|
+
const stats = await this.getEmailStats();
|
|
1395
|
+
return {
|
|
1396
|
+
total: stats.total,
|
|
1397
|
+
active: stats.active,
|
|
1398
|
+
inactive: stats.inactive,
|
|
1399
|
+
byType: stats.byProvider
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Search email accounts with filters
|
|
1404
|
+
*/
|
|
1405
|
+
async searchEmailAccounts(query, filters) {
|
|
1406
|
+
let accounts = await this.list({});
|
|
1407
|
+
if (query) {
|
|
1408
|
+
const lowerQuery = query.toLowerCase();
|
|
1409
|
+
accounts = accounts.filter(
|
|
1410
|
+
(a) => a.name?.toLowerCase().includes(lowerQuery) || a.email?.toLowerCase().includes(lowerQuery)
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
if (filters) {
|
|
1414
|
+
if (filters.providerType) {
|
|
1415
|
+
accounts = accounts.filter(
|
|
1416
|
+
(a) => a.providerType === filters.providerType
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
if (filters.email) {
|
|
1420
|
+
const emailLower = filters.email.toLowerCase();
|
|
1421
|
+
accounts = accounts.filter(
|
|
1422
|
+
(a) => a.email?.toLowerCase().includes(emailLower)
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
if (filters.isActive !== void 0) {
|
|
1426
|
+
accounts = accounts.filter((a) => a.isActive === filters.isActive);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
return accounts;
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Sync all active email accounts
|
|
1433
|
+
*/
|
|
1434
|
+
async syncAll(options) {
|
|
1435
|
+
const results = /* @__PURE__ */ new Map();
|
|
1436
|
+
const accounts = await this.getActive();
|
|
1437
|
+
for (const account of accounts) {
|
|
1438
|
+
const ea = account;
|
|
1439
|
+
const accountId = ea.id ?? ea.email ?? "unknown";
|
|
1440
|
+
try {
|
|
1441
|
+
await ea.syncFrom(options);
|
|
1442
|
+
results.set(accountId, { success: true });
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
results.set(accountId, {
|
|
1445
|
+
success: false,
|
|
1446
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
return results;
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Get total unread count across all email accounts
|
|
1454
|
+
*/
|
|
1455
|
+
async getTotalUnreadCount() {
|
|
1456
|
+
const accounts = await this.getActive();
|
|
1457
|
+
let total = 0;
|
|
1458
|
+
for (const account of accounts) {
|
|
1459
|
+
total += await account.getUnreadCount();
|
|
1460
|
+
}
|
|
1461
|
+
return total;
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Get email account statistics
|
|
1465
|
+
*/
|
|
1466
|
+
async getEmailStats() {
|
|
1467
|
+
const accounts = await this.list({});
|
|
1468
|
+
const byProvider = {
|
|
1469
|
+
smtp: 0,
|
|
1470
|
+
imap: 0,
|
|
1471
|
+
pop3: 0,
|
|
1472
|
+
gmail: 0
|
|
1473
|
+
};
|
|
1474
|
+
for (const account of accounts) {
|
|
1475
|
+
const pt = account.providerType;
|
|
1476
|
+
if (pt in byProvider) {
|
|
1477
|
+
byProvider[pt]++;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
return {
|
|
1481
|
+
total: accounts.length,
|
|
1482
|
+
active: accounts.filter((a) => a.isActive).length,
|
|
1483
|
+
inactive: accounts.filter((a) => !a.isActive).length,
|
|
1484
|
+
byProvider
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1488
|
+
// Tenant Helper Methods
|
|
1489
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1490
|
+
async findByTenant(tenantId2) {
|
|
1491
|
+
return this.list({ where: { tenantId: tenantId2 } });
|
|
1492
|
+
}
|
|
1493
|
+
async findGlobal() {
|
|
1494
|
+
return this.list({ where: { tenantId: null } });
|
|
1495
|
+
}
|
|
1496
|
+
async findWithGlobals(tenantId2) {
|
|
1497
|
+
return this.query(
|
|
1498
|
+
`SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,
|
|
1499
|
+
[tenantId2]
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
const EmailAccountCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1504
|
+
__proto__: null,
|
|
1505
|
+
EmailAccountCollection
|
|
1506
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1507
|
+
class EmailAttachment extends Attachment {
|
|
1508
|
+
/**
|
|
1509
|
+
* Legacy emailId field — maps to messageId
|
|
1510
|
+
*/
|
|
1511
|
+
get emailId() {
|
|
1512
|
+
return this.messageId;
|
|
1513
|
+
}
|
|
1514
|
+
set emailId(value) {
|
|
1515
|
+
this.messageId = value;
|
|
1516
|
+
}
|
|
1517
|
+
constructor(options = {}) {
|
|
1518
|
+
const mappedOptions = {
|
|
1519
|
+
...options,
|
|
1520
|
+
messageId: options.emailId || options.messageId || ""
|
|
1521
|
+
};
|
|
1522
|
+
super(mappedOptions);
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Get the email this attachment belongs to
|
|
1526
|
+
* @deprecated Use getMessage() instead
|
|
1527
|
+
*/
|
|
1528
|
+
async getEmail() {
|
|
1529
|
+
if (!this.messageId) return null;
|
|
1530
|
+
const { EmailCollection: EmailCollection2 } = await Promise.resolve().then(() => EmailCollection$1);
|
|
1531
|
+
const collection = await EmailCollection2.create(this.options);
|
|
1532
|
+
return await collection.get({ id: this.messageId });
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
class EmailAttachmentCollection extends AttachmentCollection {
|
|
1536
|
+
static _itemClass = EmailAttachment;
|
|
1537
|
+
/**
|
|
1538
|
+
* Get attachments for an email
|
|
1539
|
+
* @deprecated Use getByMessage() instead
|
|
1540
|
+
*/
|
|
1541
|
+
async getByEmail(emailId) {
|
|
1542
|
+
return await this.getByMessage(emailId);
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Get image attachments
|
|
1546
|
+
*/
|
|
1547
|
+
async getImages(emailId) {
|
|
1548
|
+
return await super.getImages(emailId);
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Get PDF attachments
|
|
1552
|
+
*/
|
|
1553
|
+
async getPdfs(emailId) {
|
|
1554
|
+
return await super.getPdfs(emailId);
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Get inline attachments
|
|
1558
|
+
*/
|
|
1559
|
+
async getInline(emailId) {
|
|
1560
|
+
return await super.getInline(emailId);
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Get regular attachments
|
|
1564
|
+
*/
|
|
1565
|
+
async getRegular(emailId) {
|
|
1566
|
+
return await super.getRegular(emailId);
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Delete all attachments for an email
|
|
1570
|
+
* @deprecated Use deleteByMessage() instead
|
|
1571
|
+
*/
|
|
1572
|
+
async deleteByEmail(emailId) {
|
|
1573
|
+
return await this.deleteByMessage(emailId);
|
|
1574
|
+
}
|
|
1575
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1576
|
+
// Tenant Helper Methods
|
|
1577
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1578
|
+
async findByTenant(tenantId2) {
|
|
1579
|
+
return this.list({ where: { tenantId: tenantId2 } });
|
|
1580
|
+
}
|
|
1581
|
+
async findGlobal() {
|
|
1582
|
+
return this.list({ where: { tenantId: null } });
|
|
1583
|
+
}
|
|
1584
|
+
async findWithGlobals(tenantId2) {
|
|
1585
|
+
return this.query(
|
|
1586
|
+
`SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,
|
|
1587
|
+
[tenantId2]
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
|
|
1592
|
+
var __decorateClass$7 = (decorators, target, key, kind) => {
|
|
1593
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$7(target, key) : target;
|
|
1594
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
1595
|
+
if (decorator = decorators[i])
|
|
1596
|
+
result = decorator(result) || result;
|
|
1597
|
+
return result;
|
|
1598
|
+
};
|
|
1599
|
+
let Email = class extends Message {
|
|
1600
|
+
// RFC 822 fields
|
|
1601
|
+
messageId = "";
|
|
1602
|
+
// RFC 822 Message-ID header
|
|
1603
|
+
inReplyTo = "";
|
|
1604
|
+
// Additional recipients
|
|
1605
|
+
ccAddresses = "";
|
|
1606
|
+
bccAddresses = "";
|
|
1607
|
+
replyToAddress = "";
|
|
1608
|
+
replyToName = "";
|
|
1609
|
+
// Email-specific content
|
|
1610
|
+
textBody = "";
|
|
1611
|
+
htmlBody = "";
|
|
1612
|
+
// Location
|
|
1613
|
+
folderId = "";
|
|
1614
|
+
folderPath = "";
|
|
1615
|
+
labels = "";
|
|
1616
|
+
// JSON array (Gmail)
|
|
1617
|
+
flags = "";
|
|
1618
|
+
// JSON array (IMAP)
|
|
1619
|
+
// Email-specific status flags
|
|
1620
|
+
isAnswered = false;
|
|
1621
|
+
isDraft = false;
|
|
1622
|
+
// Raw data
|
|
1623
|
+
rawMessage = "";
|
|
1624
|
+
headers = "";
|
|
1625
|
+
// JSON object
|
|
1626
|
+
constructor(options = {}) {
|
|
1627
|
+
super(options);
|
|
1628
|
+
if (options.messageId !== void 0) this.messageId = options.messageId;
|
|
1629
|
+
if (options.inReplyTo !== void 0) this.inReplyTo = options.inReplyTo;
|
|
1630
|
+
if (options.ccAddresses !== void 0)
|
|
1631
|
+
this.ccAddresses = options.ccAddresses;
|
|
1632
|
+
if (options.bccAddresses !== void 0)
|
|
1633
|
+
this.bccAddresses = options.bccAddresses;
|
|
1634
|
+
if (options.replyToAddress !== void 0)
|
|
1635
|
+
this.replyToAddress = options.replyToAddress;
|
|
1636
|
+
if (options.replyToName !== void 0)
|
|
1637
|
+
this.replyToName = options.replyToName;
|
|
1638
|
+
if (options.textBody !== void 0) this.textBody = options.textBody;
|
|
1639
|
+
if (options.htmlBody !== void 0) this.htmlBody = options.htmlBody;
|
|
1640
|
+
if (options.folderId !== void 0) this.folderId = options.folderId;
|
|
1641
|
+
if (options.folderPath !== void 0) this.folderPath = options.folderPath;
|
|
1642
|
+
if (options.labels !== void 0) this.labels = options.labels;
|
|
1643
|
+
if (options.flags !== void 0) this.flags = options.flags;
|
|
1644
|
+
if (options.isAnswered !== void 0) this.isAnswered = options.isAnswered;
|
|
1645
|
+
if (options.isDraft !== void 0) this.isDraft = options.isDraft;
|
|
1646
|
+
if (options.rawMessage !== void 0) this.rawMessage = options.rawMessage;
|
|
1647
|
+
if (options.headers !== void 0) this.headers = options.headers;
|
|
1648
|
+
if (options.textBody && !options.body) {
|
|
1649
|
+
this.body = options.textBody;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Get CC addresses as parsed array
|
|
1654
|
+
*/
|
|
1655
|
+
getCcAddresses() {
|
|
1656
|
+
if (!this.ccAddresses) return [];
|
|
1657
|
+
try {
|
|
1658
|
+
return JSON.parse(this.ccAddresses);
|
|
1659
|
+
} catch {
|
|
1660
|
+
return [];
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Get BCC addresses as parsed array
|
|
1665
|
+
*/
|
|
1666
|
+
getBccAddresses() {
|
|
1667
|
+
if (!this.bccAddresses) return [];
|
|
1668
|
+
try {
|
|
1669
|
+
return JSON.parse(this.bccAddresses);
|
|
1670
|
+
} catch {
|
|
1671
|
+
return [];
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
/**
|
|
1675
|
+
* Get labels as parsed array
|
|
1676
|
+
*/
|
|
1677
|
+
getLabels() {
|
|
1678
|
+
if (!this.labels) return [];
|
|
1679
|
+
try {
|
|
1680
|
+
return JSON.parse(this.labels);
|
|
1681
|
+
} catch {
|
|
1682
|
+
return [];
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
/**
|
|
1686
|
+
* Set labels from array
|
|
1687
|
+
*/
|
|
1688
|
+
setLabels(labels) {
|
|
1689
|
+
this.labels = JSON.stringify(labels);
|
|
1690
|
+
}
|
|
1691
|
+
/**
|
|
1692
|
+
* Get flags as parsed array
|
|
1693
|
+
*/
|
|
1694
|
+
getFlags() {
|
|
1695
|
+
if (!this.flags) return [];
|
|
1696
|
+
try {
|
|
1697
|
+
return JSON.parse(this.flags);
|
|
1698
|
+
} catch {
|
|
1699
|
+
return [];
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Set flags from array
|
|
1704
|
+
*/
|
|
1705
|
+
setFlags(flags) {
|
|
1706
|
+
this.flags = JSON.stringify(flags);
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Get headers as parsed object
|
|
1710
|
+
*/
|
|
1711
|
+
getHeaders() {
|
|
1712
|
+
if (!this.headers) return {};
|
|
1713
|
+
try {
|
|
1714
|
+
return JSON.parse(this.headers);
|
|
1715
|
+
} catch {
|
|
1716
|
+
return {};
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* Set headers from object
|
|
1721
|
+
*/
|
|
1722
|
+
setHeaders(headers) {
|
|
1723
|
+
this.headers = JSON.stringify(headers);
|
|
1724
|
+
}
|
|
1725
|
+
/**
|
|
1726
|
+
* Get the email account (typed as EmailAccount)
|
|
1727
|
+
*/
|
|
1728
|
+
async getAccount() {
|
|
1729
|
+
if (!this.accountId) return null;
|
|
1730
|
+
const { EmailAccountCollection: EmailAccountCollection2 } = await Promise.resolve().then(() => EmailAccountCollection$1);
|
|
1731
|
+
const collection = await EmailAccountCollection2.create(
|
|
1732
|
+
this.options
|
|
1733
|
+
);
|
|
1734
|
+
return await collection.get({ id: this.accountId });
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Get the folder
|
|
1738
|
+
*/
|
|
1739
|
+
async getFolder() {
|
|
1740
|
+
if (!this.folderId) return null;
|
|
1741
|
+
const { EmailFolderCollection: EmailFolderCollection2 } = await Promise.resolve().then(() => EmailFolderCollection$1);
|
|
1742
|
+
const collection = await EmailFolderCollection2.create(
|
|
1743
|
+
this.options
|
|
1744
|
+
);
|
|
1745
|
+
return await collection.get({ id: this.folderId });
|
|
1746
|
+
}
|
|
1747
|
+
/**
|
|
1748
|
+
* Get emails in the same thread
|
|
1749
|
+
*/
|
|
1750
|
+
async getThreadEmails() {
|
|
1751
|
+
if (!this.threadId) return [this];
|
|
1752
|
+
const { EmailCollection: EmailCollection2 } = await Promise.resolve().then(() => EmailCollection$1);
|
|
1753
|
+
const collection = await EmailCollection2.create(this.options);
|
|
1754
|
+
return await collection.list({ where: { threadId: this.threadId } });
|
|
1755
|
+
}
|
|
1756
|
+
/**
|
|
1757
|
+
* Get a short preview of the email body
|
|
1758
|
+
*/
|
|
1759
|
+
getPreview(maxLength = 200) {
|
|
1760
|
+
const body = this.textBody || this.htmlBody?.replace(/<[^>]*>/g, "") || "";
|
|
1761
|
+
if (body.length <= maxLength) return body;
|
|
1762
|
+
return `${body.slice(0, maxLength)}...`;
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Get References header values as array
|
|
1766
|
+
*/
|
|
1767
|
+
getReferences() {
|
|
1768
|
+
const headers = this.getHeaders();
|
|
1769
|
+
const refs = headers.references;
|
|
1770
|
+
if (!refs) return [];
|
|
1771
|
+
if (Array.isArray(refs)) return refs;
|
|
1772
|
+
return refs.split(/\s+/).filter(Boolean);
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Create a reply to this email with RFC 822 threading
|
|
1776
|
+
*/
|
|
1777
|
+
createReply(options) {
|
|
1778
|
+
const reply = new Email({
|
|
1779
|
+
...this.options,
|
|
1780
|
+
id: void 0,
|
|
1781
|
+
accountId: this.accountId,
|
|
1782
|
+
threadId: this.threadId || this.id || void 0,
|
|
1783
|
+
subject: this.subject.startsWith("Re:") ? this.subject : `Re: ${this.subject}`,
|
|
1784
|
+
fromAddress: "",
|
|
1785
|
+
fromName: "",
|
|
1786
|
+
inReplyToMessageId: this.id || void 0,
|
|
1787
|
+
sendStatus: "draft",
|
|
1788
|
+
isRead: true,
|
|
1789
|
+
isDraft: true,
|
|
1790
|
+
date: null,
|
|
1791
|
+
createdAt: void 0,
|
|
1792
|
+
updatedAt: void 0,
|
|
1793
|
+
// RFC 822 threading
|
|
1794
|
+
inReplyTo: this.messageId,
|
|
1795
|
+
// To: original sender
|
|
1796
|
+
toAddresses: JSON.stringify([
|
|
1797
|
+
{ address: this.fromAddress, name: this.fromName }
|
|
1798
|
+
])
|
|
1799
|
+
});
|
|
1800
|
+
const refs = [...this.getReferences()];
|
|
1801
|
+
if (this.messageId && !refs.includes(this.messageId)) {
|
|
1802
|
+
refs.push(this.messageId);
|
|
1803
|
+
}
|
|
1804
|
+
reply.setHeaders({ references: refs.join(" ") });
|
|
1805
|
+
if (options?.replyAll) {
|
|
1806
|
+
const allRecipients = [
|
|
1807
|
+
...this.getToAddresses(),
|
|
1808
|
+
...this.getCcAddresses()
|
|
1809
|
+
];
|
|
1810
|
+
const seen = /* @__PURE__ */ new Set([this.fromAddress.toLowerCase()]);
|
|
1811
|
+
const ccAddresses = allRecipients.filter((r) => {
|
|
1812
|
+
const addr = r.address.toLowerCase();
|
|
1813
|
+
if (seen.has(addr)) return false;
|
|
1814
|
+
seen.add(addr);
|
|
1815
|
+
return true;
|
|
1816
|
+
});
|
|
1817
|
+
reply.ccAddresses = JSON.stringify(ccAddresses);
|
|
1818
|
+
}
|
|
1819
|
+
reply.body = this.buildQuotedBody();
|
|
1820
|
+
reply.textBody = reply.body;
|
|
1821
|
+
return reply;
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* Create a forward of this email
|
|
1825
|
+
*/
|
|
1826
|
+
createForward() {
|
|
1827
|
+
const forwardBody = this.buildForwardBody();
|
|
1828
|
+
const forward = new Email({
|
|
1829
|
+
...this.options,
|
|
1830
|
+
id: void 0,
|
|
1831
|
+
accountId: this.accountId,
|
|
1832
|
+
threadId: "",
|
|
1833
|
+
subject: this.subject.startsWith("Fwd:") ? this.subject : `Fwd: ${this.subject}`,
|
|
1834
|
+
toAddresses: "[]",
|
|
1835
|
+
ccAddresses: "[]",
|
|
1836
|
+
bccAddresses: "[]",
|
|
1837
|
+
fromAddress: "",
|
|
1838
|
+
fromName: "",
|
|
1839
|
+
body: forwardBody,
|
|
1840
|
+
textBody: forwardBody,
|
|
1841
|
+
hasAttachments: this.hasAttachments,
|
|
1842
|
+
inReplyToMessageId: "",
|
|
1843
|
+
inReplyTo: "",
|
|
1844
|
+
sendStatus: "draft",
|
|
1845
|
+
isDraft: true,
|
|
1846
|
+
isRead: true,
|
|
1847
|
+
date: null,
|
|
1848
|
+
createdAt: void 0,
|
|
1849
|
+
updatedAt: void 0
|
|
1850
|
+
});
|
|
1851
|
+
return forward;
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* Build email-specific quoted body for replies
|
|
1855
|
+
*/
|
|
1856
|
+
buildQuotedBody() {
|
|
1857
|
+
const dateStr = this.date ? this.date.toLocaleString() : "unknown date";
|
|
1858
|
+
const from = this.fromName ? `${this.fromName} <${this.fromAddress}>` : this.fromAddress;
|
|
1859
|
+
const bodyText = this.textBody || this.body || "";
|
|
1860
|
+
const quotedLines = bodyText.split("\n").map((line) => `> ${line}`).join("\n");
|
|
1861
|
+
return `
|
|
1862
|
+
|
|
1863
|
+
On ${dateStr}, ${from} wrote:
|
|
1864
|
+
${quotedLines}`;
|
|
1865
|
+
}
|
|
1866
|
+
/**
|
|
1867
|
+
* Build forwarded message body
|
|
1868
|
+
*/
|
|
1869
|
+
buildForwardBody() {
|
|
1870
|
+
const dateStr = this.date ? this.date.toLocaleString() : "unknown date";
|
|
1871
|
+
const from = this.fromName ? `${this.fromName} <${this.fromAddress}>` : this.fromAddress;
|
|
1872
|
+
const toStr = this.getToAddresses().map((r) => r.name ? `${r.name} <${r.address}>` : r.address).join(", ");
|
|
1873
|
+
const bodyText = this.textBody || this.body || "";
|
|
1874
|
+
return [
|
|
1875
|
+
"",
|
|
1876
|
+
"",
|
|
1877
|
+
"---------- Forwarded message ----------",
|
|
1878
|
+
`From: ${from}`,
|
|
1879
|
+
`Date: ${dateStr}`,
|
|
1880
|
+
`Subject: ${this.subject}`,
|
|
1881
|
+
`To: ${toStr}`,
|
|
1882
|
+
"",
|
|
1883
|
+
bodyText
|
|
1884
|
+
].join("\n");
|
|
1885
|
+
}
|
|
1886
|
+
};
|
|
1887
|
+
Email = __decorateClass$7([
|
|
1888
|
+
smrt({
|
|
1889
|
+
tableStrategy: "sti",
|
|
1890
|
+
api: { include: ["list", "get", "create", "update", "delete"] },
|
|
1891
|
+
mcp: { include: ["list", "get"] },
|
|
1892
|
+
cli: true
|
|
1893
|
+
})
|
|
1894
|
+
], Email);
|
|
1895
|
+
const Email$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1896
|
+
__proto__: null,
|
|
1897
|
+
get Email() {
|
|
1898
|
+
return Email;
|
|
1899
|
+
}
|
|
1900
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1901
|
+
class EmailCollection extends MessageCollection {
|
|
1902
|
+
static _itemClass = Email;
|
|
1903
|
+
/**
|
|
1904
|
+
* Get email by RFC 822 Message-ID
|
|
1905
|
+
*/
|
|
1906
|
+
async getByMessageId(accountId, messageId) {
|
|
1907
|
+
const emails = await this.list({
|
|
1908
|
+
where: { accountId, messageId },
|
|
1909
|
+
limit: 1
|
|
1910
|
+
});
|
|
1911
|
+
return emails[0] || null;
|
|
1912
|
+
}
|
|
1913
|
+
/**
|
|
1914
|
+
* Get emails by account
|
|
1915
|
+
*/
|
|
1916
|
+
async getByAccount(accountId) {
|
|
1917
|
+
return await this.list({ where: { accountId } });
|
|
1918
|
+
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Get emails by folder
|
|
1921
|
+
*/
|
|
1922
|
+
async getByFolder(folderId) {
|
|
1923
|
+
return await this.list({ where: { folderId } });
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Get emails by thread
|
|
1927
|
+
*/
|
|
1928
|
+
async getByThread(threadId) {
|
|
1929
|
+
return await this.list({ where: { threadId } });
|
|
1930
|
+
}
|
|
1931
|
+
/**
|
|
1932
|
+
* Get unread emails
|
|
1933
|
+
*/
|
|
1934
|
+
async getUnread(accountId) {
|
|
1935
|
+
const where = { isRead: false };
|
|
1936
|
+
if (accountId) {
|
|
1937
|
+
where.accountId = accountId;
|
|
1938
|
+
}
|
|
1939
|
+
return await this.list({ where });
|
|
1940
|
+
}
|
|
1941
|
+
/**
|
|
1942
|
+
* Get flagged emails
|
|
1943
|
+
*/
|
|
1944
|
+
async getFlagged(accountId) {
|
|
1945
|
+
const where = { isFlagged: true };
|
|
1946
|
+
if (accountId) {
|
|
1947
|
+
where.accountId = accountId;
|
|
1948
|
+
}
|
|
1949
|
+
return await this.list({ where });
|
|
1950
|
+
}
|
|
1951
|
+
/**
|
|
1952
|
+
* Get emails with attachments
|
|
1953
|
+
*/
|
|
1954
|
+
async getWithAttachments(accountId) {
|
|
1955
|
+
const where = { hasAttachments: true };
|
|
1956
|
+
if (accountId) {
|
|
1957
|
+
where.accountId = accountId;
|
|
1958
|
+
}
|
|
1959
|
+
return await this.list({ where });
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Get recent emails
|
|
1963
|
+
*/
|
|
1964
|
+
async getRecent(limit = 20, accountId) {
|
|
1965
|
+
const allEmails = await this.list({
|
|
1966
|
+
where: accountId ? { accountId } : void 0
|
|
1967
|
+
});
|
|
1968
|
+
return allEmails.sort((a, b) => {
|
|
1969
|
+
const dateA = a.date?.getTime() || 0;
|
|
1970
|
+
const dateB = b.date?.getTime() || 0;
|
|
1971
|
+
return dateB - dateA;
|
|
1972
|
+
}).slice(0, limit);
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Count emails in a folder
|
|
1976
|
+
*/
|
|
1977
|
+
async countByFolder(folderId) {
|
|
1978
|
+
const emails = await this.list({ where: { folderId } });
|
|
1979
|
+
return emails.length;
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Count unread emails in a folder
|
|
1983
|
+
*/
|
|
1984
|
+
async countUnreadByFolder(folderId) {
|
|
1985
|
+
const emails = await this.list({ where: { folderId, isRead: false } });
|
|
1986
|
+
return emails.length;
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Count unread emails for an account
|
|
1990
|
+
*/
|
|
1991
|
+
async countUnreadByAccount(accountId) {
|
|
1992
|
+
const emails = await this.list({ where: { accountId, isRead: false } });
|
|
1993
|
+
return emails.length;
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Search emails with email-specific filters.
|
|
1997
|
+
* Alias: `search()` for backward compatibility.
|
|
1998
|
+
*/
|
|
1999
|
+
async search(query, filters) {
|
|
2000
|
+
return this.searchEmails(query, filters);
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Search emails with email-specific filters
|
|
2004
|
+
*/
|
|
2005
|
+
async searchEmails(query, filters) {
|
|
2006
|
+
let emails = await this.list({});
|
|
2007
|
+
if (query) {
|
|
2008
|
+
const lowerQuery = query.toLowerCase();
|
|
2009
|
+
emails = emails.filter(
|
|
2010
|
+
(e) => e.subject?.toLowerCase().includes(lowerQuery) || e.textBody?.toLowerCase().includes(lowerQuery) || e.fromAddress?.toLowerCase().includes(lowerQuery) || e.fromName?.toLowerCase().includes(lowerQuery)
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
if (filters) {
|
|
2014
|
+
if (filters.accountId) {
|
|
2015
|
+
emails = emails.filter((e) => e.accountId === filters.accountId);
|
|
2016
|
+
}
|
|
2017
|
+
if (filters.folderId) {
|
|
2018
|
+
emails = emails.filter((e) => e.folderId === filters.folderId);
|
|
2019
|
+
}
|
|
2020
|
+
if (filters.threadId) {
|
|
2021
|
+
emails = emails.filter((e) => e.threadId === filters.threadId);
|
|
2022
|
+
}
|
|
2023
|
+
if (filters.from) {
|
|
2024
|
+
const fromLower = filters.from.toLowerCase();
|
|
2025
|
+
emails = emails.filter(
|
|
2026
|
+
(e) => e.fromAddress?.toLowerCase().includes(fromLower) || e.fromName?.toLowerCase().includes(fromLower)
|
|
2027
|
+
);
|
|
2028
|
+
}
|
|
2029
|
+
if (filters.to) {
|
|
2030
|
+
const toLower = filters.to.toLowerCase();
|
|
2031
|
+
emails = emails.filter(
|
|
2032
|
+
(e) => e.toAddresses?.toLowerCase().includes(toLower)
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
if (filters.subject) {
|
|
2036
|
+
const subjectLower = filters.subject.toLowerCase();
|
|
2037
|
+
emails = emails.filter(
|
|
2038
|
+
(e) => e.subject?.toLowerCase().includes(subjectLower)
|
|
2039
|
+
);
|
|
2040
|
+
}
|
|
2041
|
+
if (filters.isRead !== void 0) {
|
|
2042
|
+
emails = emails.filter((e) => e.isRead === filters.isRead);
|
|
2043
|
+
}
|
|
2044
|
+
if (filters.isFlagged !== void 0) {
|
|
2045
|
+
emails = emails.filter((e) => e.isFlagged === filters.isFlagged);
|
|
2046
|
+
}
|
|
2047
|
+
if (filters.hasAttachments !== void 0) {
|
|
2048
|
+
emails = emails.filter(
|
|
2049
|
+
(e) => e.hasAttachments === filters.hasAttachments
|
|
2050
|
+
);
|
|
2051
|
+
}
|
|
2052
|
+
if (filters.sincDate) {
|
|
2053
|
+
emails = emails.filter(
|
|
2054
|
+
(e) => e.date && e.date >= filters.sincDate
|
|
2055
|
+
);
|
|
2056
|
+
}
|
|
2057
|
+
if (filters.beforeDate) {
|
|
2058
|
+
emails = emails.filter(
|
|
2059
|
+
(e) => e.date && e.date < filters.beforeDate
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
return emails;
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* Mark all emails in a folder as read
|
|
2067
|
+
*/
|
|
2068
|
+
async markFolderRead(folderId) {
|
|
2069
|
+
const emails = await this.getUnread();
|
|
2070
|
+
const folderEmails = emails.filter((e) => e.folderId === folderId);
|
|
2071
|
+
for (const email of folderEmails) {
|
|
2072
|
+
await email.markRead();
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
/**
|
|
2076
|
+
* Delete emails by folder
|
|
2077
|
+
*/
|
|
2078
|
+
async deleteByFolder(folderId) {
|
|
2079
|
+
const emails = await this.getByFolder(folderId);
|
|
2080
|
+
let count = 0;
|
|
2081
|
+
for (const email of emails) {
|
|
2082
|
+
await email.delete();
|
|
2083
|
+
count++;
|
|
2084
|
+
}
|
|
2085
|
+
return count;
|
|
2086
|
+
}
|
|
2087
|
+
/**
|
|
2088
|
+
* Get email statistics for an account
|
|
2089
|
+
*/
|
|
2090
|
+
async getAccountStats(accountId) {
|
|
2091
|
+
const emails = await this.getByAccount(accountId);
|
|
2092
|
+
return {
|
|
2093
|
+
total: emails.length,
|
|
2094
|
+
unread: emails.filter((e) => !e.isRead).length,
|
|
2095
|
+
flagged: emails.filter((e) => e.isFlagged).length,
|
|
2096
|
+
withAttachments: emails.filter((e) => e.hasAttachments).length,
|
|
2097
|
+
byType: { Email: emails.length }
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
2101
|
+
// Tenant Helper Methods
|
|
2102
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
2103
|
+
async findByTenant(tenantId2) {
|
|
2104
|
+
return this.list({ where: { tenantId: tenantId2 } });
|
|
2105
|
+
}
|
|
2106
|
+
async findGlobal() {
|
|
2107
|
+
return this.list({ where: { tenantId: null } });
|
|
2108
|
+
}
|
|
2109
|
+
async findWithGlobals(tenantId2) {
|
|
2110
|
+
return this.query(
|
|
2111
|
+
`SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,
|
|
2112
|
+
[tenantId2]
|
|
2113
|
+
);
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
const EmailCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2117
|
+
__proto__: null,
|
|
2118
|
+
EmailCollection
|
|
2119
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
2120
|
+
var __defProp = Object.defineProperty;
|
|
2121
|
+
var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor;
|
|
2122
|
+
var __decorateClass$6 = (decorators, target, key, kind) => {
|
|
2123
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$6(target, key) : target;
|
|
2124
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
2125
|
+
if (decorator = decorators[i])
|
|
2126
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
2127
|
+
if (kind && result) __defProp(target, key, result);
|
|
2128
|
+
return result;
|
|
2129
|
+
};
|
|
2130
|
+
let EmailFolder = class extends SmrtObject {
|
|
2131
|
+
tenantId = null;
|
|
2132
|
+
accountId = "";
|
|
2133
|
+
name = "";
|
|
2134
|
+
path = "";
|
|
2135
|
+
delimiter = "/";
|
|
2136
|
+
specialUse = "";
|
|
2137
|
+
// '\\Inbox', '\\Sent', '\\Drafts', etc.
|
|
2138
|
+
messageCount = 0;
|
|
2139
|
+
unreadCount = 0;
|
|
2140
|
+
subscribed = true;
|
|
2141
|
+
// Timestamps
|
|
2142
|
+
createdAt = /* @__PURE__ */ new Date();
|
|
2143
|
+
updatedAt = /* @__PURE__ */ new Date();
|
|
2144
|
+
constructor(options = {}) {
|
|
2145
|
+
super(options);
|
|
2146
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
2147
|
+
if (options.accountId !== void 0) this.accountId = options.accountId;
|
|
2148
|
+
if (options.name !== void 0) this.name = options.name;
|
|
2149
|
+
if (options.path !== void 0) this.path = options.path;
|
|
2150
|
+
if (options.delimiter !== void 0) this.delimiter = options.delimiter;
|
|
2151
|
+
if (options.specialUse !== void 0) this.specialUse = options.specialUse;
|
|
2152
|
+
if (options.messageCount !== void 0)
|
|
2153
|
+
this.messageCount = options.messageCount;
|
|
2154
|
+
if (options.unreadCount !== void 0)
|
|
2155
|
+
this.unreadCount = options.unreadCount;
|
|
2156
|
+
if (options.subscribed !== void 0) this.subscribed = options.subscribed;
|
|
2157
|
+
if (options.createdAt) this.createdAt = options.createdAt;
|
|
2158
|
+
if (options.updatedAt) this.updatedAt = options.updatedAt;
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Get the email account
|
|
2162
|
+
*/
|
|
2163
|
+
async getAccount() {
|
|
2164
|
+
if (!this.accountId) return null;
|
|
2165
|
+
const { EmailAccountCollection: EmailAccountCollection2 } = await Promise.resolve().then(() => EmailAccountCollection$1);
|
|
2166
|
+
const collection = await EmailAccountCollection2.create(
|
|
2167
|
+
this.options
|
|
2168
|
+
);
|
|
2169
|
+
return await collection.get({ id: this.accountId });
|
|
2170
|
+
}
|
|
2171
|
+
/**
|
|
2172
|
+
* Get all emails in this folder
|
|
2173
|
+
*/
|
|
2174
|
+
async getEmails(limit) {
|
|
2175
|
+
const { EmailCollection: EmailCollection2 } = await Promise.resolve().then(() => EmailCollection$1);
|
|
2176
|
+
const collection = await EmailCollection2.create(this.options);
|
|
2177
|
+
const options = { where: { folderId: this.id } };
|
|
2178
|
+
if (limit) {
|
|
2179
|
+
options.limit = limit;
|
|
2180
|
+
}
|
|
2181
|
+
return await collection.list(options);
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Get unread emails in this folder
|
|
2185
|
+
*/
|
|
2186
|
+
async getUnreadEmails(limit) {
|
|
2187
|
+
const { EmailCollection: EmailCollection2 } = await Promise.resolve().then(() => EmailCollection$1);
|
|
2188
|
+
const collection = await EmailCollection2.create(this.options);
|
|
2189
|
+
const options = {
|
|
2190
|
+
where: { folderId: this.id, isRead: false }
|
|
2191
|
+
};
|
|
2192
|
+
if (limit) {
|
|
2193
|
+
options.limit = limit;
|
|
2194
|
+
}
|
|
2195
|
+
return await collection.list(options);
|
|
2196
|
+
}
|
|
2197
|
+
/**
|
|
2198
|
+
* Update message counts from database
|
|
2199
|
+
*/
|
|
2200
|
+
async refreshCounts() {
|
|
2201
|
+
const { EmailCollection: EmailCollection2 } = await Promise.resolve().then(() => EmailCollection$1);
|
|
2202
|
+
const collection = await EmailCollection2.create(this.options);
|
|
2203
|
+
this.messageCount = await collection.countByFolder(this.id);
|
|
2204
|
+
this.unreadCount = await collection.countUnreadByFolder(this.id);
|
|
2205
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
2206
|
+
await this.save();
|
|
2207
|
+
}
|
|
2208
|
+
/**
|
|
2209
|
+
* Check if this is the inbox folder
|
|
2210
|
+
*/
|
|
2211
|
+
isInbox() {
|
|
2212
|
+
return this.specialUse === "\\Inbox" || this.path.toLowerCase() === "inbox";
|
|
2213
|
+
}
|
|
2214
|
+
/**
|
|
2215
|
+
* Check if this is the sent folder
|
|
2216
|
+
*/
|
|
2217
|
+
isSent() {
|
|
2218
|
+
return this.specialUse === "\\Sent" || this.path.toLowerCase() === "sent";
|
|
2219
|
+
}
|
|
2220
|
+
/**
|
|
2221
|
+
* Check if this is the drafts folder
|
|
2222
|
+
*/
|
|
2223
|
+
isDrafts() {
|
|
2224
|
+
return this.specialUse === "\\Drafts" || this.path.toLowerCase() === "drafts";
|
|
2225
|
+
}
|
|
2226
|
+
/**
|
|
2227
|
+
* Check if this is the trash folder
|
|
2228
|
+
*/
|
|
2229
|
+
isTrash() {
|
|
2230
|
+
return this.specialUse === "\\Trash" || this.path.toLowerCase() === "trash";
|
|
2231
|
+
}
|
|
2232
|
+
/**
|
|
2233
|
+
* Check if this is the spam/junk folder
|
|
2234
|
+
*/
|
|
2235
|
+
isSpam() {
|
|
2236
|
+
return this.specialUse === "\\Junk" || this.path.toLowerCase() === "spam" || this.path.toLowerCase() === "junk";
|
|
2237
|
+
}
|
|
2238
|
+
/**
|
|
2239
|
+
* Check if this is a system folder
|
|
2240
|
+
*/
|
|
2241
|
+
isSystemFolder() {
|
|
2242
|
+
return !!this.specialUse;
|
|
2243
|
+
}
|
|
2244
|
+
/**
|
|
2245
|
+
* Subscribe to folder
|
|
2246
|
+
*/
|
|
2247
|
+
async subscribe() {
|
|
2248
|
+
this.subscribed = true;
|
|
2249
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
2250
|
+
await this.save();
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Unsubscribe from folder
|
|
2254
|
+
*/
|
|
2255
|
+
async unsubscribe() {
|
|
2256
|
+
this.subscribed = false;
|
|
2257
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
2258
|
+
await this.save();
|
|
2259
|
+
}
|
|
2260
|
+
};
|
|
2261
|
+
__decorateClass$6([
|
|
2262
|
+
tenantId({ nullable: true })
|
|
2263
|
+
], EmailFolder.prototype, "tenantId", 2);
|
|
2264
|
+
__decorateClass$6([
|
|
2265
|
+
foreignKey("Account")
|
|
2266
|
+
], EmailFolder.prototype, "accountId", 2);
|
|
2267
|
+
EmailFolder = __decorateClass$6([
|
|
2268
|
+
TenantScoped({ mode: "optional" }),
|
|
2269
|
+
smrt({
|
|
2270
|
+
api: { include: ["list", "get", "create", "update", "delete"] },
|
|
2271
|
+
mcp: { include: ["list", "get"] },
|
|
2272
|
+
cli: true
|
|
2273
|
+
})
|
|
2274
|
+
], EmailFolder);
|
|
2275
|
+
const EmailFolder$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2276
|
+
__proto__: null,
|
|
2277
|
+
get EmailFolder() {
|
|
2278
|
+
return EmailFolder;
|
|
2279
|
+
}
|
|
2280
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
2281
|
+
class EmailFolderCollection extends SmrtCollection {
|
|
2282
|
+
static _itemClass = EmailFolder;
|
|
2283
|
+
/**
|
|
2284
|
+
* Get folder by path for an account
|
|
2285
|
+
*/
|
|
2286
|
+
async getByPath(accountId, path) {
|
|
2287
|
+
const folders = await this.list({ where: { accountId, path } });
|
|
2288
|
+
return folders[0] || null;
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Get folders by account
|
|
2292
|
+
*/
|
|
2293
|
+
async getByAccount(accountId) {
|
|
2294
|
+
return await this.list({ where: { accountId } });
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Get inbox folder for an account
|
|
2298
|
+
*/
|
|
2299
|
+
async getInbox(accountId) {
|
|
2300
|
+
const folders = await this.getByAccount(accountId);
|
|
2301
|
+
return folders.find((f) => f.isInbox()) || null;
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Get sent folder for an account
|
|
2305
|
+
*/
|
|
2306
|
+
async getSent(accountId) {
|
|
2307
|
+
const folders = await this.getByAccount(accountId);
|
|
2308
|
+
return folders.find((f) => f.isSent()) || null;
|
|
2309
|
+
}
|
|
2310
|
+
/**
|
|
2311
|
+
* Get drafts folder for an account
|
|
2312
|
+
*/
|
|
2313
|
+
async getDrafts(accountId) {
|
|
2314
|
+
const folders = await this.getByAccount(accountId);
|
|
2315
|
+
return folders.find((f) => f.isDrafts()) || null;
|
|
2316
|
+
}
|
|
2317
|
+
/**
|
|
2318
|
+
* Get trash folder for an account
|
|
2319
|
+
*/
|
|
2320
|
+
async getTrash(accountId) {
|
|
2321
|
+
const folders = await this.getByAccount(accountId);
|
|
2322
|
+
return folders.find((f) => f.isTrash()) || null;
|
|
2323
|
+
}
|
|
2324
|
+
/**
|
|
2325
|
+
* Get spam folder for an account
|
|
2326
|
+
*/
|
|
2327
|
+
async getSpam(accountId) {
|
|
2328
|
+
const folders = await this.getByAccount(accountId);
|
|
2329
|
+
return folders.find((f) => f.isSpam()) || null;
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2332
|
+
* Get system folders for an account
|
|
2333
|
+
*/
|
|
2334
|
+
async getSystemFolders(accountId) {
|
|
2335
|
+
const folders = await this.getByAccount(accountId);
|
|
2336
|
+
return folders.filter((f) => f.isSystemFolder());
|
|
2337
|
+
}
|
|
2338
|
+
/**
|
|
2339
|
+
* Get user-created folders for an account
|
|
2340
|
+
*/
|
|
2341
|
+
async getUserFolders(accountId) {
|
|
2342
|
+
const folders = await this.getByAccount(accountId);
|
|
2343
|
+
return folders.filter((f) => !f.isSystemFolder());
|
|
2344
|
+
}
|
|
2345
|
+
/**
|
|
2346
|
+
* Get subscribed folders
|
|
2347
|
+
*/
|
|
2348
|
+
async getSubscribed(accountId) {
|
|
2349
|
+
const where = { subscribed: true };
|
|
2350
|
+
if (accountId) {
|
|
2351
|
+
where.accountId = accountId;
|
|
2352
|
+
}
|
|
2353
|
+
return await this.list({ where });
|
|
2354
|
+
}
|
|
2355
|
+
/**
|
|
2356
|
+
* Get folders with unread messages
|
|
2357
|
+
*/
|
|
2358
|
+
async getWithUnread(accountId) {
|
|
2359
|
+
const folders = accountId ? await this.getByAccount(accountId) : await this.list({});
|
|
2360
|
+
return folders.filter((f) => f.unreadCount > 0);
|
|
2361
|
+
}
|
|
2362
|
+
/**
|
|
2363
|
+
* Search folders with filters
|
|
2364
|
+
*/
|
|
2365
|
+
async search(query, filters) {
|
|
2366
|
+
let folders = await this.list({});
|
|
2367
|
+
if (query) {
|
|
2368
|
+
const lowerQuery = query.toLowerCase();
|
|
2369
|
+
folders = folders.filter(
|
|
2370
|
+
(f) => f.name?.toLowerCase().includes(lowerQuery) || f.path?.toLowerCase().includes(lowerQuery)
|
|
2371
|
+
);
|
|
2372
|
+
}
|
|
2373
|
+
if (filters) {
|
|
2374
|
+
if (filters.accountId) {
|
|
2375
|
+
folders = folders.filter((f) => f.accountId === filters.accountId);
|
|
2376
|
+
}
|
|
2377
|
+
if (filters.specialUse) {
|
|
2378
|
+
folders = folders.filter((f) => f.specialUse === filters.specialUse);
|
|
2379
|
+
}
|
|
2380
|
+
if (filters.subscribed !== void 0) {
|
|
2381
|
+
folders = folders.filter((f) => f.subscribed === filters.subscribed);
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
return folders;
|
|
2385
|
+
}
|
|
2386
|
+
/**
|
|
2387
|
+
* Refresh counts for all folders in an account
|
|
2388
|
+
*/
|
|
2389
|
+
async refreshAllCounts(accountId) {
|
|
2390
|
+
const folders = await this.getByAccount(accountId);
|
|
2391
|
+
for (const folder of folders) {
|
|
2392
|
+
await folder.refreshCounts();
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
/**
|
|
2396
|
+
* Get folder statistics for an account
|
|
2397
|
+
*/
|
|
2398
|
+
async getAccountStats(accountId) {
|
|
2399
|
+
const folders = await this.getByAccount(accountId);
|
|
2400
|
+
return {
|
|
2401
|
+
totalFolders: folders.length,
|
|
2402
|
+
totalMessages: folders.reduce((sum, f) => sum + f.messageCount, 0),
|
|
2403
|
+
totalUnread: folders.reduce((sum, f) => sum + f.unreadCount, 0),
|
|
2404
|
+
systemFolders: folders.filter((f) => f.isSystemFolder()).length,
|
|
2405
|
+
userFolders: folders.filter((f) => !f.isSystemFolder()).length
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
/**
|
|
2409
|
+
* Create standard system folders for an account
|
|
2410
|
+
*/
|
|
2411
|
+
async createSystemFolders(accountId) {
|
|
2412
|
+
const standardFolders = [
|
|
2413
|
+
{ name: "INBOX", path: "INBOX", specialUse: "\\Inbox" },
|
|
2414
|
+
{ name: "Sent", path: "Sent", specialUse: "\\Sent" },
|
|
2415
|
+
{ name: "Drafts", path: "Drafts", specialUse: "\\Drafts" },
|
|
2416
|
+
{ name: "Trash", path: "Trash", specialUse: "\\Trash" },
|
|
2417
|
+
{ name: "Spam", path: "Spam", specialUse: "\\Junk" }
|
|
2418
|
+
];
|
|
2419
|
+
for (const folderData of standardFolders) {
|
|
2420
|
+
const existing = await this.getByPath(accountId, folderData.path);
|
|
2421
|
+
if (!existing) {
|
|
2422
|
+
const folder = await this.create({
|
|
2423
|
+
accountId,
|
|
2424
|
+
...folderData
|
|
2425
|
+
});
|
|
2426
|
+
await folder.save();
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
2431
|
+
// Tenant Helper Methods
|
|
2432
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
2433
|
+
/**
|
|
2434
|
+
* Find all email folders belonging to a specific tenant
|
|
2435
|
+
*/
|
|
2436
|
+
async findByTenant(tenantId2) {
|
|
2437
|
+
return this.list({ where: { tenantId: tenantId2 } });
|
|
2438
|
+
}
|
|
2439
|
+
/**
|
|
2440
|
+
* Find all global email folders (no tenant)
|
|
2441
|
+
*/
|
|
2442
|
+
async findGlobal() {
|
|
2443
|
+
return this.list({ where: { tenantId: null } });
|
|
2444
|
+
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Find email folders for a tenant including global folders
|
|
2447
|
+
*/
|
|
2448
|
+
async findWithGlobals(tenantId2) {
|
|
2449
|
+
return this.query(
|
|
2450
|
+
`SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,
|
|
2451
|
+
[tenantId2]
|
|
2452
|
+
);
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
const EmailFolderCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2456
|
+
__proto__: null,
|
|
2457
|
+
EmailFolderCollection
|
|
2458
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
2459
|
+
var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
|
|
2460
|
+
var __decorateClass$5 = (decorators, target, key, kind) => {
|
|
2461
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
|
|
2462
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
2463
|
+
if (decorator = decorators[i])
|
|
2464
|
+
result = decorator(result) || result;
|
|
2465
|
+
return result;
|
|
2466
|
+
};
|
|
2467
|
+
let Blacklist = class extends SmrtObject {
|
|
2468
|
+
pattern = "";
|
|
2469
|
+
type = "email";
|
|
2470
|
+
reason = "";
|
|
2471
|
+
autoArchive = true;
|
|
2472
|
+
constructor(options = {}) {
|
|
2473
|
+
super(options);
|
|
2474
|
+
if (options.pattern !== void 0) {
|
|
2475
|
+
this.pattern = options.pattern;
|
|
2476
|
+
}
|
|
2477
|
+
if (options.type !== void 0) {
|
|
2478
|
+
this.type = options.type;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
/**
|
|
2482
|
+
* Check if an email address matches this blacklist entry
|
|
2483
|
+
*/
|
|
2484
|
+
matches(email) {
|
|
2485
|
+
const normalizedEmail = email.toLowerCase().trim();
|
|
2486
|
+
switch (this.type) {
|
|
2487
|
+
case "email":
|
|
2488
|
+
return normalizedEmail === this.pattern.toLowerCase().trim();
|
|
2489
|
+
case "domain": {
|
|
2490
|
+
const domain = normalizedEmail.split("@")[1];
|
|
2491
|
+
return domain === this.pattern.toLowerCase().trim();
|
|
2492
|
+
}
|
|
2493
|
+
case "regex": {
|
|
2494
|
+
const pattern = this.pattern.trim();
|
|
2495
|
+
if (!pattern) {
|
|
2496
|
+
return false;
|
|
2497
|
+
}
|
|
2498
|
+
try {
|
|
2499
|
+
const regex = new RegExp(pattern, "i");
|
|
2500
|
+
return regex.test(normalizedEmail);
|
|
2501
|
+
} catch {
|
|
2502
|
+
return false;
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
default:
|
|
2506
|
+
return false;
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
};
|
|
2510
|
+
Blacklist = __decorateClass$5([
|
|
2511
|
+
smrt({
|
|
2512
|
+
api: { include: ["list", "get", "create", "update", "delete"] },
|
|
2513
|
+
cli: true,
|
|
2514
|
+
tenantScoped: true
|
|
2515
|
+
})
|
|
2516
|
+
], Blacklist);
|
|
2517
|
+
class BlacklistCollection extends SmrtCollection {
|
|
2518
|
+
static _itemClass = Blacklist;
|
|
2519
|
+
/**
|
|
2520
|
+
* Check if an email is blacklisted
|
|
2521
|
+
*/
|
|
2522
|
+
async isBlacklisted(email) {
|
|
2523
|
+
const entries = await this.list({});
|
|
2524
|
+
for (const entry of entries) {
|
|
2525
|
+
if (entry.matches(email)) {
|
|
2526
|
+
return true;
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
return false;
|
|
2530
|
+
}
|
|
2531
|
+
/**
|
|
2532
|
+
* Get the matching blacklist entry for an email
|
|
2533
|
+
*/
|
|
2534
|
+
async getMatchingEntry(email) {
|
|
2535
|
+
const entries = await this.list({});
|
|
2536
|
+
for (const entry of entries) {
|
|
2537
|
+
if (entry.matches(email)) {
|
|
2538
|
+
return entry;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
return null;
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
|
|
2545
|
+
var __decorateClass$4 = (decorators, target, key, kind) => {
|
|
2546
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
|
|
2547
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
2548
|
+
if (decorator = decorators[i])
|
|
2549
|
+
result = decorator(result) || result;
|
|
2550
|
+
return result;
|
|
2551
|
+
};
|
|
2552
|
+
let Whitelist = class extends SmrtObject {
|
|
2553
|
+
pattern = "";
|
|
2554
|
+
type = "email";
|
|
2555
|
+
category = null;
|
|
2556
|
+
description = "";
|
|
2557
|
+
constructor(options = {}) {
|
|
2558
|
+
super(options);
|
|
2559
|
+
if (options.pattern !== void 0) {
|
|
2560
|
+
this.pattern = options.pattern;
|
|
2561
|
+
}
|
|
2562
|
+
if (options.type !== void 0) {
|
|
2563
|
+
this.type = options.type;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
/**
|
|
2567
|
+
* Check if an email address matches this whitelist entry
|
|
2568
|
+
*/
|
|
2569
|
+
matches(email) {
|
|
2570
|
+
const normalizedEmail = email.toLowerCase().trim();
|
|
2571
|
+
switch (this.type) {
|
|
2572
|
+
case "email":
|
|
2573
|
+
return normalizedEmail === this.pattern.toLowerCase().trim();
|
|
2574
|
+
case "domain": {
|
|
2575
|
+
const domain = normalizedEmail.split("@")[1];
|
|
2576
|
+
return domain === this.pattern.toLowerCase().trim();
|
|
2577
|
+
}
|
|
2578
|
+
case "regex": {
|
|
2579
|
+
const pattern = this.pattern.trim();
|
|
2580
|
+
if (!pattern) {
|
|
2581
|
+
return false;
|
|
2582
|
+
}
|
|
2583
|
+
try {
|
|
2584
|
+
const regex = new RegExp(pattern, "i");
|
|
2585
|
+
return regex.test(normalizedEmail);
|
|
2586
|
+
} catch {
|
|
2587
|
+
return false;
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
default:
|
|
2591
|
+
return false;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
};
|
|
2595
|
+
Whitelist = __decorateClass$4([
|
|
2596
|
+
smrt({
|
|
2597
|
+
api: { include: ["list", "get", "create", "update", "delete"] },
|
|
2598
|
+
cli: true,
|
|
2599
|
+
tenantScoped: true
|
|
2600
|
+
})
|
|
2601
|
+
], Whitelist);
|
|
2602
|
+
class WhitelistCollection extends SmrtCollection {
|
|
2603
|
+
static _itemClass = Whitelist;
|
|
2604
|
+
/**
|
|
2605
|
+
* Check if an email is whitelisted for a specific category
|
|
2606
|
+
*/
|
|
2607
|
+
async isWhitelisted(email, category) {
|
|
2608
|
+
const entries = await this.list({});
|
|
2609
|
+
for (const entry of entries) {
|
|
2610
|
+
if (!entry.matches(email)) {
|
|
2611
|
+
continue;
|
|
2612
|
+
}
|
|
2613
|
+
if (!category) {
|
|
2614
|
+
return true;
|
|
2615
|
+
}
|
|
2616
|
+
if (entry.category === category || entry.category === null) {
|
|
2617
|
+
return true;
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
return false;
|
|
2621
|
+
}
|
|
2622
|
+
/**
|
|
2623
|
+
* Get the matching whitelist entry for an email
|
|
2624
|
+
*/
|
|
2625
|
+
async getMatchingEntry(email) {
|
|
2626
|
+
const entries = await this.list({});
|
|
2627
|
+
for (const entry of entries) {
|
|
2628
|
+
if (entry.matches(email)) {
|
|
2629
|
+
return entry;
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
return null;
|
|
2633
|
+
}
|
|
2634
|
+
/**
|
|
2635
|
+
* Get whitelist entries by category
|
|
2636
|
+
*/
|
|
2637
|
+
async getByCategory(category) {
|
|
2638
|
+
return await this.list({
|
|
2639
|
+
where: { category }
|
|
2640
|
+
});
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
|
|
2644
|
+
var __decorateClass$3 = (decorators, target, key, kind) => {
|
|
2645
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
|
|
2646
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
2647
|
+
if (decorator = decorators[i])
|
|
2648
|
+
result = decorator(result) || result;
|
|
2649
|
+
return result;
|
|
2650
|
+
};
|
|
2651
|
+
let SlackAccount = class extends Account {
|
|
2652
|
+
workspaceId = "";
|
|
2653
|
+
workspaceName = "";
|
|
2654
|
+
botUserId = "";
|
|
2655
|
+
constructor(options = {}) {
|
|
2656
|
+
super(options);
|
|
2657
|
+
if (options.workspaceId !== void 0)
|
|
2658
|
+
this.workspaceId = options.workspaceId;
|
|
2659
|
+
if (options.workspaceName !== void 0)
|
|
2660
|
+
this.workspaceName = options.workspaceName;
|
|
2661
|
+
if (options.botUserId !== void 0) this.botUserId = options.botUserId;
|
|
2662
|
+
if (!this.providerType) this.providerType = "slack";
|
|
2663
|
+
}
|
|
2664
|
+
/**
|
|
2665
|
+
* Create a sender for this Slack account
|
|
2666
|
+
*/
|
|
2667
|
+
async createSender() {
|
|
2668
|
+
const { SlackSender: SlackSender2 } = await Promise.resolve().then(() => SlackSender$1);
|
|
2669
|
+
return new SlackSender2(this);
|
|
2670
|
+
}
|
|
2671
|
+
};
|
|
2672
|
+
SlackAccount = __decorateClass$3([
|
|
2673
|
+
smrt({
|
|
2674
|
+
tableStrategy: "sti",
|
|
2675
|
+
api: { include: ["list", "get", "create", "update", "delete"] },
|
|
2676
|
+
mcp: { include: ["list", "get"] },
|
|
2677
|
+
cli: true
|
|
2678
|
+
})
|
|
2679
|
+
], SlackAccount);
|
|
2680
|
+
var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
|
|
2681
|
+
var __decorateClass$2 = (decorators, target, key, kind) => {
|
|
2682
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
|
|
2683
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
2684
|
+
if (decorator = decorators[i])
|
|
2685
|
+
result = decorator(result) || result;
|
|
2686
|
+
return result;
|
|
2687
|
+
};
|
|
2688
|
+
let SlackMessage = class extends Message {
|
|
2689
|
+
channelId = "";
|
|
2690
|
+
channelName = "";
|
|
2691
|
+
slackTs = "";
|
|
2692
|
+
// Slack message timestamp (unique ID)
|
|
2693
|
+
slackThreadTs = "";
|
|
2694
|
+
// Thread parent timestamp
|
|
2695
|
+
reactions = "";
|
|
2696
|
+
// JSON array of {name, count, users[]}
|
|
2697
|
+
isEdited = false;
|
|
2698
|
+
messageType = "";
|
|
2699
|
+
// 'message', 'bot_message', etc.
|
|
2700
|
+
blocks = "";
|
|
2701
|
+
// JSON - Slack Block Kit blocks
|
|
2702
|
+
constructor(options = {}) {
|
|
2703
|
+
super(options);
|
|
2704
|
+
if (options.channelId !== void 0) this.channelId = options.channelId;
|
|
2705
|
+
if (options.channelName !== void 0)
|
|
2706
|
+
this.channelName = options.channelName;
|
|
2707
|
+
if (options.slackTs !== void 0) this.slackTs = options.slackTs;
|
|
2708
|
+
if (options.slackThreadTs !== void 0)
|
|
2709
|
+
this.slackThreadTs = options.slackThreadTs;
|
|
2710
|
+
if (options.reactions !== void 0) this.reactions = options.reactions;
|
|
2711
|
+
if (options.isEdited !== void 0) this.isEdited = options.isEdited;
|
|
2712
|
+
if (options.messageType !== void 0)
|
|
2713
|
+
this.messageType = options.messageType;
|
|
2714
|
+
if (options.blocks !== void 0) this.blocks = options.blocks;
|
|
2715
|
+
}
|
|
2716
|
+
/**
|
|
2717
|
+
* Get reactions as parsed array
|
|
2718
|
+
*/
|
|
2719
|
+
getReactions() {
|
|
2720
|
+
if (!this.reactions) return [];
|
|
2721
|
+
try {
|
|
2722
|
+
return JSON.parse(this.reactions);
|
|
2723
|
+
} catch {
|
|
2724
|
+
return [];
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
/**
|
|
2728
|
+
* Get blocks as parsed array
|
|
2729
|
+
*/
|
|
2730
|
+
getBlocks() {
|
|
2731
|
+
if (!this.blocks) return [];
|
|
2732
|
+
try {
|
|
2733
|
+
return JSON.parse(this.blocks);
|
|
2734
|
+
} catch {
|
|
2735
|
+
return [];
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
/**
|
|
2739
|
+
* Check if message is in a thread
|
|
2740
|
+
*/
|
|
2741
|
+
isInThread() {
|
|
2742
|
+
return !!this.slackThreadTs && this.slackThreadTs !== this.slackTs;
|
|
2743
|
+
}
|
|
2744
|
+
/**
|
|
2745
|
+
* Get total reaction count
|
|
2746
|
+
*/
|
|
2747
|
+
getTotalReactions() {
|
|
2748
|
+
return this.getReactions().reduce((sum, r) => sum + r.count, 0);
|
|
2749
|
+
}
|
|
2750
|
+
};
|
|
2751
|
+
SlackMessage = __decorateClass$2([
|
|
2752
|
+
smrt({
|
|
2753
|
+
tableStrategy: "sti",
|
|
2754
|
+
api: { include: ["list", "get", "create"] },
|
|
2755
|
+
mcp: { include: ["list", "get"] },
|
|
2756
|
+
cli: true
|
|
2757
|
+
})
|
|
2758
|
+
], SlackMessage);
|
|
2759
|
+
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
2760
|
+
var __decorateClass$1 = (decorators, target, key, kind) => {
|
|
2761
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
|
|
2762
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
2763
|
+
if (decorator = decorators[i])
|
|
2764
|
+
result = decorator(result) || result;
|
|
2765
|
+
return result;
|
|
2766
|
+
};
|
|
2767
|
+
let Tweet = class extends Message {
|
|
2768
|
+
tweetId = "";
|
|
2769
|
+
retweetCount = 0;
|
|
2770
|
+
likeCount = 0;
|
|
2771
|
+
replyCount = 0;
|
|
2772
|
+
isRetweet = false;
|
|
2773
|
+
isReply = false;
|
|
2774
|
+
mediaUrls = "";
|
|
2775
|
+
// JSON array
|
|
2776
|
+
hashtags = "";
|
|
2777
|
+
// JSON array
|
|
2778
|
+
mentions = "";
|
|
2779
|
+
// JSON array
|
|
2780
|
+
constructor(options = {}) {
|
|
2781
|
+
super(options);
|
|
2782
|
+
if (options.tweetId !== void 0) this.tweetId = options.tweetId;
|
|
2783
|
+
if (options.retweetCount !== void 0)
|
|
2784
|
+
this.retweetCount = options.retweetCount;
|
|
2785
|
+
if (options.likeCount !== void 0) this.likeCount = options.likeCount;
|
|
2786
|
+
if (options.replyCount !== void 0) this.replyCount = options.replyCount;
|
|
2787
|
+
if (options.isRetweet !== void 0) this.isRetweet = options.isRetweet;
|
|
2788
|
+
if (options.isReply !== void 0) this.isReply = options.isReply;
|
|
2789
|
+
if (options.mediaUrls !== void 0) this.mediaUrls = options.mediaUrls;
|
|
2790
|
+
if (options.hashtags !== void 0) this.hashtags = options.hashtags;
|
|
2791
|
+
if (options.mentions !== void 0) this.mentions = options.mentions;
|
|
2792
|
+
}
|
|
2793
|
+
/**
|
|
2794
|
+
* Get media URLs as parsed array
|
|
2795
|
+
*/
|
|
2796
|
+
getMediaUrls() {
|
|
2797
|
+
if (!this.mediaUrls) return [];
|
|
2798
|
+
try {
|
|
2799
|
+
return JSON.parse(this.mediaUrls);
|
|
2800
|
+
} catch {
|
|
2801
|
+
return [];
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
/**
|
|
2805
|
+
* Get hashtags as parsed array
|
|
2806
|
+
*/
|
|
2807
|
+
getHashtags() {
|
|
2808
|
+
if (!this.hashtags) return [];
|
|
2809
|
+
try {
|
|
2810
|
+
return JSON.parse(this.hashtags);
|
|
2811
|
+
} catch {
|
|
2812
|
+
return [];
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
/**
|
|
2816
|
+
* Get mentions as parsed array
|
|
2817
|
+
*/
|
|
2818
|
+
getMentions() {
|
|
2819
|
+
if (!this.mentions) return [];
|
|
2820
|
+
try {
|
|
2821
|
+
return JSON.parse(this.mentions);
|
|
2822
|
+
} catch {
|
|
2823
|
+
return [];
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
/**
|
|
2827
|
+
* Get total engagement count
|
|
2828
|
+
*/
|
|
2829
|
+
getEngagement() {
|
|
2830
|
+
return this.retweetCount + this.likeCount + this.replyCount;
|
|
2831
|
+
}
|
|
2832
|
+
};
|
|
2833
|
+
Tweet = __decorateClass$1([
|
|
2834
|
+
smrt({
|
|
2835
|
+
tableStrategy: "sti",
|
|
2836
|
+
api: { include: ["list", "get", "create"] },
|
|
2837
|
+
mcp: { include: ["list", "get"] },
|
|
2838
|
+
cli: true
|
|
2839
|
+
})
|
|
2840
|
+
], Tweet);
|
|
2841
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
2842
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
2843
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
2844
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
2845
|
+
if (decorator = decorators[i])
|
|
2846
|
+
result = decorator(result) || result;
|
|
2847
|
+
return result;
|
|
2848
|
+
};
|
|
2849
|
+
let TwitterAccount = class extends Account {
|
|
2850
|
+
handle = "";
|
|
2851
|
+
twitterUserId = "";
|
|
2852
|
+
constructor(options = {}) {
|
|
2853
|
+
super(options);
|
|
2854
|
+
if (options.handle !== void 0) this.handle = options.handle;
|
|
2855
|
+
if (options.twitterUserId !== void 0)
|
|
2856
|
+
this.twitterUserId = options.twitterUserId;
|
|
2857
|
+
if (!this.providerType) this.providerType = "twitter";
|
|
2858
|
+
}
|
|
2859
|
+
/**
|
|
2860
|
+
* Create a sender for this Twitter account
|
|
2861
|
+
*/
|
|
2862
|
+
async createSender() {
|
|
2863
|
+
const { TweetSender: TweetSender2 } = await Promise.resolve().then(() => TweetSender$1);
|
|
2864
|
+
return new TweetSender2(this);
|
|
2865
|
+
}
|
|
2866
|
+
};
|
|
2867
|
+
TwitterAccount = __decorateClass([
|
|
2868
|
+
smrt({
|
|
2869
|
+
tableStrategy: "sti",
|
|
2870
|
+
api: { include: ["list", "get", "create", "update", "delete"] },
|
|
2871
|
+
mcp: { include: ["list", "get"] },
|
|
2872
|
+
cli: true
|
|
2873
|
+
})
|
|
2874
|
+
], TwitterAccount);
|
|
2875
|
+
class EmailSender {
|
|
2876
|
+
providerType = "email";
|
|
2877
|
+
client;
|
|
2878
|
+
account;
|
|
2879
|
+
constructor(client, account) {
|
|
2880
|
+
this.client = client;
|
|
2881
|
+
this.account = account;
|
|
2882
|
+
}
|
|
2883
|
+
isReady() {
|
|
2884
|
+
return this.client.isConnected();
|
|
2885
|
+
}
|
|
2886
|
+
async send(message, _options) {
|
|
2887
|
+
const toAddresses = message.getToAddresses();
|
|
2888
|
+
const ccAddresses = message.getCcAddresses();
|
|
2889
|
+
const bccAddresses = message.getBccAddresses();
|
|
2890
|
+
const emailMessage = {
|
|
2891
|
+
from: {
|
|
2892
|
+
name: message.fromName || this.account.name,
|
|
2893
|
+
address: message.fromAddress || this.account.email
|
|
2894
|
+
},
|
|
2895
|
+
to: toAddresses.map((r) => ({
|
|
2896
|
+
name: r.name,
|
|
2897
|
+
address: r.address
|
|
2898
|
+
})),
|
|
2899
|
+
cc: ccAddresses.length > 0 ? ccAddresses.map((r) => ({ name: r.name, address: r.address })) : void 0,
|
|
2900
|
+
bcc: bccAddresses.length > 0 ? bccAddresses.map((r) => ({ name: r.name, address: r.address })) : void 0,
|
|
2901
|
+
subject: message.subject,
|
|
2902
|
+
text: message.textBody || message.body,
|
|
2903
|
+
html: message.htmlBody || void 0,
|
|
2904
|
+
inReplyTo: message.inReplyTo || void 0
|
|
2905
|
+
};
|
|
2906
|
+
try {
|
|
2907
|
+
const result = await this.client.send(emailMessage);
|
|
2908
|
+
return {
|
|
2909
|
+
success: true,
|
|
2910
|
+
providerMessageId: result.messageId,
|
|
2911
|
+
accepted: result.accepted,
|
|
2912
|
+
rejected: result.rejected,
|
|
2913
|
+
providerResponse: { response: result.response },
|
|
2914
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
2915
|
+
};
|
|
2916
|
+
} catch (error) {
|
|
2917
|
+
return {
|
|
2918
|
+
success: false,
|
|
2919
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2920
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
const EmailSender$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2926
|
+
__proto__: null,
|
|
2927
|
+
EmailSender
|
|
2928
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
2929
|
+
class SlackSender {
|
|
2930
|
+
providerType = "slack";
|
|
2931
|
+
account;
|
|
2932
|
+
constructor(account) {
|
|
2933
|
+
this.account = account;
|
|
2934
|
+
}
|
|
2935
|
+
isReady() {
|
|
2936
|
+
return this.account.isActive;
|
|
2937
|
+
}
|
|
2938
|
+
async send(message, _options) {
|
|
2939
|
+
const { getMessageClient } = await import("@happyvertical/messages");
|
|
2940
|
+
const credentials = await this.account.getCredentials();
|
|
2941
|
+
if (!credentials?.botToken) {
|
|
2942
|
+
return {
|
|
2943
|
+
success: false,
|
|
2944
|
+
error: "No botToken found in account credentials",
|
|
2945
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
2946
|
+
};
|
|
2947
|
+
}
|
|
2948
|
+
const client = await getMessageClient({
|
|
2949
|
+
type: "slack",
|
|
2950
|
+
botToken: credentials.botToken
|
|
2951
|
+
});
|
|
2952
|
+
try {
|
|
2953
|
+
await client.connect();
|
|
2954
|
+
const result = await client.send(
|
|
2955
|
+
{
|
|
2956
|
+
from: { id: this.account.botUserId, name: this.account.name },
|
|
2957
|
+
channelId: message.channelId,
|
|
2958
|
+
content: message.body
|
|
2959
|
+
},
|
|
2960
|
+
{
|
|
2961
|
+
replyTo: message.slackThreadTs || void 0
|
|
2962
|
+
}
|
|
2963
|
+
);
|
|
2964
|
+
await client.disconnect();
|
|
2965
|
+
return {
|
|
2966
|
+
success: result.success,
|
|
2967
|
+
providerMessageId: result.messageId,
|
|
2968
|
+
providerResponse: result.providerResponse,
|
|
2969
|
+
sentAt: result.timestamp
|
|
2970
|
+
};
|
|
2971
|
+
} catch (error) {
|
|
2972
|
+
return {
|
|
2973
|
+
success: false,
|
|
2974
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2975
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
2976
|
+
};
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
const SlackSender$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2981
|
+
__proto__: null,
|
|
2982
|
+
SlackSender
|
|
2983
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
2984
|
+
class TweetSender {
|
|
2985
|
+
providerType = "twitter";
|
|
2986
|
+
account;
|
|
2987
|
+
constructor(account) {
|
|
2988
|
+
this.account = account;
|
|
2989
|
+
}
|
|
2990
|
+
isReady() {
|
|
2991
|
+
return this.account.isActive;
|
|
2992
|
+
}
|
|
2993
|
+
async send(message, _options) {
|
|
2994
|
+
const { getMessageClient } = await import("@happyvertical/messages");
|
|
2995
|
+
const credentials = await this.account.getCredentials();
|
|
2996
|
+
if (!credentials?.apiKey || !credentials?.apiSecret || !credentials?.accessToken || !credentials?.accessSecret) {
|
|
2997
|
+
return {
|
|
2998
|
+
success: false,
|
|
2999
|
+
error: "Missing Twitter API credentials in account",
|
|
3000
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
3001
|
+
};
|
|
3002
|
+
}
|
|
3003
|
+
const client = await getMessageClient({
|
|
3004
|
+
type: "twitter",
|
|
3005
|
+
apiKey: credentials.apiKey,
|
|
3006
|
+
apiSecret: credentials.apiSecret,
|
|
3007
|
+
accessToken: credentials.accessToken,
|
|
3008
|
+
accessSecret: credentials.accessSecret
|
|
3009
|
+
});
|
|
3010
|
+
try {
|
|
3011
|
+
const sendOptions = {};
|
|
3012
|
+
if (message.isReply && message.inReplyToMessageId) {
|
|
3013
|
+
sendOptions.replyTo = message.inReplyToMessageId;
|
|
3014
|
+
}
|
|
3015
|
+
const result = await client.send(
|
|
3016
|
+
{
|
|
3017
|
+
from: { id: this.account.twitterUserId, name: this.account.handle },
|
|
3018
|
+
content: message.body
|
|
3019
|
+
},
|
|
3020
|
+
sendOptions.replyTo ? { replyTo: sendOptions.replyTo } : void 0
|
|
3021
|
+
);
|
|
3022
|
+
return {
|
|
3023
|
+
success: result.success,
|
|
3024
|
+
providerMessageId: result.messageId,
|
|
3025
|
+
providerResponse: result.providerResponse,
|
|
3026
|
+
sentAt: result.timestamp
|
|
3027
|
+
};
|
|
3028
|
+
} catch (error) {
|
|
3029
|
+
return {
|
|
3030
|
+
success: false,
|
|
3031
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3032
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
3033
|
+
};
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
const TweetSender$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
3038
|
+
__proto__: null,
|
|
3039
|
+
TweetSender
|
|
3040
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
3041
|
+
export {
|
|
3042
|
+
Account,
|
|
3043
|
+
AccountCollection,
|
|
3044
|
+
Attachment,
|
|
3045
|
+
AttachmentCollection,
|
|
3046
|
+
Blacklist,
|
|
3047
|
+
BlacklistCollection,
|
|
3048
|
+
Email,
|
|
3049
|
+
EmailAccount,
|
|
3050
|
+
EmailAccountCollection,
|
|
3051
|
+
EmailAttachment,
|
|
3052
|
+
EmailAttachmentCollection,
|
|
3053
|
+
EmailCollection,
|
|
3054
|
+
EmailFolder,
|
|
3055
|
+
EmailFolderCollection,
|
|
3056
|
+
EmailSender,
|
|
3057
|
+
Message,
|
|
3058
|
+
MessageCollection,
|
|
3059
|
+
SlackAccount,
|
|
3060
|
+
SlackMessage,
|
|
3061
|
+
SlackSender,
|
|
3062
|
+
Tweet,
|
|
3063
|
+
TweetSender,
|
|
3064
|
+
TwitterAccount,
|
|
3065
|
+
Whitelist,
|
|
3066
|
+
WhitelistCollection
|
|
3067
|
+
};
|
|
3068
|
+
//# sourceMappingURL=index.js.map
|