@flowselections/floriday-authenticatie-module 1.0.89 → 1.0.91
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/floriday/FlorydayConnectionPage.d.ts.map +1 -1
- package/dist-lib/components/floriday/FlorydayConnectionPage.js +2 -1
- package/dist-lib/components/settings/FloricodeHealthBanner.d.ts +5 -0
- package/dist-lib/components/settings/FloricodeHealthBanner.d.ts.map +1 -0
- package/dist-lib/components/settings/FloricodeHealthBanner.js +27 -0
- package/dist-lib/components/settings/FloricodeSettingsCard.d.ts.map +1 -1
- package/dist-lib/components/settings/FloricodeSettingsCard.js +17 -2
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +1 -0
- package/dist-lib/integrations/supabase/types.d.ts +166 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/cron/sync-floricode-all.d.ts.map +1 -1
- package/dist-lib/lib/cron/sync-floricode-all.js +0 -2
- package/dist-lib/lib/floricode-regulatory-features.d.ts +1 -0
- package/dist-lib/lib/floricode-regulatory-features.d.ts.map +1 -1
- package/dist-lib/lib/floricode-regulatory-features.js +27 -12
- package/dist-lib/lib/floricode-sync-plan.d.ts +64 -0
- package/dist-lib/lib/floricode-sync-plan.d.ts.map +1 -0
- package/dist-lib/lib/floricode-sync-plan.js +54 -0
- package/dist-lib/lib/floricode-sync.server.d.ts +39 -7
- package/dist-lib/lib/floricode-sync.server.d.ts.map +1 -1
- package/dist-lib/lib/floricode-sync.server.js +131 -86
- package/dist-lib/lib/floricode.functions.d.ts +14 -0
- package/dist-lib/lib/floricode.functions.d.ts.map +1 -1
- package/dist-lib/lib/floricode.functions.js +26 -9
- package/dist-lib/lib/gateway/webhook-subscribe.d.ts.map +1 -1
- package/dist-lib/lib/gateway/webhook-subscribe.js +11 -1
- package/dist-lib/lib/module-version.d.ts +1 -1
- package/dist-lib/lib/module-version.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-floricode-all.d.ts","sourceRoot":"","sources":["../../../src/lib/cron/sync-floricode-all.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"sync-floricode-all.d.ts","sourceRoot":"","sources":["../../../src/lib/cron/sync-floricode-all.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,CAqBnB"}
|
|
@@ -10,11 +10,9 @@ export async function handleSyncFloricodeAll(request, supabase) {
|
|
|
10
10
|
return await withJobLock(supabase, "floricode-sync:all", async () => {
|
|
11
11
|
const url = new URL(request.url);
|
|
12
12
|
const batchSize = Number(url.searchParams.get("batchSize") ?? "5000");
|
|
13
|
-
const maxBatches = Number(url.searchParams.get("maxBatchesPerResource") ?? "2");
|
|
14
13
|
const { syncAllFloricodeResources } = await import("../floricode-sync.server.js");
|
|
15
14
|
const result = await syncAllFloricodeResources({
|
|
16
15
|
batchSize: Number.isFinite(batchSize) ? batchSize : 5000,
|
|
17
|
-
maxBatchesPerResource: Number.isFinite(maxBatches) ? maxBatches : 2,
|
|
18
16
|
});
|
|
19
17
|
return Response.json(result);
|
|
20
18
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"floricode-regulatory-features.d.ts","sourceRoot":"","sources":["../../src/lib/floricode-regulatory-features.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"floricode-regulatory-features.d.ts","sourceRoot":"","sources":["../../src/lib/floricode-regulatory-features.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAa5D,UAAU,cAAc;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,kBAAkB,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5C,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,UAAU,cAAc;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAYD,wBAAsB,0CAA0C,CAC9D,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,cAAc,EAAE,CAAC,CAgB3B;AAiDD,wBAAsB,8BAA8B,CAClD,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,cAAc,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,CAqBjB;AAED,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,cAAc,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,CAqBjB;AAiBD,wBAAsB,4BAA4B,CAAC,QAAQ,EAAE,cAAc;;;;;;GA6B1E;AAED,wBAAsB,+BAA+B,CACnD,QAAQ,EAAE,cAAc,EACxB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO;;;;;;;;;;;;;;;;;;;;;;GAgL/F"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getFloricodeAccessToken } from "./floricode-credentials.server.js";
|
|
2
|
+
import { FLORICODE_ROUND_BUDGET_MS, laterIso, stateAfterPage } from "./floricode-sync-plan.js";
|
|
2
3
|
const REG_FEATURE_TYPE_URL = "https://api.floricode.com/v2/VBN/RegulatoryFeatureType";
|
|
3
4
|
const REGULATION_TYPE_URL = "https://api.floricode.com/v2/VBN/RegulationType";
|
|
4
5
|
const REG_FEATURE_TYPE_RESOURCE = "RegulatoryFeatureType";
|
|
@@ -127,6 +128,8 @@ export async function syncFloricodeRegulationTypes(supabase) {
|
|
|
127
128
|
error_message: null,
|
|
128
129
|
last_sequence_number: upserted,
|
|
129
130
|
last_sync_at: new Date().toISOString(),
|
|
131
|
+
completed_at: new Date().toISOString(),
|
|
132
|
+
total_count: items.length,
|
|
130
133
|
});
|
|
131
134
|
return { ok: true, resource: REGULATION_TYPE_RESOURCE, fetched: items.length, upserted, durationMs: Date.now() - startedAt };
|
|
132
135
|
}
|
|
@@ -145,6 +148,9 @@ export async function syncFloricodeRegulatoryFeatures(supabase, options = {}) {
|
|
|
145
148
|
const batchSize = Math.min(Math.max(options.batchSize ?? 5000, 1), 5000);
|
|
146
149
|
const maxBatches = options.maxBatches && options.maxBatches > 0 ? options.maxBatches : Infinity;
|
|
147
150
|
const resume = options.resume !== false;
|
|
151
|
+
const deadline = options.deadline ?? Date.now() + FLORICODE_ROUND_BUDGET_MS;
|
|
152
|
+
// Datum waarmee een lopende ronde verdergaat; pas bij compleet het nieuwe watermerk.
|
|
153
|
+
let pending = null;
|
|
148
154
|
// Read cursor from state so we can resume where the previous invocation stopped.
|
|
149
155
|
let skip = 0;
|
|
150
156
|
let maxChangeDateTime = null;
|
|
@@ -157,14 +163,15 @@ export async function syncFloricodeRegulatoryFeatures(supabase, options = {}) {
|
|
|
157
163
|
if (resume) {
|
|
158
164
|
const { data: state } = await supabase
|
|
159
165
|
.from("floricode_sync_state")
|
|
160
|
-
.select("cursor_skip,last_change_date_time,status")
|
|
166
|
+
.select("cursor_skip,last_change_date_time,pending_change_date_time,status")
|
|
161
167
|
.eq("resource", REG_FEATURE_TYPE_RESOURCE)
|
|
162
168
|
.maybeSingle();
|
|
163
169
|
const s = state;
|
|
164
|
-
if (s?.status === "running") {
|
|
170
|
+
if (s?.status === "running" || s?.status === "error") {
|
|
165
171
|
skip = Number(s.cursor_skip ?? 0) || 0;
|
|
166
172
|
maxChangeDateTime = s.last_change_date_time ?? null;
|
|
167
173
|
sinceIso = s.last_change_date_time ?? null;
|
|
174
|
+
pending = s.pending_change_date_time ?? null;
|
|
168
175
|
}
|
|
169
176
|
else if (s?.status === "ok" && s.last_change_date_time) {
|
|
170
177
|
sinceIso = s.last_change_date_time;
|
|
@@ -181,12 +188,13 @@ export async function syncFloricodeRegulatoryFeatures(supabase, options = {}) {
|
|
|
181
188
|
status: "running",
|
|
182
189
|
error_message: null,
|
|
183
190
|
cursor_skip: skip,
|
|
184
|
-
last_change_date_time:
|
|
191
|
+
last_change_date_time: sinceIso,
|
|
192
|
+
pending_change_date_time: pending,
|
|
185
193
|
last_sync_at: new Date().toISOString(),
|
|
186
194
|
});
|
|
187
195
|
try {
|
|
188
196
|
const token = await getFloricodeToken(REG_FEATURE_TYPE_SCOPE);
|
|
189
|
-
while (batches < maxBatches) {
|
|
197
|
+
while (batches < maxBatches && Date.now() < deadline) {
|
|
190
198
|
const items = await fetchRegFeatureTypePage(token, skip, batchSize, sinceIso);
|
|
191
199
|
if (items.length === 0) {
|
|
192
200
|
completed = true;
|
|
@@ -194,19 +202,22 @@ export async function syncFloricodeRegulatoryFeatures(supabase, options = {}) {
|
|
|
194
202
|
}
|
|
195
203
|
fetched += items.length;
|
|
196
204
|
upserted += await upsertRegulatoryFeatureRecords(supabase, items);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
maxChangeDateTime = changed;
|
|
201
|
-
}
|
|
205
|
+
let pageMax = null;
|
|
206
|
+
for (const item of items)
|
|
207
|
+
pageMax = laterIso(pageMax, item.change_date_time ?? null);
|
|
202
208
|
batches += 1;
|
|
209
|
+
const next = stateAfterPage({ startWatermark: sinceIso, pending, pageMax, skipBefore: skip, pageSize: items.length, batchSize });
|
|
203
210
|
skip += items.length;
|
|
204
|
-
|
|
211
|
+
pending = next.pending_change_date_time ?? laterIso(pending, pageMax);
|
|
212
|
+
maxChangeDateTime = laterIso(sinceIso, pending);
|
|
213
|
+
// Alleen positie + voorlopige datum; het filter (sinceIso) blijft gelijk
|
|
214
|
+
// tot de bron compleet is — anders sloeg hervatten regels over (lus).
|
|
205
215
|
await setSyncState(supabase, REG_FEATURE_TYPE_RESOURCE, {
|
|
206
216
|
status: "running",
|
|
207
217
|
error_message: null,
|
|
208
218
|
cursor_skip: skip,
|
|
209
|
-
last_change_date_time:
|
|
219
|
+
last_change_date_time: sinceIso,
|
|
220
|
+
pending_change_date_time: pending,
|
|
210
221
|
last_sync_at: new Date().toISOString(),
|
|
211
222
|
});
|
|
212
223
|
// Lichte per-product projectie: alleen de zojuist geraakte producten
|
|
@@ -250,7 +261,10 @@ export async function syncFloricodeRegulatoryFeatures(supabase, options = {}) {
|
|
|
250
261
|
cursor_skip: 0,
|
|
251
262
|
last_sequence_number: skip,
|
|
252
263
|
last_change_date_time: maxChangeDateTime,
|
|
264
|
+
pending_change_date_time: null,
|
|
253
265
|
last_sync_at: new Date().toISOString(),
|
|
266
|
+
completed_at: new Date().toISOString(),
|
|
267
|
+
...(incremental ? {} : { total_count: skip }),
|
|
254
268
|
});
|
|
255
269
|
return {
|
|
256
270
|
ok: true,
|
|
@@ -284,7 +298,8 @@ export async function syncFloricodeRegulatoryFeatures(supabase, options = {}) {
|
|
|
284
298
|
status: "error",
|
|
285
299
|
error_message: msg,
|
|
286
300
|
cursor_skip: skip,
|
|
287
|
-
last_change_date_time:
|
|
301
|
+
last_change_date_time: sinceIso,
|
|
302
|
+
pending_change_date_time: pending,
|
|
288
303
|
last_sync_at: new Date().toISOString(),
|
|
289
304
|
});
|
|
290
305
|
throw e;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export declare const FLORICODE_CRON_BUSY = "*/10 * * * *";
|
|
2
|
+
export declare const FLORICODE_CRON_NIGHTLY = "0 2 * * *";
|
|
3
|
+
/** Tijdsbudget per aanroep (Worker); daarna gaat de volgende ronde verder. */
|
|
4
|
+
export declare const FLORICODE_ROUND_BUDGET_MS = 25000;
|
|
5
|
+
export declare const FLORICODE_STALE_MS: number;
|
|
6
|
+
/** Nachtelijke wijzigingenronde: niet vaker dan eens per 20 uur. */
|
|
7
|
+
export declare const FLORICODE_NIGHTLY_MIN_MS: number;
|
|
8
|
+
export declare function laterIso(a: string | null | undefined, b: string | null | undefined): string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Staat na een pagina. De watermerkdatum ("bijgewerkt tot") verschuift alleen
|
|
11
|
+
* als de bron compleet is; tijdens de ronde blijft hij gelijk aan het begin,
|
|
12
|
+
* zodat filter + positie bij hervatten consistent blijven.
|
|
13
|
+
*/
|
|
14
|
+
export declare function stateAfterPage(input: {
|
|
15
|
+
startWatermark: string | null;
|
|
16
|
+
pending: string | null;
|
|
17
|
+
pageMax: string | null;
|
|
18
|
+
skipBefore: number;
|
|
19
|
+
pageSize: number;
|
|
20
|
+
batchSize: number;
|
|
21
|
+
}): {
|
|
22
|
+
finished: true;
|
|
23
|
+
status: "ok";
|
|
24
|
+
cursor_skip: number;
|
|
25
|
+
last_change_date_time: string | null;
|
|
26
|
+
pending_change_date_time: null;
|
|
27
|
+
} | {
|
|
28
|
+
finished: false;
|
|
29
|
+
status: "running";
|
|
30
|
+
cursor_skip: number;
|
|
31
|
+
last_change_date_time: string | null;
|
|
32
|
+
pending_change_date_time: string | null;
|
|
33
|
+
};
|
|
34
|
+
export declare function floricodeCronSchedule(anyIncomplete: boolean): string;
|
|
35
|
+
export declare function isIncomplete(status: string | null | undefined): boolean;
|
|
36
|
+
export declare function nightlyDue(lastCompletedAt: string | null, now: number): boolean;
|
|
37
|
+
export type FloricodeSourceView = {
|
|
38
|
+
kind: "complete";
|
|
39
|
+
stale: boolean;
|
|
40
|
+
} | {
|
|
41
|
+
kind: "busy";
|
|
42
|
+
done: number;
|
|
43
|
+
total: number | null;
|
|
44
|
+
stale: boolean;
|
|
45
|
+
} | {
|
|
46
|
+
kind: "stalled";
|
|
47
|
+
since: string | null;
|
|
48
|
+
stale: boolean;
|
|
49
|
+
} | {
|
|
50
|
+
kind: "error";
|
|
51
|
+
stale: boolean;
|
|
52
|
+
} | {
|
|
53
|
+
kind: "never";
|
|
54
|
+
stale: boolean;
|
|
55
|
+
};
|
|
56
|
+
/** "Compleet" / "Bezig (x van y)" / "Staat stil sinds …"; stale = ouder dan 7 dagen. */
|
|
57
|
+
export declare function classifyFloricodeSource(row: {
|
|
58
|
+
status: string | null;
|
|
59
|
+
lastSyncAt: string | null;
|
|
60
|
+
completedAt?: string | null;
|
|
61
|
+
rowCount: number;
|
|
62
|
+
totalCount?: number | null;
|
|
63
|
+
}, now: number): FloricodeSourceView;
|
|
64
|
+
//# sourceMappingURL=floricode-sync-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"floricode-sync-plan.d.ts","sourceRoot":"","sources":["../../src/lib/floricode-sync-plan.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAClD,eAAO,MAAM,sBAAsB,cAAc,CAAC;AAClD,8EAA8E;AAC9E,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAChD,eAAO,MAAM,kBAAkB,QAAuB,CAAC;AACvD,oEAAoE;AACpE,eAAO,MAAM,wBAAwB,QAAmB,CAAC;AAEzD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAIlG;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE;IACpC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;;;;;;;;;;;;EAOA;AAED,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,OAAO,GAAG,MAAM,CAEpE;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAEvE;AAED,wBAAgB,UAAU,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAG/E;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtC,wFAAwF;AACxF,wBAAgB,uBAAuB,CAAC,GAAG,EAAE;IAC3C,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,EAAE,GAAG,EAAE,MAAM,GAAG,mBAAmB,CASnC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Pure beslisregels voor de Floricode-ronde (testbaar, geen I/O).
|
|
2
|
+
export const FLORICODE_CRON_BUSY = "*/10 * * * *";
|
|
3
|
+
export const FLORICODE_CRON_NIGHTLY = "0 2 * * *";
|
|
4
|
+
/** Tijdsbudget per aanroep (Worker); daarna gaat de volgende ronde verder. */
|
|
5
|
+
export const FLORICODE_ROUND_BUDGET_MS = 25000;
|
|
6
|
+
export const FLORICODE_STALE_MS = 7 * 24 * 60 * 60000;
|
|
7
|
+
/** Nachtelijke wijzigingenronde: niet vaker dan eens per 20 uur. */
|
|
8
|
+
export const FLORICODE_NIGHTLY_MIN_MS = 20 * 60 * 60000;
|
|
9
|
+
export function laterIso(a, b) {
|
|
10
|
+
if (!a)
|
|
11
|
+
return b ?? null;
|
|
12
|
+
if (!b)
|
|
13
|
+
return a;
|
|
14
|
+
return new Date(b).getTime() > new Date(a).getTime() ? b : a;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Staat na een pagina. De watermerkdatum ("bijgewerkt tot") verschuift alleen
|
|
18
|
+
* als de bron compleet is; tijdens de ronde blijft hij gelijk aan het begin,
|
|
19
|
+
* zodat filter + positie bij hervatten consistent blijven.
|
|
20
|
+
*/
|
|
21
|
+
export function stateAfterPage(input) {
|
|
22
|
+
const pending = laterIso(input.pending, input.pageMax);
|
|
23
|
+
const skip = input.skipBefore + input.pageSize;
|
|
24
|
+
const finished = input.pageSize < input.batchSize;
|
|
25
|
+
return finished
|
|
26
|
+
? { finished, status: "ok", cursor_skip: 0, last_change_date_time: laterIso(input.startWatermark, pending), pending_change_date_time: null }
|
|
27
|
+
: { finished, status: "running", cursor_skip: skip, last_change_date_time: input.startWatermark, pending_change_date_time: pending };
|
|
28
|
+
}
|
|
29
|
+
export function floricodeCronSchedule(anyIncomplete) {
|
|
30
|
+
return anyIncomplete ? FLORICODE_CRON_BUSY : FLORICODE_CRON_NIGHTLY;
|
|
31
|
+
}
|
|
32
|
+
export function isIncomplete(status) {
|
|
33
|
+
return status !== "ok" && status !== "success";
|
|
34
|
+
}
|
|
35
|
+
export function nightlyDue(lastCompletedAt, now) {
|
|
36
|
+
if (!lastCompletedAt)
|
|
37
|
+
return true;
|
|
38
|
+
return now - new Date(lastCompletedAt).getTime() >= FLORICODE_NIGHTLY_MIN_MS;
|
|
39
|
+
}
|
|
40
|
+
/** "Compleet" / "Bezig (x van y)" / "Staat stil sinds …"; stale = ouder dan 7 dagen. */
|
|
41
|
+
export function classifyFloricodeSource(row, now) {
|
|
42
|
+
const ref = row.completedAt ?? row.lastSyncAt;
|
|
43
|
+
const stale = !ref || now - new Date(ref).getTime() > FLORICODE_STALE_MS;
|
|
44
|
+
if (!row.lastSyncAt && !row.status)
|
|
45
|
+
return { kind: "never", stale: true };
|
|
46
|
+
if (row.status === "error")
|
|
47
|
+
return { kind: "error", stale };
|
|
48
|
+
if (!isIncomplete(row.status))
|
|
49
|
+
return { kind: "complete", stale };
|
|
50
|
+
const idle = !row.lastSyncAt || now - new Date(row.lastSyncAt).getTime() > 60 * 60000;
|
|
51
|
+
if (idle)
|
|
52
|
+
return { kind: "stalled", since: row.lastSyncAt, stale };
|
|
53
|
+
return { kind: "busy", done: row.rowCount, total: row.totalCount ?? null, stale };
|
|
54
|
+
}
|
|
@@ -16,20 +16,52 @@ export interface FloricodeSyncResult {
|
|
|
16
16
|
nextWatermark: string | null;
|
|
17
17
|
durationMs: number;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Haalt pagina's op tot de bron compleet is of het tijdsbudget op is.
|
|
21
|
+
* Positie (cursor_skip) gaat alleen vooruit na een opgeslagen pagina; de
|
|
22
|
+
* watermerkdatum verschuift pas als de bron compleet is (zie stateAfterPage).
|
|
23
|
+
*/
|
|
19
24
|
export declare function syncFloricodeResource(resource: string, opts?: {
|
|
20
25
|
batchSize?: number;
|
|
21
26
|
maxBatches?: number;
|
|
22
27
|
resume?: boolean;
|
|
28
|
+
deadline?: number;
|
|
23
29
|
}): Promise<FloricodeSyncResult>;
|
|
24
|
-
|
|
30
|
+
type AllResult = FloricodeSyncResult | {
|
|
31
|
+
resource: string;
|
|
32
|
+
ok: false;
|
|
33
|
+
error: string;
|
|
34
|
+
} | {
|
|
35
|
+
resource: string;
|
|
36
|
+
ok: true;
|
|
37
|
+
skipped: true;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Eén cron-tik: eerst onvolledige bronnen afmaken binnen het tijdsbudget;
|
|
41
|
+
* zijn alle bronnen compleet, dan hooguit eens per 20 uur alleen wijzigingen.
|
|
42
|
+
* Stelt daarna het cron-schema in (elke 10 min zolang iets bezig is, anders 's nachts).
|
|
43
|
+
*/
|
|
44
|
+
export declare function syncAllFloricodeResources(opts?: {
|
|
25
45
|
batchSize?: number;
|
|
26
|
-
|
|
46
|
+
budgetMs?: number;
|
|
27
47
|
}): Promise<{
|
|
28
48
|
ok: true;
|
|
29
|
-
results:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
results: AllResult[];
|
|
50
|
+
incomplete: string[];
|
|
51
|
+
schedule: string | null;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Plant de Floricode-ronde in als die nog niet bestaat (uurlijks aangeroepen).
|
|
55
|
+
* Adres via resolveVerifiedFlorydayCronUrl; ontbreekt het Shell-adres, dan
|
|
56
|
+
* niet inplannen (fail closed) — de stilstand-melding wordt daar vastgelegd.
|
|
57
|
+
*/
|
|
58
|
+
export declare function ensureFloricodeSyncCron(opts?: {
|
|
59
|
+
force?: boolean;
|
|
60
|
+
}): Promise<{
|
|
61
|
+
ok: boolean;
|
|
62
|
+
scheduled: boolean;
|
|
63
|
+
schedule: string | null;
|
|
64
|
+
error?: string;
|
|
34
65
|
}>;
|
|
66
|
+
export {};
|
|
35
67
|
//# sourceMappingURL=floricode-sync.server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"floricode-sync.server.d.ts","sourceRoot":"","sources":["../../src/lib/floricode-sync.server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"floricode-sync.server.d.ts","sourceRoot":"","sources":["../../src/lib/floricode-sync.server.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,eAAO,MAAM,mBAAmB,EAAE,oBAAoB,EAkBrD,CAAC;AAoFF,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAC1F,OAAO,CAAC,mBAAmB,CAAC,CAuE9B;AAED,KAAK,SAAS,GAAG,mBAAmB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,IAAI,CAAA;CAAE,CAAC;AAEtI;;;;GAIG;AACH,wBAAsB,yBAAyB,CAAC,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC;IAC7G,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,SAAS,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/E,CAAC,CAuCD;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC;IACrF,EAAE,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CAC1E,CAAC,CAkBD"}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// ============================================================================
|
|
14
14
|
import { supabaseAdmin } from "../integrations/supabase/client.server.js";
|
|
15
15
|
import { getFloricodeAccessToken } from "./floricode-credentials.server.js";
|
|
16
|
+
import { FLORICODE_ROUND_BUDGET_MS, floricodeCronSchedule, isIncomplete, laterIso, nightlyDue, stateAfterPage, } from "./floricode-sync-plan.js";
|
|
16
17
|
export const FLORICODE_RESOURCES = [
|
|
17
18
|
// VBN
|
|
18
19
|
{ resource: "FeatureType", endpoint: "/v2/VBN/FeatureType", scope: "vbn_featuretype:read", keyColumns: ["id"] },
|
|
@@ -38,14 +39,23 @@ function buildRecordId(def, row) {
|
|
|
38
39
|
return def.keyColumns.map((k) => String(row[k] ?? "")).join("|");
|
|
39
40
|
}
|
|
40
41
|
async function fetchPage(def, token, opts) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
const build = (withCount) => {
|
|
43
|
+
const url = new URL(BASE_URL + def.endpoint);
|
|
44
|
+
url.searchParams.set("$top", String(opts.top));
|
|
45
|
+
url.searchParams.set("$skip", String(opts.skip));
|
|
46
|
+
url.searchParams.set("$orderby", ["change_date_time", ...def.keyColumns].join(","));
|
|
47
|
+
if (opts.sinceIso)
|
|
48
|
+
url.searchParams.set("$filter", `change_date_time gt ${opts.sinceIso}`);
|
|
49
|
+
if (withCount)
|
|
50
|
+
url.searchParams.set("$count", "true");
|
|
51
|
+
return url.toString();
|
|
52
|
+
};
|
|
53
|
+
// Totaal alleen op de eerste pagina vragen; ondersteunt de API $count niet, dan zonder.
|
|
54
|
+
const wantCount = opts.skip === 0;
|
|
55
|
+
let res = await fetch(build(wantCount), { headers: { authorization: `Bearer ${token}` } });
|
|
56
|
+
if (wantCount && res.status === 400) {
|
|
57
|
+
res = await fetch(build(false), { headers: { authorization: `Bearer ${token}` } });
|
|
47
58
|
}
|
|
48
|
-
const res = await fetch(url.toString(), { headers: { authorization: `Bearer ${token}` } });
|
|
49
59
|
const json = await res.json().catch(() => ({}));
|
|
50
60
|
if (!res.ok) {
|
|
51
61
|
const upstream = json?.error?.message ?? json?.error ?? res.statusText;
|
|
@@ -57,7 +67,8 @@ async function fetchPage(def, token, opts) {
|
|
|
57
67
|
}
|
|
58
68
|
throw new Error(`Floricode ${def.resource} pagina mislukt (${res.status}): ${upstream}`);
|
|
59
69
|
}
|
|
60
|
-
|
|
70
|
+
const c = Number(json?.["@odata.count"]);
|
|
71
|
+
return { rows: Array.isArray(json?.value) ? json.value : [], total: Number.isFinite(c) ? c : null };
|
|
61
72
|
}
|
|
62
73
|
async function upsertRows(def, rows) {
|
|
63
74
|
if (rows.length === 0)
|
|
@@ -95,117 +106,151 @@ async function upsertRows(def, rows) {
|
|
|
95
106
|
}
|
|
96
107
|
return done;
|
|
97
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Haalt pagina's op tot de bron compleet is of het tijdsbudget op is.
|
|
111
|
+
* Positie (cursor_skip) gaat alleen vooruit na een opgeslagen pagina; de
|
|
112
|
+
* watermerkdatum verschuift pas als de bron compleet is (zie stateAfterPage).
|
|
113
|
+
*/
|
|
98
114
|
export async function syncFloricodeResource(resource, opts = {}) {
|
|
99
115
|
const def = FLORICODE_RESOURCES.find((r) => r.resource === resource);
|
|
100
116
|
if (!def)
|
|
101
117
|
throw new Error(`Onbekende Floricode-resource: ${resource}`);
|
|
102
118
|
const batchSize = opts.batchSize ?? DEFAULT_BATCH;
|
|
103
|
-
const maxBatches =
|
|
119
|
+
const maxBatches = opts.maxBatches && opts.maxBatches > 0 ? opts.maxBatches : Infinity;
|
|
120
|
+
const deadline = opts.deadline ?? Date.now() + FLORICODE_ROUND_BUDGET_MS;
|
|
104
121
|
const resume = opts.resume ?? true;
|
|
105
122
|
const startedAt = Date.now();
|
|
106
123
|
const { data: state } = await supabaseAdmin
|
|
107
124
|
.from("floricode_sync_state")
|
|
108
|
-
.select("cursor_skip, last_change_date_time")
|
|
125
|
+
.select("cursor_skip, last_change_date_time, pending_change_date_time, status")
|
|
109
126
|
.eq("resource", def.resource)
|
|
110
127
|
.maybeSingle();
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
128
|
+
const s = state;
|
|
129
|
+
const resuming = resume && (s?.status === "running" || s?.status === "error");
|
|
130
|
+
let skip = resuming ? Number(s?.cursor_skip ?? 0) : 0;
|
|
131
|
+
const sinceIso = s?.last_change_date_time ?? null;
|
|
132
|
+
let pending = resuming ? s?.pending_change_date_time ?? null : null;
|
|
133
|
+
const save = async (patch) => {
|
|
134
|
+
const { error } = await supabaseAdmin.from("floricode_sync_state").upsert({ resource: def.resource, updated_at: new Date().toISOString(), ...patch }, { onConflict: "resource" });
|
|
135
|
+
if (error)
|
|
136
|
+
throw new Error(`Status ${def.resource} opslaan mislukt: ${error.message}`);
|
|
137
|
+
};
|
|
138
|
+
await save({ status: "running", error_message: null, cursor_skip: skip, pending_change_date_time: pending });
|
|
122
139
|
let fetched = 0;
|
|
123
140
|
let upserted = 0;
|
|
124
141
|
let batches = 0;
|
|
125
|
-
let latestChange = sinceIso;
|
|
126
142
|
let finished = false;
|
|
143
|
+
let watermark = sinceIso;
|
|
127
144
|
try {
|
|
128
|
-
|
|
129
|
-
|
|
145
|
+
const token = await getFloricodeAccessToken(def.scope);
|
|
146
|
+
while (batches < maxBatches && Date.now() < deadline) {
|
|
147
|
+
const { rows: page, total } = await fetchPage(def, token, { skip, top: batchSize, sinceIso });
|
|
130
148
|
batches++;
|
|
131
|
-
if (page.length === 0) {
|
|
132
|
-
finished = true;
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
149
|
fetched += page.length;
|
|
136
150
|
upserted += await upsertRows(def, page);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// Worker-timeout hervat vanaf de laatste geslaagde pagina.
|
|
145
|
-
await supabaseAdmin
|
|
146
|
-
.from("floricode_sync_state")
|
|
147
|
-
.upsert({
|
|
148
|
-
resource: def.resource,
|
|
149
|
-
status: "running",
|
|
151
|
+
let pageMax = null;
|
|
152
|
+
for (const row of page)
|
|
153
|
+
pageMax = laterIso(pageMax, row?.change_date_time ?? null);
|
|
154
|
+
const next = stateAfterPage({ startWatermark: sinceIso, pending, pageMax, skipBefore: skip, pageSize: page.length, batchSize });
|
|
155
|
+
const now = new Date().toISOString();
|
|
156
|
+
await save({
|
|
157
|
+
status: next.status,
|
|
150
158
|
error_message: null,
|
|
151
|
-
cursor_skip:
|
|
152
|
-
last_change_date_time:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
159
|
+
cursor_skip: next.cursor_skip,
|
|
160
|
+
last_change_date_time: next.last_change_date_time,
|
|
161
|
+
pending_change_date_time: next.pending_change_date_time,
|
|
162
|
+
last_sync_at: now,
|
|
163
|
+
...(total != null && !sinceIso ? { total_count: total } : {}),
|
|
164
|
+
...(next.finished ? { completed_at: now } : {}),
|
|
165
|
+
});
|
|
166
|
+
skip = next.cursor_skip;
|
|
167
|
+
pending = next.pending_change_date_time;
|
|
168
|
+
watermark = next.last_change_date_time;
|
|
169
|
+
if (next.finished) {
|
|
157
170
|
finished = true;
|
|
158
171
|
break;
|
|
159
172
|
}
|
|
160
173
|
}
|
|
161
|
-
await supabaseAdmin
|
|
162
|
-
.from("floricode_sync_state")
|
|
163
|
-
.upsert({
|
|
164
|
-
resource: def.resource,
|
|
165
|
-
status: "ok",
|
|
166
|
-
error_message: null,
|
|
167
|
-
last_sync_at: new Date().toISOString(),
|
|
168
|
-
cursor_skip: finished ? 0 : skip,
|
|
169
|
-
last_change_date_time: finished ? latestChange : sinceIso,
|
|
170
|
-
updated_at: new Date().toISOString(),
|
|
171
|
-
}, { onConflict: "resource" });
|
|
172
174
|
}
|
|
173
175
|
catch (e) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
.upsert({
|
|
177
|
-
resource: def.resource,
|
|
178
|
-
status: "error",
|
|
179
|
-
error_message: String(e?.message ?? e),
|
|
180
|
-
cursor_skip: skip,
|
|
181
|
-
updated_at: new Date().toISOString(),
|
|
182
|
-
}, { onConflict: "resource" });
|
|
176
|
+
// Positie blijft op de laatste gelukte pagina; status 'error', volgende ronde hervat.
|
|
177
|
+
await save({ status: "error", error_message: String(e?.message ?? e) }).catch(() => { });
|
|
183
178
|
throw e;
|
|
184
179
|
}
|
|
185
|
-
return {
|
|
186
|
-
ok: true,
|
|
187
|
-
resource: def.resource,
|
|
188
|
-
fetched,
|
|
189
|
-
upserted,
|
|
190
|
-
batches,
|
|
191
|
-
finished,
|
|
192
|
-
nextWatermark: latestChange,
|
|
193
|
-
durationMs: Date.now() - startedAt,
|
|
194
|
-
};
|
|
180
|
+
return { ok: true, resource: def.resource, fetched, upserted, batches, finished, nextWatermark: watermark, durationMs: Date.now() - startedAt };
|
|
195
181
|
}
|
|
196
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Eén cron-tik: eerst onvolledige bronnen afmaken binnen het tijdsbudget;
|
|
184
|
+
* zijn alle bronnen compleet, dan hooguit eens per 20 uur alleen wijzigingen.
|
|
185
|
+
* Stelt daarna het cron-schema in (elke 10 min zolang iets bezig is, anders 's nachts).
|
|
186
|
+
*/
|
|
187
|
+
export async function syncAllFloricodeResources(opts = {}) {
|
|
188
|
+
const deadline = Date.now() + (opts.budgetMs ?? FLORICODE_ROUND_BUDGET_MS);
|
|
189
|
+
const all = [...FLORICODE_RESOURCES.map((r) => r.resource), "RegulatoryFeatureType", "RegulationType"];
|
|
190
|
+
const readStates = async () => {
|
|
191
|
+
const { data } = await supabaseAdmin.from("floricode_sync_state").select("resource,status,completed_at,last_sync_at").in("resource", all);
|
|
192
|
+
return new Map((data ?? []).map((r) => [r.resource, r]));
|
|
193
|
+
};
|
|
194
|
+
const states = await readStates();
|
|
195
|
+
const incompleteBefore = all.filter((r) => isIncomplete(states.get(r)?.status));
|
|
196
|
+
const nightly = incompleteBefore.length === 0 &&
|
|
197
|
+
all.some((r) => nightlyDue(states.get(r)?.completed_at ?? states.get(r)?.last_sync_at ?? null, Date.now()));
|
|
198
|
+
const todo = incompleteBefore.length > 0 ? incompleteBefore : nightly ? all : [];
|
|
197
199
|
const results = [];
|
|
198
|
-
|
|
200
|
+
const sb = supabaseAdmin;
|
|
201
|
+
for (const resource of todo) {
|
|
202
|
+
if (Date.now() >= deadline)
|
|
203
|
+
break;
|
|
199
204
|
try {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
+
if (resource === "RegulationType") {
|
|
206
|
+
const { syncFloricodeRegulationTypes } = await import("./floricode-regulatory-features.js");
|
|
207
|
+
const r = await syncFloricodeRegulationTypes(sb);
|
|
208
|
+
results.push({ resource, ok: true, fetched: r?.upserted ?? 0, upserted: r?.upserted ?? 0, batches: 1, finished: true, nextWatermark: null, durationMs: 0 });
|
|
209
|
+
}
|
|
210
|
+
else if (resource === "RegulatoryFeatureType") {
|
|
211
|
+
const { syncFloricodeRegulatoryFeatures } = await import("./floricode-regulatory-features.js");
|
|
212
|
+
const r = await syncFloricodeRegulatoryFeatures(sb, { deadline });
|
|
213
|
+
results.push({ resource, ok: true, fetched: r.fetched, upserted: r.upserted, batches: r.batches, finished: r.completed, nextWatermark: null, durationMs: r.durationMs });
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
results.push(await syncFloricodeResource(resource, { batchSize: opts.batchSize, deadline }));
|
|
217
|
+
}
|
|
205
218
|
}
|
|
206
219
|
catch (e) {
|
|
207
|
-
results.push({ resource
|
|
220
|
+
results.push({ resource, ok: false, error: String(e?.message ?? e) });
|
|
208
221
|
}
|
|
209
222
|
}
|
|
210
|
-
|
|
223
|
+
const after = await readStates();
|
|
224
|
+
const incomplete = all.filter((r) => isIncomplete(after.get(r)?.status));
|
|
225
|
+
const schedule = floricodeCronSchedule(incomplete.length > 0);
|
|
226
|
+
const { error } = await supabaseAdmin.rpc("floricode_sync_cron_set_schedule", { p_schedule: schedule });
|
|
227
|
+
return { ok: true, results, incomplete, schedule: error ? null : schedule };
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Plant de Floricode-ronde in als die nog niet bestaat (uurlijks aangeroepen).
|
|
231
|
+
* Adres via resolveVerifiedFlorydayCronUrl; ontbreekt het Shell-adres, dan
|
|
232
|
+
* niet inplannen (fail closed) — de stilstand-melding wordt daar vastgelegd.
|
|
233
|
+
*/
|
|
234
|
+
export async function ensureFloricodeSyncCron(opts = {}) {
|
|
235
|
+
const { data } = await supabaseAdmin.rpc("floricode_sync_cron_status");
|
|
236
|
+
const row = Array.isArray(data) ? data[0] : data;
|
|
237
|
+
if (row?.scheduled && !opts.force)
|
|
238
|
+
return { ok: true, scheduled: true, schedule: row.schedule ?? null };
|
|
239
|
+
const apikey = process.env.SUPABASE_PUBLISHABLE_KEY;
|
|
240
|
+
if (!apikey)
|
|
241
|
+
return { ok: false, scheduled: false, schedule: null, error: "SUPABASE_PUBLISHABLE_KEY ontbreekt" };
|
|
242
|
+
try {
|
|
243
|
+
const { resolveVerifiedFlorydayCronUrl } = await import("./floriday.functions.js");
|
|
244
|
+
const url = await resolveVerifiedFlorydayCronUrl("/api/public/cron/sync-floricode-all");
|
|
245
|
+
const { data: st } = await supabaseAdmin.from("floricode_sync_state").select("status");
|
|
246
|
+
const anyIncomplete = (st ?? []).length === 0 || (st ?? []).some((r) => isIncomplete(r.status));
|
|
247
|
+
const schedule = floricodeCronSchedule(anyIncomplete);
|
|
248
|
+
const { error } = await supabaseAdmin.rpc("floricode_sync_cron_schedule", { p_url: url, p_apikey: apikey, p_schedule: schedule });
|
|
249
|
+
if (error)
|
|
250
|
+
return { ok: false, scheduled: false, schedule: null, error: error.message };
|
|
251
|
+
return { ok: true, scheduled: true, schedule };
|
|
252
|
+
}
|
|
253
|
+
catch (e) {
|
|
254
|
+
return { ok: false, scheduled: false, schedule: null, error: String(e?.message ?? e) };
|
|
255
|
+
}
|
|
211
256
|
}
|
|
@@ -46,6 +46,8 @@ export interface FloricodeResourceStatus {
|
|
|
46
46
|
cursorSkip: number | null;
|
|
47
47
|
errorMessage: string | null;
|
|
48
48
|
rowCount: number;
|
|
49
|
+
completedAt: string | null;
|
|
50
|
+
totalCount: number | null;
|
|
49
51
|
}
|
|
50
52
|
export declare const getFloricodeSyncStatus: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
51
53
|
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
@@ -69,4 +71,16 @@ export declare const resetFloricodeResourceCursor: import("@tanstack/start-clien
|
|
|
69
71
|
ok: true;
|
|
70
72
|
resource: string;
|
|
71
73
|
}>>;
|
|
74
|
+
/** Stand van de automatische Floricode-ronde (ingepland? Shell-adres?). */
|
|
75
|
+
export declare const getFloricodeCronStatus: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
76
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
77
|
+
userId: string;
|
|
78
|
+
claims: import("@supabase/auth-js").JwtPayload;
|
|
79
|
+
}, undefined, undefined, undefined>], undefined, Promise<{
|
|
80
|
+
scheduled: boolean;
|
|
81
|
+
schedule: string | null;
|
|
82
|
+
lastRunAt: string | null;
|
|
83
|
+
lastRunStatus: string | null;
|
|
84
|
+
shellAddressMissing: boolean;
|
|
85
|
+
}>>;
|
|
72
86
|
//# sourceMappingURL=floricode.functions.d.ts.map
|