@happyvertical/smrt-messages 0.37.2 → 0.37.4

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