@flowselections/mailbox-orders 1.0.47 → 1.0.49
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-lib/components/MailboxProposalsPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProposalsPage.js +4 -3
- package/dist-lib/components/MailboxSkippedPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxSkippedPage.js +5 -3
- package/dist-lib/components/MailboxStatusLine.d.ts +5 -0
- package/dist-lib/components/MailboxStatusLine.d.ts.map +1 -0
- package/dist-lib/components/MailboxStatusLine.js +23 -0
- package/dist-lib/integrations/supabase/types.d.ts +3364 -122
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +1 -0
- package/dist-lib/lib/mailbox-filter-settings.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox-filter-settings.functions.js +3 -0
- package/dist-lib/lib/mailbox-folders.d.ts +25 -0
- package/dist-lib/lib/mailbox-folders.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-folders.js +73 -0
- package/dist-lib/lib/mailbox-ingest.server.d.ts +5 -0
- package/dist-lib/lib/mailbox-ingest.server.d.ts.map +1 -1
- package/dist-lib/lib/mailbox-ingest.server.js +338 -109
- package/dist-lib/lib/mailbox.functions.d.ts +29 -0
- package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.js +106 -2
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ import { detectLoadCarrierInText, isLoadCarrierAllowed, loadCarrierLabel, normal
|
|
|
13
13
|
import { classifyMessage, extractLatestPart } from "./mailbox-filter.js";
|
|
14
14
|
import { normalizeUnitKind, toPlants, unitPricePerPlant } from "./mailbox-units.js";
|
|
15
15
|
import { matchLine, rankCandidates } from "./mailbox-match.js";
|
|
16
|
+
import { classifyEventFolder } from "./mailbox-folders.js";
|
|
16
17
|
/**
|
|
17
18
|
* Ladingdragers komen live uit de Logistics Engine — nooit cachen, nooit een
|
|
18
19
|
* eigen lijst bijhouden.
|
|
@@ -713,6 +714,7 @@ async function analyzeAndBuild(admin, msg, customerId, text, s) {
|
|
|
713
714
|
},
|
|
714
715
|
};
|
|
715
716
|
}
|
|
717
|
+
export const NO_AI_AGENT_NOTE = "Automatisch uitlezen staat uit: koppel een AI-agent bij Instellingen › AI-Authenticatie";
|
|
716
718
|
/**
|
|
717
719
|
* Maakt exact één voorstel per bericht. Eerst het voorfilter (zonder AI), dan
|
|
718
720
|
* het vastleggen van de analyse (één tegelijk + limieten), en pas dan de AI.
|
|
@@ -757,6 +759,33 @@ export async function createStubProposal(admin, messageId, fromEmail, opts = {})
|
|
|
757
759
|
else {
|
|
758
760
|
await setMsg({ filter_outcome: msg.filter_outcome ?? "analyseren", filter_reason: "Handmatig gestart" });
|
|
759
761
|
}
|
|
762
|
+
// Geen AI-agent gekoppeld: toch een voorstel (zonder automatisch uitlezen), nooit stil overslaan.
|
|
763
|
+
{
|
|
764
|
+
const { currentAiFingerprint } = await import("./mailbox-ai.server.js");
|
|
765
|
+
const fp = await currentAiFingerprint(admin).catch(() => null);
|
|
766
|
+
if (fp === null) {
|
|
767
|
+
await setMsg({ analysis_note: NO_AI_AGENT_NOTE });
|
|
768
|
+
const { error: stubErr } = await admin.from("mailbox_order_proposals").insert({
|
|
769
|
+
message_id: messageId,
|
|
770
|
+
matched_customer_id: matchResult.customerId,
|
|
771
|
+
status: "pending",
|
|
772
|
+
notes: NO_AI_AGENT_NOTE,
|
|
773
|
+
parsed_payload: {
|
|
774
|
+
auto_matched_customer: matchResult.customerId !== null,
|
|
775
|
+
auto_match_source: matchResult.source,
|
|
776
|
+
auto_match_email: senderEmail || null,
|
|
777
|
+
auto_match_forwarded: !!latest.forwardedFrom,
|
|
778
|
+
auto_match_warning: matchResult.warning,
|
|
779
|
+
auto_match_candidates: matchResult.candidates,
|
|
780
|
+
stub: true,
|
|
781
|
+
reason: "geen_ai_agent",
|
|
782
|
+
},
|
|
783
|
+
});
|
|
784
|
+
if (stubErr && stubErr.code !== "23505")
|
|
785
|
+
throw new Error(stubErr.message);
|
|
786
|
+
return { status: stubErr ? "exists" : "created", reason: "geen_ai_agent" };
|
|
787
|
+
}
|
|
788
|
+
}
|
|
760
789
|
const claim = await claimAnalysis(admin, messageId, opts.force === true, opts.trigger ?? "auto");
|
|
761
790
|
if (!claim.ok) {
|
|
762
791
|
return claim.reason === "busy" ? { status: "busy" } : { status: "waiting", reason: claim.reason };
|
|
@@ -1003,9 +1032,16 @@ export async function retryFailedAiProposals(admin, limit = 3, maxRetries = AI_M
|
|
|
1003
1032
|
}
|
|
1004
1033
|
return { retried, recovered, stillFailing };
|
|
1005
1034
|
}
|
|
1006
|
-
|
|
1035
|
+
/** Een event dat niet uit een gevolgde map komt: alleen metadata vastleggen, geen inhoud. */
|
|
1036
|
+
async function recordIgnoredEvent(admin, eventId, connectionId, reason) {
|
|
1037
|
+
await admin
|
|
1038
|
+
.from("mailbox_ignored_events")
|
|
1039
|
+
.upsert({ event_id: eventId, connection_id: connectionId, reason }, { onConflict: "event_id", ignoreDuplicates: true });
|
|
1040
|
+
}
|
|
1041
|
+
async function ingestSingleEvent(admin, connectionId, ev, opts = {}) {
|
|
1007
1042
|
const p = ev.payload ?? {};
|
|
1008
1043
|
const externalId = ev.external_id ?? pickStr(p, "message_id", "messageId", "id") ?? ev.id;
|
|
1044
|
+
const providerMessageId = pickStr(p, "message_id", "messageId") ?? externalId;
|
|
1009
1045
|
// Idempotent: bestaat er al een message voor deze external_id op deze connectie?
|
|
1010
1046
|
if (externalId) {
|
|
1011
1047
|
const { data: existing } = await admin
|
|
@@ -1020,6 +1056,17 @@ async function ingestSingleEvent(admin, connectionId, ev) {
|
|
|
1020
1056
|
return { inserted: false, messageId: existing.id };
|
|
1021
1057
|
}
|
|
1022
1058
|
}
|
|
1059
|
+
// Ontdubbelen over koppelingen heen: dezelfde mail (bericht-id van de provider) via een andere koppeling.
|
|
1060
|
+
if (providerMessageId) {
|
|
1061
|
+
const { data: same } = await admin
|
|
1062
|
+
.from("mailbox_messages")
|
|
1063
|
+
.select("id")
|
|
1064
|
+
.eq("message_id", providerMessageId)
|
|
1065
|
+
.limit(1)
|
|
1066
|
+
.maybeSingle();
|
|
1067
|
+
if (same?.id)
|
|
1068
|
+
return { inserted: false, messageId: same.id };
|
|
1069
|
+
}
|
|
1023
1070
|
const fromEmail = pickStr(p, "from_email", "fromEmail", "from.address", "from.email", "sender.address") ?? "";
|
|
1024
1071
|
const fromName = pickStr(p, "from_name", "fromName", "from.name", "sender.name");
|
|
1025
1072
|
const subject = pickStr(p, "subject", "title") ?? "";
|
|
@@ -1028,12 +1075,14 @@ async function ingestSingleEvent(admin, connectionId, ev) {
|
|
|
1028
1075
|
const receivedAt = pickStr(p, "received_at", "receivedAt", "date", "internalDate") ??
|
|
1029
1076
|
ev.received_at ??
|
|
1030
1077
|
new Date().toISOString();
|
|
1078
|
+
const tooOld = isTooOld(receivedAt, opts.maxAgeDays);
|
|
1031
1079
|
const { data: inserted, error: insErr } = await admin
|
|
1032
1080
|
.from("mailbox_messages")
|
|
1033
1081
|
.insert({
|
|
1034
1082
|
external_id: externalId,
|
|
1035
|
-
message_id:
|
|
1083
|
+
message_id: providerMessageId,
|
|
1036
1084
|
connection_id: connectionId,
|
|
1085
|
+
account_email: opts.accountEmail ?? null,
|
|
1037
1086
|
from_email: fromEmail,
|
|
1038
1087
|
from_name: fromName,
|
|
1039
1088
|
subject,
|
|
@@ -1046,11 +1095,12 @@ async function ingestSingleEvent(admin, connectionId, ev) {
|
|
|
1046
1095
|
(Array.isArray(p?.attachments) && p.attachments.length > 0),
|
|
1047
1096
|
received_at: receivedAt,
|
|
1048
1097
|
status: "new",
|
|
1098
|
+
...(tooOld ? { filter_outcome: "te_oud", filter_reason: "te oud bij eerste verwerking" } : {}),
|
|
1049
1099
|
})
|
|
1050
1100
|
.select("id")
|
|
1051
1101
|
.single();
|
|
1052
1102
|
if (insErr) {
|
|
1053
|
-
// Race met cron/realtime
|
|
1103
|
+
// Race met cron/realtime of dezelfde mail via een andere koppeling.
|
|
1054
1104
|
if (insErr.code === "23505") {
|
|
1055
1105
|
const { data: dup } = await admin
|
|
1056
1106
|
.from("mailbox_messages")
|
|
@@ -1058,110 +1108,288 @@ async function ingestSingleEvent(admin, connectionId, ev) {
|
|
|
1058
1108
|
.eq("connection_id", connectionId)
|
|
1059
1109
|
.eq("external_id", externalId)
|
|
1060
1110
|
.maybeSingle();
|
|
1061
|
-
if (dup?.id)
|
|
1111
|
+
if (dup?.id) {
|
|
1062
1112
|
await createStubProposal(admin, dup.id, fromEmail);
|
|
1063
|
-
|
|
1113
|
+
return { inserted: false, messageId: dup.id };
|
|
1114
|
+
}
|
|
1115
|
+
const { data: same } = await admin
|
|
1116
|
+
.from("mailbox_messages")
|
|
1117
|
+
.select("id")
|
|
1118
|
+
.eq("message_id", providerMessageId)
|
|
1119
|
+
.limit(1)
|
|
1120
|
+
.maybeSingle();
|
|
1121
|
+
return { inserted: false, messageId: same?.id ?? null };
|
|
1064
1122
|
}
|
|
1065
1123
|
throw new Error(insErr.message);
|
|
1066
1124
|
}
|
|
1067
|
-
|
|
1125
|
+
if (!tooOld)
|
|
1126
|
+
await createStubProposal(admin, inserted.id, fromEmail);
|
|
1068
1127
|
return { inserted: true, messageId: inserted.id };
|
|
1069
1128
|
}
|
|
1129
|
+
/** Ouder dan de ingestelde grens (standaard 14 dagen) bij eerste verwerking. */
|
|
1130
|
+
export function isTooOld(receivedAt, maxAgeDays, now = Date.now()) {
|
|
1131
|
+
const days = Number(maxAgeDays ?? 14);
|
|
1132
|
+
if (!receivedAt || !Number.isFinite(days) || days <= 0)
|
|
1133
|
+
return false;
|
|
1134
|
+
const t = Date.parse(receivedAt);
|
|
1135
|
+
if (!Number.isFinite(t))
|
|
1136
|
+
return false;
|
|
1137
|
+
return now - t > days * 86400000;
|
|
1138
|
+
}
|
|
1139
|
+
/** Nachtelijk (03:00–03:30 Amsterdam) één portie oude rondes opruimen. */
|
|
1140
|
+
function inPruneWindow(now = new Date()) {
|
|
1141
|
+
const parts = new Intl.DateTimeFormat("en-GB", { timeZone: "Europe/Amsterdam", hour: "2-digit", minute: "2-digit", hour12: false })
|
|
1142
|
+
.formatToParts(now);
|
|
1143
|
+
const h = Number(parts.find((x) => x.type === "hour")?.value);
|
|
1144
|
+
const m = Number(parts.find((x) => x.type === "minute")?.value);
|
|
1145
|
+
return h === 3 && m < 30;
|
|
1146
|
+
}
|
|
1147
|
+
async function loadConnections(admin, ids) {
|
|
1148
|
+
const out = new Map();
|
|
1149
|
+
if (ids.length === 0)
|
|
1150
|
+
return out;
|
|
1151
|
+
// select("*") zodat een oudere e-mailmodule zonder watch_scope geen fout geeft.
|
|
1152
|
+
const { data } = await admin.from("email_auth_connections").select("*").in("id", ids);
|
|
1153
|
+
for (const c of (Array.isArray(data) ? data : [])) {
|
|
1154
|
+
out.set(c.id, {
|
|
1155
|
+
id: c.id,
|
|
1156
|
+
provider: c.provider ?? null,
|
|
1157
|
+
account_email: c.account_email ?? null,
|
|
1158
|
+
watch_scope: c.watch_scope ?? null,
|
|
1159
|
+
graph_subscription_id: c.graph_subscription_id ?? null,
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
return out;
|
|
1163
|
+
}
|
|
1164
|
+
async function loadWatchFolders(admin, ids) {
|
|
1165
|
+
const out = new Map();
|
|
1166
|
+
if (ids.length === 0)
|
|
1167
|
+
return out;
|
|
1168
|
+
const { data, error } = await admin.from("email_auth_watch_folders").select("*").in("connection_id", ids);
|
|
1169
|
+
if (error)
|
|
1170
|
+
return out;
|
|
1171
|
+
for (const f of (Array.isArray(data) ? data : [])) {
|
|
1172
|
+
const list = out.get(f.connection_id) ?? [];
|
|
1173
|
+
list.push({ folder_id: f.folder_id ?? null, folder_name: f.folder_name ?? null, graph_subscription_id: f.graph_subscription_id ?? null });
|
|
1174
|
+
out.set(f.connection_id, list);
|
|
1175
|
+
}
|
|
1176
|
+
return out;
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Ontkoppelde adressen met onverwerkte events: alleen events ontvangen vóór de
|
|
1180
|
+
* laatste ronde waarin die koppeling nog actief was, worden alsnog verwerkt.
|
|
1181
|
+
*/
|
|
1182
|
+
async function leftoverConnections(admin, activeIds) {
|
|
1183
|
+
const out = new Map();
|
|
1184
|
+
let q = admin.from("email_auth_events").select("connection_id").is("consumed_at", null).limit(1000);
|
|
1185
|
+
if (activeIds.length > 0)
|
|
1186
|
+
q = q.not("connection_id", "in", `(${activeIds.join(",")})`);
|
|
1187
|
+
const { data, error } = await q;
|
|
1188
|
+
if (error)
|
|
1189
|
+
throw new Error(`select email_auth_events: ${error.message}`);
|
|
1190
|
+
const ids = Array.from(new Set((Array.isArray(data) ? data : []).map((r) => r.connection_id).filter(Boolean)));
|
|
1191
|
+
for (const id of ids) {
|
|
1192
|
+
const { data: last } = await admin
|
|
1193
|
+
.from("mailbox_ingest_runs")
|
|
1194
|
+
.select("started_at")
|
|
1195
|
+
.eq("connection_id", id)
|
|
1196
|
+
.order("started_at", { ascending: false })
|
|
1197
|
+
.limit(1)
|
|
1198
|
+
.maybeSingle();
|
|
1199
|
+
if (last?.started_at)
|
|
1200
|
+
out.set(id, last.started_at);
|
|
1201
|
+
}
|
|
1202
|
+
return out;
|
|
1203
|
+
}
|
|
1204
|
+
function plainReason(errors, sample) {
|
|
1205
|
+
if (errors === 0)
|
|
1206
|
+
return null;
|
|
1207
|
+
return `${errors} mail(s) konden niet worden verwerkt${sample ? `: ${sample}` : ""}`.slice(0, 500);
|
|
1208
|
+
}
|
|
1209
|
+
async function recordFailedRun(admin, trigger, connectionId, reason) {
|
|
1210
|
+
await admin
|
|
1211
|
+
.from("mailbox_ingest_runs")
|
|
1212
|
+
.insert({ trigger, connection_id: connectionId, status: "fout", reason, finished_at: new Date().toISOString(), errors: 1, error_sample: reason.slice(0, 500) });
|
|
1213
|
+
}
|
|
1070
1214
|
// De volledige pipeline. Alle triggers roepen dit aan.
|
|
1071
1215
|
export async function runIngest(trigger) {
|
|
1072
1216
|
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
1073
|
-
|
|
1074
|
-
|
|
1217
|
+
const admin = supabaseAdmin;
|
|
1218
|
+
// 0. Zelfherstel: ontbrekende standaardinstellingen aanmaken (overschrijft nooit iets).
|
|
1219
|
+
try {
|
|
1220
|
+
await admin.rpc("mailbox_ensure_defaults");
|
|
1221
|
+
}
|
|
1222
|
+
catch {
|
|
1223
|
+
/* functie ontbreekt nog: loadFilterSettings meldt het hieronder */
|
|
1224
|
+
}
|
|
1225
|
+
// 1. Alle actieve orders/receive koppelingen (service_role → geen bearer nodig).
|
|
1226
|
+
const { data: active, error: activeErr } = await admin.rpc("email_auth_for_purpose", {
|
|
1075
1227
|
_purpose: "orders",
|
|
1076
1228
|
_direction: "receive",
|
|
1077
1229
|
});
|
|
1078
|
-
if (activeErr)
|
|
1230
|
+
if (activeErr) {
|
|
1231
|
+
await recordFailedRun(admin, trigger, null, `Gekoppelde mailbox kon niet worden opgehaald: ${activeErr.message}`).catch(() => { });
|
|
1079
1232
|
throw new Error(`email_auth_for_purpose: ${activeErr.message}`);
|
|
1080
|
-
const row = Array.isArray(active) ? active[0] : null;
|
|
1081
|
-
const connectionId = row?.id ?? null;
|
|
1082
|
-
if (!connectionId) {
|
|
1083
|
-
return {
|
|
1084
|
-
ok: true,
|
|
1085
|
-
runId: null,
|
|
1086
|
-
processed: 0,
|
|
1087
|
-
inserted: 0,
|
|
1088
|
-
errors: 0,
|
|
1089
|
-
errorSample: null,
|
|
1090
|
-
connectionId: null,
|
|
1091
|
-
skipped: "no_active_connection",
|
|
1092
|
-
};
|
|
1093
1233
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1234
|
+
const activeIds = Array.from(new Set((Array.isArray(active) ? active : []).map((r) => r?.id).filter(Boolean)));
|
|
1235
|
+
if (activeIds.length === 0) {
|
|
1236
|
+
const left = await leftoverConnections(admin, []);
|
|
1237
|
+
if (left.size === 0) {
|
|
1238
|
+
return {
|
|
1239
|
+
ok: true,
|
|
1240
|
+
runId: null,
|
|
1241
|
+
processed: 0,
|
|
1242
|
+
inserted: 0,
|
|
1243
|
+
errors: 0,
|
|
1244
|
+
errorSample: null,
|
|
1245
|
+
connectionId: null,
|
|
1246
|
+
skipped: "no_active_connection",
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
// Controleer instellingen vóór writes.
|
|
1251
|
+
let settings;
|
|
1252
|
+
try {
|
|
1253
|
+
settings = await loadFilterSettings(admin);
|
|
1254
|
+
}
|
|
1255
|
+
catch (e) {
|
|
1256
|
+
const reason = e instanceof MailboxSettingsMissingError
|
|
1257
|
+
? "De instellingen van de mailbox ontbreken en konden niet worden aangemaakt."
|
|
1258
|
+
: String(e?.message ?? e);
|
|
1259
|
+
await recordFailedRun(admin, trigger, activeIds[0] ?? null, reason).catch(() => { });
|
|
1260
|
+
throw e;
|
|
1261
|
+
}
|
|
1262
|
+
if (trigger === "cron" && inPruneWindow()) {
|
|
1263
|
+
await admin.rpc("mailbox_prune_ingest_runs", { p_limit: 5000 }).then(() => { }, () => { });
|
|
1264
|
+
}
|
|
1265
|
+
const leftovers = await leftoverConnections(admin, activeIds);
|
|
1266
|
+
const allIds = [...activeIds, ...leftovers.keys()];
|
|
1267
|
+
const conns = await loadConnections(admin, allIds);
|
|
1268
|
+
const folders = await loadWatchFolders(admin, allIds);
|
|
1269
|
+
const perConn = Math.max(20, Math.ceil(200 / Math.max(1, allIds.length)));
|
|
1103
1270
|
let processed = 0;
|
|
1104
1271
|
let inserted = 0;
|
|
1105
1272
|
let errors = 0;
|
|
1273
|
+
let ignored = 0;
|
|
1106
1274
|
let errorSample = null;
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
const
|
|
1111
|
-
|
|
1112
|
-
.
|
|
1113
|
-
.
|
|
1114
|
-
.
|
|
1115
|
-
.
|
|
1116
|
-
|
|
1117
|
-
if (
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1275
|
+
let firstRunId = null;
|
|
1276
|
+
for (const connectionId of allIds) {
|
|
1277
|
+
const conn = conns.get(connectionId) ?? { id: connectionId, provider: null, account_email: null, watch_scope: null, graph_subscription_id: null };
|
|
1278
|
+
const cutoff = leftovers.get(connectionId) ?? null;
|
|
1279
|
+
const { data: runRow } = await admin
|
|
1280
|
+
.from("mailbox_ingest_runs")
|
|
1281
|
+
.insert({ trigger, connection_id: connectionId })
|
|
1282
|
+
.select("id")
|
|
1283
|
+
.single();
|
|
1284
|
+
const runId = runRow?.id ?? null;
|
|
1285
|
+
if (!firstRunId)
|
|
1286
|
+
firstRunId = runId;
|
|
1287
|
+
let cProcessed = 0, cInserted = 0, cErrors = 0, cIgnored = 0;
|
|
1288
|
+
let cSample = null;
|
|
1289
|
+
const done = [];
|
|
1290
|
+
let fatal = null;
|
|
1291
|
+
try {
|
|
1292
|
+
let q = admin
|
|
1293
|
+
.from("email_auth_events")
|
|
1294
|
+
.select("id, connection_id, external_id, payload, received_at")
|
|
1295
|
+
.eq("connection_id", connectionId)
|
|
1296
|
+
.is("consumed_at", null);
|
|
1297
|
+
if (cutoff)
|
|
1298
|
+
q = q.lte("received_at", cutoff);
|
|
1299
|
+
const { data: events, error: evErr } = await q.order("received_at", { ascending: true }).limit(perConn);
|
|
1300
|
+
if (evErr)
|
|
1301
|
+
throw new Error(`select email_auth_events: ${evErr.message}`);
|
|
1302
|
+
const ctx = {
|
|
1303
|
+
provider: conn.provider,
|
|
1304
|
+
watchScope: conn.watch_scope,
|
|
1305
|
+
connectionSubscriptionId: conn.graph_subscription_id,
|
|
1306
|
+
folders: folders.get(connectionId) ?? [],
|
|
1307
|
+
};
|
|
1308
|
+
for (const ev of (Array.isArray(events) ? events : [])) {
|
|
1309
|
+
cProcessed++;
|
|
1310
|
+
try {
|
|
1311
|
+
const verdict = classifyEventFolder(ctx, ev.payload ?? {});
|
|
1312
|
+
if (verdict.action === "negeren") {
|
|
1313
|
+
await recordIgnoredEvent(admin, ev.id, connectionId, verdict.reason);
|
|
1314
|
+
cIgnored++;
|
|
1315
|
+
done.push(ev.id);
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1318
|
+
const res = await ingestSingleEvent(admin, connectionId, {
|
|
1319
|
+
id: ev.id,
|
|
1320
|
+
external_id: ev.external_id ?? null,
|
|
1321
|
+
payload: ev.payload ?? {},
|
|
1322
|
+
received_at: ev.received_at ?? null,
|
|
1323
|
+
}, { accountEmail: conn.account_email, maxAgeDays: Number(settings.max_age_days ?? 14) });
|
|
1324
|
+
if (res.inserted)
|
|
1325
|
+
cInserted++;
|
|
1326
|
+
done.push(ev.id);
|
|
1327
|
+
}
|
|
1328
|
+
catch (e) {
|
|
1329
|
+
cErrors++;
|
|
1330
|
+
if (!cSample)
|
|
1331
|
+
cSample = String(e?.message ?? e).slice(0, 500);
|
|
1332
|
+
// Event blijft onverwerkt; de volgende ronde probeert het opnieuw.
|
|
1333
|
+
}
|
|
1131
1334
|
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1335
|
+
if (done.length > 0) {
|
|
1336
|
+
const { error: markErr } = await admin
|
|
1337
|
+
.from("email_auth_events")
|
|
1338
|
+
.update({ consumed_at: new Date().toISOString() })
|
|
1339
|
+
.in("id", done);
|
|
1340
|
+
if (markErr) {
|
|
1341
|
+
cErrors++;
|
|
1342
|
+
if (!cSample)
|
|
1343
|
+
cSample = `mark consumed: ${markErr.message}`.slice(0, 500);
|
|
1344
|
+
}
|
|
1137
1345
|
}
|
|
1138
1346
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1347
|
+
catch (e) {
|
|
1348
|
+
fatal = e;
|
|
1349
|
+
cErrors++;
|
|
1350
|
+
if (!cSample)
|
|
1351
|
+
cSample = String(e?.message ?? e).slice(0, 500);
|
|
1352
|
+
}
|
|
1353
|
+
finally {
|
|
1354
|
+
if (runId) {
|
|
1355
|
+
await admin
|
|
1356
|
+
.from("mailbox_ingest_runs")
|
|
1357
|
+
.update({
|
|
1358
|
+
finished_at: new Date().toISOString(),
|
|
1359
|
+
processed: cProcessed,
|
|
1360
|
+
inserted: cInserted,
|
|
1361
|
+
errors: cErrors,
|
|
1362
|
+
error_sample: cSample,
|
|
1363
|
+
status: cErrors > 0 ? "fout" : "ok",
|
|
1364
|
+
reason: plainReason(cErrors, cSample),
|
|
1365
|
+
})
|
|
1366
|
+
.eq("id", runId);
|
|
1149
1367
|
}
|
|
1150
1368
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1369
|
+
processed += cProcessed;
|
|
1370
|
+
inserted += cInserted;
|
|
1371
|
+
errors += cErrors;
|
|
1372
|
+
ignored += cIgnored;
|
|
1373
|
+
if (!errorSample)
|
|
1374
|
+
errorSample = cSample;
|
|
1375
|
+
if (fatal)
|
|
1376
|
+
throw fatal;
|
|
1377
|
+
}
|
|
1378
|
+
// Backfill: berichten die nog nooit door het voorfilter zijn gegaan.
|
|
1379
|
+
try {
|
|
1380
|
+
const { data: orphans } = await admin
|
|
1153
1381
|
.from("mailbox_messages")
|
|
1154
1382
|
.select("id, from_email, mailbox_order_proposals(id)")
|
|
1155
|
-
.
|
|
1383
|
+
.in("connection_id", allIds)
|
|
1156
1384
|
.is("filter_outcome", null)
|
|
1157
1385
|
.order("received_at", { ascending: false })
|
|
1158
1386
|
.limit(200);
|
|
1159
|
-
for (const m of (orphans
|
|
1387
|
+
for (const m of (Array.isArray(orphans) ? orphans : [])) {
|
|
1160
1388
|
const props = Array.isArray(m.mailbox_order_proposals) ? m.mailbox_order_proposals : [];
|
|
1161
1389
|
if (props.length > 0)
|
|
1162
1390
|
continue;
|
|
1163
1391
|
try {
|
|
1164
|
-
await createStubProposal(
|
|
1392
|
+
await createStubProposal(admin, m.id, m.from_email ?? null, { trigger });
|
|
1165
1393
|
}
|
|
1166
1394
|
catch (e) {
|
|
1167
1395
|
errors++;
|
|
@@ -1169,43 +1397,44 @@ export async function runIngest(trigger) {
|
|
|
1169
1397
|
errorSample = `orphan-stub: ${String(e?.message ?? e)}`.slice(0, 500);
|
|
1170
1398
|
}
|
|
1171
1399
|
}
|
|
1172
|
-
// 6 + 7. Gewijzigde AI-agent en herstelrondes: alleen vanuit cron en begrensd.
|
|
1173
|
-
if (trigger === "cron") {
|
|
1174
|
-
const s = settings;
|
|
1175
|
-
try {
|
|
1176
|
-
if (Number(s.max_agent_reparse) > 0)
|
|
1177
|
-
await reparseChangedAgentProposals(supabaseAdmin, Number(s.max_agent_reparse));
|
|
1178
|
-
}
|
|
1179
|
-
catch (e) {
|
|
1180
|
-
errors++;
|
|
1181
|
-
if (!errorSample)
|
|
1182
|
-
errorSample = `reparse: ${String(e?.message ?? e)}`.slice(0, 500);
|
|
1183
|
-
}
|
|
1184
|
-
try {
|
|
1185
|
-
await retryFailedAiProposals(supabaseAdmin, Number(s.max_retries_per_run), Number(s.max_retry_attempts));
|
|
1186
|
-
}
|
|
1187
|
-
catch (e) {
|
|
1188
|
-
errors++;
|
|
1189
|
-
if (!errorSample)
|
|
1190
|
-
errorSample = `retry: ${String(e?.message ?? e)}`.slice(0, 500);
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
1400
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1401
|
+
catch (e) {
|
|
1402
|
+
errors++;
|
|
1403
|
+
if (!errorSample)
|
|
1404
|
+
errorSample = `orphan-stub: ${String(e?.message ?? e)}`.slice(0, 500);
|
|
1405
|
+
}
|
|
1406
|
+
// Gewijzigde AI-agent en herstelrondes: alleen vanuit cron en begrensd.
|
|
1407
|
+
if (trigger === "cron") {
|
|
1408
|
+
const s = settings;
|
|
1409
|
+
try {
|
|
1410
|
+
if (Number(s.max_agent_reparse) > 0)
|
|
1411
|
+
await reparseChangedAgentProposals(admin, Number(s.max_agent_reparse));
|
|
1412
|
+
}
|
|
1413
|
+
catch (e) {
|
|
1414
|
+
errors++;
|
|
1415
|
+
if (!errorSample)
|
|
1416
|
+
errorSample = `reparse: ${String(e?.message ?? e)}`.slice(0, 500);
|
|
1417
|
+
}
|
|
1418
|
+
try {
|
|
1419
|
+
await retryFailedAiProposals(admin, Number(s.max_retries_per_run), Number(s.max_retry_attempts));
|
|
1420
|
+
}
|
|
1421
|
+
catch (e) {
|
|
1422
|
+
errors++;
|
|
1423
|
+
if (!errorSample)
|
|
1424
|
+
errorSample = `retry: ${String(e?.message ?? e)}`.slice(0, 500);
|
|
1206
1425
|
}
|
|
1207
1426
|
}
|
|
1208
|
-
return {
|
|
1427
|
+
return {
|
|
1428
|
+
ok: true,
|
|
1429
|
+
runId: firstRunId,
|
|
1430
|
+
processed,
|
|
1431
|
+
inserted,
|
|
1432
|
+
errors,
|
|
1433
|
+
errorSample,
|
|
1434
|
+
connectionId: activeIds[0] ?? allIds[0] ?? null,
|
|
1435
|
+
ignored,
|
|
1436
|
+
connections: allIds.length,
|
|
1437
|
+
};
|
|
1209
1438
|
}
|
|
1210
1439
|
// ─── Testset: filter + extractie zonder voorstel of order ───────────────────
|
|
1211
1440
|
export async function dryRunAnalysis(admin, input) {
|
|
@@ -476,4 +476,33 @@ export declare const getStickerDefaults: import("@tanstack/start-client-core").O
|
|
|
476
476
|
available: true;
|
|
477
477
|
defaults: Record<string, boolean>;
|
|
478
478
|
}>>;
|
|
479
|
+
/** Maakt ontbrekende singleton-instellingen aan (overschrijft nooit iets). Faalt nooit hard. */
|
|
480
|
+
export declare function ensureMailboxDefaults(admin: unknown): Promise<void>;
|
|
481
|
+
export declare const ensureMailboxDefaultsFn: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
482
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
483
|
+
userId: string;
|
|
484
|
+
claims: import("@supabase/auth-js").User;
|
|
485
|
+
}, undefined, undefined, undefined>], undefined, Promise<{
|
|
486
|
+
ok: true;
|
|
487
|
+
}>>;
|
|
488
|
+
export type MailboxStatus = {
|
|
489
|
+
lastOkAt: string | null;
|
|
490
|
+
lastFailAt: string | null;
|
|
491
|
+
lastFailReason: string | null;
|
|
492
|
+
problem: string | null;
|
|
493
|
+
waiting: number;
|
|
494
|
+
aiAgent: boolean;
|
|
495
|
+
addresses: string[];
|
|
496
|
+
ignoredNotFollowed: number;
|
|
497
|
+
ignoredUnknown: number;
|
|
498
|
+
disconnected: {
|
|
499
|
+
email: string;
|
|
500
|
+
count: number;
|
|
501
|
+
}[];
|
|
502
|
+
};
|
|
503
|
+
export declare const getMailboxStatus: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
504
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
505
|
+
userId: string;
|
|
506
|
+
claims: import("@supabase/auth-js").User;
|
|
507
|
+
}, undefined, undefined, undefined>], undefined, Promise<MailboxStatus>>;
|
|
479
508
|
//# sourceMappingURL=mailbox.functions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mailbox.functions.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox.functions.ts"],"names":[],"mappings":"AACA,OAAO,EAAyB,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AA8BnF,eAAO,MAAM,sBAAsB,2GAqB/B,CAAC;AAWL,eAAO,MAAM,cAAc,2EACD,OAAO;;;;;;;;GA+B7B,CAAC;AAGL,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;GAWzB,CAAC;AAEL,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB;;;;8CAEL,OAAO;;;;;GAK7B,CAAC;AAEL,iFAAiF;AACjF,eAAO,MAAM,8BAA8B;;;;;;;;;GAMvC,CAAC;AAML,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;GAWrB,CAAC;AAEL,eAAO,MAAM,YAAY;;;;8CAEC,OAAO;;;;GAa7B,CAAC;AAKL,eAAO,MAAM,aAAa;;;;8CAEA,OAAO;;;;
|
|
1
|
+
{"version":3,"file":"mailbox.functions.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox.functions.ts"],"names":[],"mappings":"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"}
|