@bobfrankston/rmfmail 1.0.555 → 1.0.559
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/_dev-pop.ts +328 -0
- package/_dev-populate.ts +222 -0
- package/client/components/message-list.js +2 -2
- package/client/components/message-list.js.map +1 -1
- package/client/components/message-list.ts +2 -2
- package/package.json +5 -5
- package/packages/mailx-imap/index.d.ts +4 -1
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +49 -70
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +51 -56
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-store/db.d.ts +17 -2
- package/packages/mailx-store/db.d.ts.map +1 -1
- package/packages/mailx-store/db.js +73 -9
- package/packages/mailx-store/db.js.map +1 -1
- package/packages/mailx-store/db.ts +94 -9
- package/packages/mailx-store/package.json +1 -1
|
@@ -909,6 +909,37 @@ export class ImapManager extends EventEmitter {
|
|
|
909
909
|
// monotonically increasing within a UIDVALIDITY (RFC 3501); a
|
|
910
910
|
// high-water mark is the right anchor for incremental fetch.
|
|
911
911
|
const highestUid = this.db.getHighestUid(accountId, folderId);
|
|
912
|
+
|
|
913
|
+
// STATUS-before-SELECT: ask the server cheaply whether anything has
|
|
914
|
+
// changed (UIDNEXT moved or message count differs). STATUS doesn't
|
|
915
|
+
// load the mailbox index, doesn't lock the mailbox, doesn't risk the
|
|
916
|
+
// SELECT-wedge that's been hammering bobma. Only when STATUS says
|
|
917
|
+
// there's actual work to do do we fall through to SELECT+FETCH.
|
|
918
|
+
// Apply uniformly to every folder — no special-casing.
|
|
919
|
+
try {
|
|
920
|
+
if (typeof (client as any).getStatus === "function") {
|
|
921
|
+
const status = await (client as any).getStatus(folder.path);
|
|
922
|
+
const serverHighest = (status.uidNext || 1) - 1;
|
|
923
|
+
const serverCount = status.messages ?? -1;
|
|
924
|
+
const localCount = this.db.getMessageCount(accountId, folderId);
|
|
925
|
+
const isUpToDate = highestUid > 0
|
|
926
|
+
&& serverHighest <= highestUid
|
|
927
|
+
&& serverCount >= 0
|
|
928
|
+
&& serverCount === localCount;
|
|
929
|
+
if (isUpToDate) {
|
|
930
|
+
console.log(` [sync] ${accountId}/${folder.path}: STATUS up-to-date (uidNext=${status.uidNext}, server=${serverCount}, local=${localCount}) — skipping SELECT`);
|
|
931
|
+
this.emit("syncProgress", accountId, `sync:${folder.path}`, 100);
|
|
932
|
+
return 0;
|
|
933
|
+
}
|
|
934
|
+
console.log(` [sync] ${accountId}/${folder.path}: STATUS uidNext=${status.uidNext} server=${serverCount} local=${localCount} highestUid=${highestUid} — needs SELECT`);
|
|
935
|
+
}
|
|
936
|
+
} catch (e: any) {
|
|
937
|
+
// STATUS shouldn't fail, but don't make it load-bearing — fall
|
|
938
|
+
// through to the SELECT path on any error and let that path's
|
|
939
|
+
// existing handling deal with it.
|
|
940
|
+
console.log(` [sync] ${accountId}/${folder.path}: STATUS failed (${e?.message || e}) — falling through to SELECT`);
|
|
941
|
+
}
|
|
942
|
+
|
|
912
943
|
console.log(` [sync] ${accountId}/${folder.path}: highestUid=${highestUid}, fetching...`);
|
|
913
944
|
|
|
914
945
|
let messages: any[];
|
|
@@ -1140,7 +1171,7 @@ export class ImapManager extends EventEmitter {
|
|
|
1140
1171
|
const tag = env ? `msgid=${env.messageId || "?"} subj="${(env.subject || "").slice(0, 60)}"` : "unknown";
|
|
1141
1172
|
console.log(` [reconcile-delete] ${accountId}/${folder.path} uid=${uid} ${tag}`);
|
|
1142
1173
|
this.unlinkBodyFile(accountId, uid, folderId).catch(() => {});
|
|
1143
|
-
this.db.deleteMessage(accountId, uid);
|
|
1174
|
+
this.db.deleteMessage(accountId, uid, "reconcile: server returned UID list without this row", `mailx-imap syncFolder reconcile (${folder.path})`);
|
|
1144
1175
|
deletedCount++;
|
|
1145
1176
|
}
|
|
1146
1177
|
if (deletedCount > 0) console.log(` removed ${deletedCount} deleted messages`);
|
|
@@ -1530,7 +1561,7 @@ export class ImapManager extends EventEmitter {
|
|
|
1530
1561
|
const tag = env ? `msgid=${env.messageId || "?"} subj="${(env.subject || "").slice(0, 60)}"` : "unknown";
|
|
1531
1562
|
console.log(` [reconcile-delete] ${accountId}/${folder.path} uid=${uid} ${tag}`);
|
|
1532
1563
|
this.unlinkBodyFile(accountId, uid, folder.id).catch(() => {});
|
|
1533
|
-
this.db.deleteMessage(accountId, uid);
|
|
1564
|
+
this.db.deleteMessage(accountId, uid, "Gmail-API reconcile: server list missing this UID", `mailx-imap Gmail reconcile (${folder.path})`);
|
|
1534
1565
|
}
|
|
1535
1566
|
if (toDelete.length > 0) console.log(` [api] ${accountId}/${folder.path}: ${toDelete.length} deleted`);
|
|
1536
1567
|
}
|
|
@@ -1900,7 +1931,10 @@ export class ImapManager extends EventEmitter {
|
|
|
1900
1931
|
return !!config?.tokenProvider;
|
|
1901
1932
|
}
|
|
1902
1933
|
|
|
1903
|
-
/** Start IMAP IDLE
|
|
1934
|
+
/** Start an IMAP IDLE watcher per account on INBOX. Other folders are
|
|
1935
|
+
* not special-cased here — sync runs uniformly on every folder via the
|
|
1936
|
+
* periodic full-sync timer (STATUS-before-SELECT optimization is the
|
|
1937
|
+
* right way to make that cheap, not piling per-folder IDLE sockets). */
|
|
1904
1938
|
async startWatching(): Promise<void> {
|
|
1905
1939
|
for (const [accountId] of this.configs) {
|
|
1906
1940
|
if (this.watchers.has(accountId)) continue;
|
|
@@ -1909,56 +1943,17 @@ export class ImapManager extends EventEmitter {
|
|
|
1909
1943
|
// is parked in IDLE, it's unusable for any other command, so
|
|
1910
1944
|
// it can't share the ops queue. Counts against the per-host
|
|
1911
1945
|
// semaphore (one slot for the IDLE socket).
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
const stops: Array<() => Promise<void>> = [];
|
|
1919
|
-
const clients: Array<{ logout(): Promise<void> }> = [];
|
|
1920
|
-
|
|
1921
|
-
const watchOne = async (mailboxLabel: "inbox" | "sent" | "drafts", path: string): Promise<void> => {
|
|
1922
|
-
const client = await this.createClient(accountId, "idle");
|
|
1923
|
-
clients.push(client);
|
|
1924
|
-
const stop = await client.watchMailbox(path, (newCount: number) => {
|
|
1925
|
-
console.log(` [idle] ${accountId} ${path}: ${newCount} new message(s)`);
|
|
1926
|
-
if (mailboxLabel === "inbox") {
|
|
1927
|
-
// Fast path: incremental fetch of NEW UIDs only.
|
|
1928
|
-
// Heavy reconcile runs on the 5-minute STATUS poll.
|
|
1929
|
-
this.syncInboxNewOnly(accountId).catch(e =>
|
|
1930
|
-
console.error(` [idle] inbox sync error: ${e.message}`));
|
|
1931
|
-
} else {
|
|
1932
|
-
// Sent / Drafts changed elsewhere. Use the
|
|
1933
|
-
// standard folder sync — picks up the new UID,
|
|
1934
|
-
// rebinds any optimistic local row by Message-ID.
|
|
1935
|
-
const folder = this.findFolder(accountId, mailboxLabel);
|
|
1936
|
-
if (folder) {
|
|
1937
|
-
this.syncFolder(accountId, folder.id).catch(e =>
|
|
1938
|
-
console.error(` [idle] ${path} sync error: ${e.message}`));
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
});
|
|
1942
|
-
stops.push(stop);
|
|
1943
|
-
};
|
|
1944
|
-
|
|
1945
|
-
await watchOne("inbox", "INBOX");
|
|
1946
|
-
const sent = this.findFolder(accountId, "sent");
|
|
1947
|
-
if (sent) {
|
|
1948
|
-
try { await watchOne("sent", sent.path); }
|
|
1949
|
-
catch (e: any) { console.error(` [idle] Failed to watch ${sent.path}: ${e.message}`); }
|
|
1950
|
-
}
|
|
1951
|
-
const drafts = this.findFolder(accountId, "drafts");
|
|
1952
|
-
if (drafts) {
|
|
1953
|
-
try { await watchOne("drafts", drafts.path); }
|
|
1954
|
-
catch (e: any) { console.error(` [idle] Failed to watch ${drafts.path}: ${e.message}`); }
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1946
|
+
const watchClient = await this.createClient(accountId, "idle");
|
|
1947
|
+
const stop = await watchClient.watchMailbox("INBOX", (newCount: number) => {
|
|
1948
|
+
console.log(` [idle] ${accountId}: ${newCount} new message(s)`);
|
|
1949
|
+
this.syncInboxNewOnly(accountId).catch(e =>
|
|
1950
|
+
console.error(` [idle] sync error: ${e.message}`));
|
|
1951
|
+
});
|
|
1957
1952
|
this.watchers.set(accountId, async () => {
|
|
1958
|
-
|
|
1959
|
-
|
|
1953
|
+
await stop();
|
|
1954
|
+
await watchClient.logout();
|
|
1960
1955
|
});
|
|
1961
|
-
console.log(` [idle] Watching INBOX
|
|
1956
|
+
console.log(` [idle] Watching INBOX for ${accountId}`);
|
|
1962
1957
|
} catch (e: any) {
|
|
1963
1958
|
console.error(` [idle] Failed to watch ${accountId}: ${e.message}`);
|
|
1964
1959
|
}
|
|
@@ -2211,7 +2206,7 @@ export class ImapManager extends EventEmitter {
|
|
|
2211
2206
|
if (received.has(uid)) continue;
|
|
2212
2207
|
try {
|
|
2213
2208
|
this.unlinkBodyFile(accountId, uid, folderId).catch(() => {});
|
|
2214
|
-
this.db.deleteMessage(accountId, uid);
|
|
2209
|
+
this.db.deleteMessage(accountId, uid, "prefetch batch: server didn't return body for queued UID — assumed deleted", "mailx-imap prefetchBodies (Gmail batch)");
|
|
2215
2210
|
counters.deleted++;
|
|
2216
2211
|
madeProgress = true;
|
|
2217
2212
|
} catch { /* ignore */ }
|
|
@@ -2295,7 +2290,7 @@ export class ImapManager extends EventEmitter {
|
|
|
2295
2290
|
if (received.has(uid)) continue;
|
|
2296
2291
|
try {
|
|
2297
2292
|
this.unlinkBodyFile(accountId, uid, folderId).catch(() => {});
|
|
2298
|
-
this.db.deleteMessage(accountId, uid);
|
|
2293
|
+
this.db.deleteMessage(accountId, uid, "prefetch batch: server didn't return body for queued UID — assumed deleted", "mailx-imap prefetchBodies (IMAP batch)");
|
|
2299
2294
|
counters.deleted++;
|
|
2300
2295
|
madeProgress = true;
|
|
2301
2296
|
} catch { /* ignore */ }
|
|
@@ -2332,7 +2327,7 @@ export class ImapManager extends EventEmitter {
|
|
|
2332
2327
|
// Local first — remove all from DB immediately
|
|
2333
2328
|
for (const msg of messages) {
|
|
2334
2329
|
this.unlinkBodyFile(accountId, msg.uid, msg.folderId).catch(() => {});
|
|
2335
|
-
this.db.deleteMessage(accountId, msg.uid);
|
|
2330
|
+
this.db.deleteMessage(accountId, msg.uid, "user-initiated delete (bulk)", "mailx-imap deleteMessages");
|
|
2336
2331
|
}
|
|
2337
2332
|
console.log(` Deleted ${messages.length} messages locally`);
|
|
2338
2333
|
|
|
@@ -2388,7 +2383,7 @@ export class ImapManager extends EventEmitter {
|
|
|
2388
2383
|
|
|
2389
2384
|
// Local first — remove from DB immediately
|
|
2390
2385
|
this.unlinkBodyFile(accountId, uid, folderId).catch(() => {});
|
|
2391
|
-
this.db.deleteMessage(accountId, uid);
|
|
2386
|
+
this.db.deleteMessage(accountId, uid, "user-initiated trash", "mailx-imap trashMessage");
|
|
2392
2387
|
|
|
2393
2388
|
// Queue IMAP action + log the resolution so "I deleted a message and
|
|
2394
2389
|
// now it's in neither trash nor deleted" is diagnosable from the log.
|
|
@@ -2435,7 +2430,7 @@ export class ImapManager extends EventEmitter {
|
|
|
2435
2430
|
const msg = await sourceClient.fetchMessageByUid(fromFolder.path, uid, { source: true });
|
|
2436
2431
|
if (!msg) throw new Error(`Message UID ${uid} not found in ${fromFolder.path}`);
|
|
2437
2432
|
await sourceClient.moveMessageToServer(msg, fromFolder.path, targetClient, toFolder.path);
|
|
2438
|
-
this.db.deleteMessage(fromAccountId, uid);
|
|
2433
|
+
this.db.deleteMessage(fromAccountId, uid, `cross-account move to ${toAccountId}/${toFolder.path}`, "mailx-imap moveBetweenAccounts");
|
|
2439
2434
|
console.log(` Cross-account move: ${fromAccountId}/${fromFolder.path} UID ${uid} → ${toAccountId}/${toFolder.path}`);
|
|
2440
2435
|
});
|
|
2441
2436
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-imap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bobfrankston/mailx-imap",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.26",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@bobfrankston/iflow-direct": "^0.1.27",
|
|
@@ -148,6 +148,20 @@ export declare class MailxDB {
|
|
|
148
148
|
updateLastSync(accountId: string, timestamp: number): void;
|
|
149
149
|
upsertFolder(accountId: string, folderPath: string, name: string, specialUse: string, delimiter: string): number;
|
|
150
150
|
getFolders(accountId: string): Folder[];
|
|
151
|
+
/** Append a row to the audit_log table. Every destructive operation on
|
|
152
|
+
* the messages table writes here so "where did the rows go?" has an
|
|
153
|
+
* authoritative answer. Cheap insert, indexed by ts and (account, folder). */
|
|
154
|
+
audit(entry: {
|
|
155
|
+
kind: "delete-msg" | "delete-bulk" | "migration" | "manual" | string;
|
|
156
|
+
accountId?: string;
|
|
157
|
+
folderId?: number;
|
|
158
|
+
uid?: number;
|
|
159
|
+
count?: number;
|
|
160
|
+
messageId?: string;
|
|
161
|
+
subject?: string;
|
|
162
|
+
reason?: string;
|
|
163
|
+
source?: string;
|
|
164
|
+
}): void;
|
|
151
165
|
deleteFolder(folderId: number): void;
|
|
152
166
|
markFolderRead(folderId: number): void;
|
|
153
167
|
deleteAllMessages(accountId: string, folderId: number): void;
|
|
@@ -204,8 +218,9 @@ export declare class MailxDB {
|
|
|
204
218
|
getMessageCount(accountId: string, folderId: number): number;
|
|
205
219
|
/** Get all UIDs for a folder */
|
|
206
220
|
getUidsForFolder(accountId: string, folderId: number): number[];
|
|
207
|
-
/** Delete a message by account + UID
|
|
208
|
-
|
|
221
|
+
/** Delete a message by account + UID. Reason is propagated into the
|
|
222
|
+
* audit_log so the DB carries an authoritative trail of every removal. */
|
|
223
|
+
deleteMessage(accountId: string, uid: number, reason?: string, source?: string): void;
|
|
209
224
|
/** Recalculate folder total/unread counts from actual messages */
|
|
210
225
|
recalcFolderCounts(folderId: number): void;
|
|
211
226
|
/** Bulk insert within a transaction for sync performance */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["db.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAc,MAAM,2BAA2B,CAAC;AAgC9H;yEACyE;AACzE,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAOhE;
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["db.ts"],"names":[],"mappings":"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"}
|
|
@@ -285,6 +285,35 @@ const SCHEMA = `
|
|
|
285
285
|
updated_at INTEGER NOT NULL,
|
|
286
286
|
PRIMARY KEY(scope, key)
|
|
287
287
|
);
|
|
288
|
+
|
|
289
|
+
-- Audit trail of every destructive DB operation. Writes ONLY — the row
|
|
290
|
+
-- inserted here records the deletion the caller is about to (or just
|
|
291
|
+
-- did) commit on the messages table. Lets the user retroactively answer
|
|
292
|
+
-- "where did my Sent rows go?" — every deletion has a row with
|
|
293
|
+
-- kind/reason/folder/uid + caller stack hint. Trivial cost (rows are
|
|
294
|
+
-- tiny), no retention policy yet — kept indefinitely.
|
|
295
|
+
--
|
|
296
|
+
-- kind values:
|
|
297
|
+
-- delete-msg single message removed (sync_actions drain, body
|
|
298
|
+
-- fetch reported isNotFound, reconcile)
|
|
299
|
+
-- delete-bulk bulk delete (reconcile pruning, emptyFolder, etc.)
|
|
300
|
+
-- migration schema migrations that touch messages
|
|
301
|
+
-- manual direct user "rebuild" / "wipe folder"
|
|
302
|
+
CREATE TABLE IF NOT EXISTS audit_log (
|
|
303
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
304
|
+
ts INTEGER NOT NULL,
|
|
305
|
+
kind TEXT NOT NULL,
|
|
306
|
+
account_id TEXT,
|
|
307
|
+
folder_id INTEGER,
|
|
308
|
+
uid INTEGER,
|
|
309
|
+
count INTEGER,
|
|
310
|
+
message_id TEXT,
|
|
311
|
+
subject TEXT,
|
|
312
|
+
reason TEXT,
|
|
313
|
+
source TEXT
|
|
314
|
+
);
|
|
315
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_ts ON audit_log(ts);
|
|
316
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_folder ON audit_log(account_id, folder_id);
|
|
288
317
|
`;
|
|
289
318
|
export class MailxDB {
|
|
290
319
|
db;
|
|
@@ -344,8 +373,10 @@ export class MailxDB {
|
|
|
344
373
|
const purgedFlag = this.getKv("schema", "drop_synthetic_uids_v1");
|
|
345
374
|
if (!purgedFlag) {
|
|
346
375
|
const r = this.db.prepare("DELETE FROM messages WHERE uid < 0").run();
|
|
347
|
-
|
|
348
|
-
|
|
376
|
+
const count = r.changes || 0;
|
|
377
|
+
if (count > 0) {
|
|
378
|
+
console.log(` [migration] dropped ${count} synthetic-UID rows from messages`);
|
|
379
|
+
this.audit({ kind: "migration", count, reason: "drop_synthetic_uids_v1 — purge negative-UID optimistic-Sent rows", source: "MailxDB constructor" });
|
|
349
380
|
}
|
|
350
381
|
this.setKv("schema", "drop_synthetic_uids_v1", "1");
|
|
351
382
|
}
|
|
@@ -930,8 +961,24 @@ export class MailxDB {
|
|
|
930
961
|
}
|
|
931
962
|
return folders;
|
|
932
963
|
}
|
|
964
|
+
/** Append a row to the audit_log table. Every destructive operation on
|
|
965
|
+
* the messages table writes here so "where did the rows go?" has an
|
|
966
|
+
* authoritative answer. Cheap insert, indexed by ts and (account, folder). */
|
|
967
|
+
audit(entry) {
|
|
968
|
+
try {
|
|
969
|
+
this.db.prepare(`INSERT INTO audit_log (ts, kind, account_id, folder_id, uid, count, message_id, subject, reason, source)
|
|
970
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(Date.now(), entry.kind, entry.accountId ?? null, entry.folderId ?? null, entry.uid ?? null, entry.count ?? null, entry.messageId ?? null, entry.subject ?? null, entry.reason ?? null, entry.source ?? null);
|
|
971
|
+
}
|
|
972
|
+
catch (e) {
|
|
973
|
+
// Audit must never break the operation it's auditing.
|
|
974
|
+
console.error(` [audit] failed: ${e?.message || e}`);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
933
977
|
deleteFolder(folderId) {
|
|
934
|
-
this.db.prepare("DELETE FROM messages WHERE folder_id = ?").run(folderId);
|
|
978
|
+
const r = this.db.prepare("DELETE FROM messages WHERE folder_id = ?").run(folderId);
|
|
979
|
+
const count = r.changes || 0;
|
|
980
|
+
if (count > 0)
|
|
981
|
+
this.audit({ kind: "delete-bulk", folderId, count, reason: "folder deleted", source: "deleteFolder" });
|
|
935
982
|
this.db.prepare("DELETE FROM folders WHERE id = ?").run(folderId);
|
|
936
983
|
}
|
|
937
984
|
markFolderRead(folderId) {
|
|
@@ -939,7 +986,10 @@ export class MailxDB {
|
|
|
939
986
|
this.recalcFolderCounts(folderId);
|
|
940
987
|
}
|
|
941
988
|
deleteAllMessages(accountId, folderId) {
|
|
942
|
-
this.db.prepare("DELETE FROM messages WHERE account_id = ? AND folder_id = ?").run(accountId, folderId);
|
|
989
|
+
const r = this.db.prepare("DELETE FROM messages WHERE account_id = ? AND folder_id = ?").run(accountId, folderId);
|
|
990
|
+
const count = r.changes || 0;
|
|
991
|
+
if (count > 0)
|
|
992
|
+
this.audit({ kind: "delete-bulk", accountId, folderId, count, reason: "deleteAllMessages (emptyFolder)", source: "deleteAllMessages" });
|
|
943
993
|
this.recalcFolderCounts(folderId);
|
|
944
994
|
}
|
|
945
995
|
updateFolderCounts(folderId, total, unread) {
|
|
@@ -1231,11 +1281,25 @@ export class MailxDB {
|
|
|
1231
1281
|
const rows = this.db.prepare("SELECT uid FROM messages WHERE account_id = ? AND folder_id = ?").all(accountId, folderId);
|
|
1232
1282
|
return rows.map(r => r.uid);
|
|
1233
1283
|
}
|
|
1234
|
-
/** Delete a message by account + UID
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1284
|
+
/** Delete a message by account + UID. Reason is propagated into the
|
|
1285
|
+
* audit_log so the DB carries an authoritative trail of every removal. */
|
|
1286
|
+
deleteMessage(accountId, uid, reason, source) {
|
|
1287
|
+
// Get folderId + message_id + subject before deleting so the audit
|
|
1288
|
+
// row carries enough context to identify what was removed.
|
|
1289
|
+
const msg = this.db.prepare("SELECT folder_id, message_id, subject FROM messages WHERE account_id = ? AND uid = ?").get(accountId, uid);
|
|
1290
|
+
const r = this.db.prepare("DELETE FROM messages WHERE account_id = ? AND uid = ?").run(accountId, uid);
|
|
1291
|
+
if (r.changes && msg) {
|
|
1292
|
+
this.audit({
|
|
1293
|
+
kind: "delete-msg",
|
|
1294
|
+
accountId,
|
|
1295
|
+
folderId: msg.folder_id,
|
|
1296
|
+
uid,
|
|
1297
|
+
messageId: msg.message_id || undefined,
|
|
1298
|
+
subject: msg.subject || undefined,
|
|
1299
|
+
reason: reason || "deleteMessage (no reason)",
|
|
1300
|
+
source: source || "db.deleteMessage",
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1239
1303
|
// Refresh folder counts
|
|
1240
1304
|
if (msg)
|
|
1241
1305
|
this.recalcFolderCounts(msg.folder_id);
|