@flowselections/floriday-authenticatie-module 1.0.17 → 1.0.18

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.
Files changed (62) hide show
  1. package/dist-lib/components/floriday/FlorydayConnectionPage.d.ts.map +1 -1
  2. package/dist-lib/components/floriday/FlorydayConnectionPage.js +2 -1
  3. package/dist-lib/components/settings/FloricodeSettingsCard.d.ts +2 -0
  4. package/dist-lib/components/settings/FloricodeSettingsCard.d.ts.map +1 -0
  5. package/dist-lib/components/settings/FloricodeSettingsCard.js +235 -0
  6. package/dist-lib/index.d.ts.map +1 -1
  7. package/dist-lib/index.js +2 -0
  8. package/dist-lib/integrations/supabase/auth-middleware.d.ts +484 -3
  9. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  10. package/dist-lib/integrations/supabase/client.d.ts +484 -3
  11. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  12. package/dist-lib/integrations/supabase/client.server.d.ts +484 -3
  13. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  14. package/dist-lib/integrations/supabase/types.d.ts +492 -3
  15. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  16. package/dist-lib/lib/floricode-credentials.server.d.ts +21 -0
  17. package/dist-lib/lib/floricode-credentials.server.d.ts.map +1 -0
  18. package/dist-lib/lib/floricode-credentials.server.js +158 -0
  19. package/dist-lib/lib/floricode-product-features.d.ts.map +1 -1
  20. package/dist-lib/lib/floricode-product-features.js +19 -33
  21. package/dist-lib/lib/floricode-regulatory-features.d.ts.map +1 -1
  22. package/dist-lib/lib/floricode-regulatory-features.js +51 -41
  23. package/dist-lib/lib/floricode-sync.server.d.ts +35 -0
  24. package/dist-lib/lib/floricode-sync.server.d.ts.map +1 -0
  25. package/dist-lib/lib/floricode-sync.server.js +211 -0
  26. package/dist-lib/lib/floricode.functions.d.ts +78555 -0
  27. package/dist-lib/lib/floricode.functions.d.ts.map +1 -0
  28. package/dist-lib/lib/floricode.functions.js +178 -0
  29. package/dist-lib/lib/floriday-auction-letters.functions.d.ts +484 -3
  30. package/dist-lib/lib/floriday-auction-letters.functions.d.ts.map +1 -1
  31. package/dist-lib/lib/floriday-auto-process.server.d.ts +34 -0
  32. package/dist-lib/lib/floriday-auto-process.server.d.ts.map +1 -0
  33. package/dist-lib/lib/floriday-auto-process.server.js +365 -0
  34. package/dist-lib/lib/floriday-fulfillment-lookup.server.d.ts +19 -0
  35. package/dist-lib/lib/floriday-fulfillment-lookup.server.d.ts.map +1 -0
  36. package/dist-lib/lib/floriday-fulfillment-lookup.server.js +130 -0
  37. package/dist-lib/lib/floriday-gateway-auth.d.ts +24 -0
  38. package/dist-lib/lib/floriday-gateway-auth.d.ts.map +1 -0
  39. package/dist-lib/lib/floriday-gateway-auth.js +127 -0
  40. package/dist-lib/lib/floriday-labels.functions.d.ts +484 -3
  41. package/dist-lib/lib/floriday-labels.functions.d.ts.map +1 -1
  42. package/dist-lib/lib/floriday-labels.functions.js +3 -2
  43. package/dist-lib/lib/floriday-public.functions.d.ts +2904 -18
  44. package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
  45. package/dist-lib/lib/floriday-rate-limit.server.d.ts +21 -0
  46. package/dist-lib/lib/floriday-rate-limit.server.d.ts.map +1 -0
  47. package/dist-lib/lib/floriday-rate-limit.server.js +75 -0
  48. package/dist-lib/lib/floriday-stickers.functions.d.ts +484 -3
  49. package/dist-lib/lib/floriday-stickers.functions.d.ts.map +1 -1
  50. package/dist-lib/lib/floriday-sync.functions.d.ts +15972 -99
  51. package/dist-lib/lib/floriday-sync.functions.d.ts.map +1 -1
  52. package/dist-lib/lib/floriday-writes.functions.d.ts +2952 -42
  53. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  54. package/dist-lib/lib/floriday-writes.functions.js +29 -4
  55. package/dist-lib/lib/floriday.functions.d.ts +9054 -198
  56. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  57. package/dist-lib/lib/floriday.functions.js +39 -4
  58. package/dist-lib/lib/planning-helpers.d.ts +30 -0
  59. package/dist-lib/lib/planning-helpers.d.ts.map +1 -0
  60. package/dist-lib/lib/planning-helpers.js +62 -0
  61. package/package.json +3 -2
  62. package/public/flowselections-assets/floriday-authenticatie/floricode-logo.png +0 -0
@@ -0,0 +1,211 @@
1
+ // ============================================================================
2
+ // Floricode generic incremental sync engine.
3
+ // ----------------------------------------------------------------------------
4
+ // One resource registry. Each entry describes:
5
+ // - REST endpoint on api.floricode.com
6
+ // - OAuth scope to request
7
+ // - unique-key columns (used both for `record_id` derivation and $orderby)
8
+ // The engine reads a per-resource cursor from `floricode_sync_state`:
9
+ // - `last_change_date_time` is the incremental watermark
10
+ // - `cursor_skip` handles paging WITHIN a single watermark run
11
+ // When a run finishes cleanly, watermark advances and cursor resets.
12
+ // Rows land in `public.floricode_records` with (resource, record_id) as key.
13
+ // ============================================================================
14
+ import { supabaseAdmin } from "../integrations/supabase/client.server";
15
+ import { getFloricodeAccessToken } from "./floricode-credentials.server";
16
+ export const FLORICODE_RESOURCES = [
17
+ // VBN
18
+ { resource: "FeatureType", endpoint: "/v2/VBN/FeatureType", scope: "vbn_featuretype:read", keyColumns: ["id"] },
19
+ { resource: "FeatureValue", endpoint: "/v2/VBN/FeatureValue", scope: "vbn_featurevalue:read", keyColumns: ["feature_type_id", "code"] },
20
+ { resource: "VbnProduct", endpoint: "/v2/VBN/Product", scope: "vbn_product:read", keyColumns: ["id"] },
21
+ { resource: "PackagingType", endpoint: "/v2/VBN/PackagingType", scope: "vbn_packagingtype:read", keyColumns: ["id"] },
22
+ // Plant/Packaging hebben géén `id`-veld → composite keys uit de EF-modellen.
23
+ { resource: "BotanicalName", endpoint: "/v2/VBN/Plant", scope: "vbn_plant:read", keyColumns: ["registrator_id", "taxonomic_number", "entry_date"] },
24
+ { resource: "LoadCarrier", endpoint: "/v2/VBN/Packaging", scope: "vbn_packaging:read", keyColumns: ["code"] },
25
+ // FEC (voorheen CAO — Floricode heeft de company-namespace verhuisd)
26
+ { resource: "Company", endpoint: "/v2/FEC/Company", scope: "fec_company:read", keyColumns: ["id"] },
27
+ { resource: "CompanyLocation", endpoint: "/v2/FEC/CompanyLocation", scope: "fec_location:read", keyColumns: ["location_id"] },
28
+ { resource: "CompanyRole", endpoint: "/v2/FEC/CompanyRole", scope: "fec_companyrole:read", keyColumns: ["id"] },
29
+ { resource: "CompanyLevel", endpoint: "/v2/FEC/CompanyLevel", scope: "fec_companylevel:read", keyColumns: ["id"] },
30
+ { resource: "LocationType", endpoint: "/v2/FEC/LocationType", scope: "fec_locationtype:read", keyColumns: ["id"] },
31
+ // ISO/FLC-resources zijn verwijderd: Floricode geeft de bijbehorende scopes
32
+ // (`iso_country:read`, `flc_*`) niet vrij aan onze Client ID. Weer aanzetten
33
+ // zodra Floricode support die scopes toekent.
34
+ ];
35
+ const BASE_URL = "https://api.floricode.com";
36
+ const DEFAULT_BATCH = 5000;
37
+ function buildRecordId(def, row) {
38
+ return def.keyColumns.map((k) => String(row[k] ?? "")).join("|");
39
+ }
40
+ async function fetchPage(def, token, opts) {
41
+ const url = new URL(BASE_URL + def.endpoint);
42
+ url.searchParams.set("$top", String(opts.top));
43
+ url.searchParams.set("$skip", String(opts.skip));
44
+ url.searchParams.set("$orderby", ["change_date_time", ...def.keyColumns].join(","));
45
+ if (opts.sinceIso) {
46
+ url.searchParams.set("$filter", `change_date_time gt ${opts.sinceIso}`);
47
+ }
48
+ const res = await fetch(url.toString(), { headers: { authorization: `Bearer ${token}` } });
49
+ const json = await res.json().catch(() => ({}));
50
+ if (!res.ok) {
51
+ const upstream = json?.error?.message ?? json?.error ?? res.statusText;
52
+ if (res.status === 404) {
53
+ throw new Error(`Floricode ${def.resource} endpoint niet beschikbaar (404) op ${def.endpoint}. Controleer of de scope '${def.scope}' voor deze Client ID is geactiveerd in het Floricode-portaal.`);
54
+ }
55
+ if (res.status === 401 || res.status === 403) {
56
+ throw new Error(`Floricode ${def.resource} niet geautoriseerd (${res.status}). Scope '${def.scope}' is niet toegekend aan de huidige Client ID.`);
57
+ }
58
+ throw new Error(`Floricode ${def.resource} pagina mislukt (${res.status}): ${upstream}`);
59
+ }
60
+ return Array.isArray(json?.value) ? json.value : [];
61
+ }
62
+ async function upsertRows(def, rows) {
63
+ if (rows.length === 0)
64
+ return 0;
65
+ const now = new Date().toISOString();
66
+ // In-batch dedupe op composite record_id. Floricode kan binnen één pagina
67
+ // dubbele sleutels teruggeven; zonder dedupe crasht de upsert met
68
+ // "ON CONFLICT DO UPDATE command cannot affect row a second time".
69
+ const dedup = new Map();
70
+ for (const r of rows) {
71
+ const record_id = buildRecordId(def, r);
72
+ if (!record_id)
73
+ continue;
74
+ dedup.set(record_id, {
75
+ resource: def.resource,
76
+ record_id,
77
+ data: r,
78
+ change_date_time: r?.change_date_time ?? null,
79
+ last_synced_at: now,
80
+ });
81
+ }
82
+ const payload = Array.from(dedup.values());
83
+ // Kleine chunks (100) om Supabase Disk IO-pieken tijdens de initiële
84
+ // backfill te dempen; grote JSONB-upserts drukken WAL + toast zwaar.
85
+ const CHUNK = 100;
86
+ let done = 0;
87
+ for (let i = 0; i < payload.length; i += CHUNK) {
88
+ const part = payload.slice(i, i + CHUNK);
89
+ const { error } = await supabaseAdmin
90
+ .from("floricode_records")
91
+ .upsert(part, { onConflict: "resource,record_id" });
92
+ if (error)
93
+ throw new Error(`Upsert ${def.resource} mislukt: ${error.message}`);
94
+ done += part.length;
95
+ }
96
+ return done;
97
+ }
98
+ export async function syncFloricodeResource(resource, opts = {}) {
99
+ const def = FLORICODE_RESOURCES.find((r) => r.resource === resource);
100
+ if (!def)
101
+ throw new Error(`Onbekende Floricode-resource: ${resource}`);
102
+ const batchSize = opts.batchSize ?? DEFAULT_BATCH;
103
+ const maxBatches = Math.max(1, opts.maxBatches ?? 3);
104
+ const resume = opts.resume ?? true;
105
+ const startedAt = Date.now();
106
+ const { data: state } = await supabaseAdmin
107
+ .from("floricode_sync_state")
108
+ .select("cursor_skip, last_change_date_time")
109
+ .eq("resource", def.resource)
110
+ .maybeSingle();
111
+ let skip = resume ? Number(state?.cursor_skip ?? 0) : 0;
112
+ const sinceIso = state?.last_change_date_time ?? null;
113
+ await supabaseAdmin
114
+ .from("floricode_sync_state")
115
+ .upsert({
116
+ resource: def.resource,
117
+ status: "running",
118
+ error_message: null,
119
+ updated_at: new Date().toISOString(),
120
+ }, { onConflict: "resource" });
121
+ const token = await getFloricodeAccessToken(def.scope);
122
+ let fetched = 0;
123
+ let upserted = 0;
124
+ let batches = 0;
125
+ let latestChange = sinceIso;
126
+ let finished = false;
127
+ try {
128
+ for (let i = 0; i < maxBatches; i++) {
129
+ const page = await fetchPage(def, token, { skip, top: batchSize, sinceIso });
130
+ batches++;
131
+ if (page.length === 0) {
132
+ finished = true;
133
+ break;
134
+ }
135
+ fetched += page.length;
136
+ upserted += await upsertRows(def, page);
137
+ for (const row of page) {
138
+ const cd = row?.change_date_time;
139
+ if (cd && (!latestChange || cd > latestChange))
140
+ latestChange = cd;
141
+ }
142
+ skip += page.length;
143
+ // Cursor + watermark per batch persistent maken zodat een crash of
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",
150
+ error_message: null,
151
+ cursor_skip: skip,
152
+ last_change_date_time: latestChange,
153
+ last_sync_at: new Date().toISOString(),
154
+ updated_at: new Date().toISOString(),
155
+ }, { onConflict: "resource" });
156
+ if (page.length < batchSize) {
157
+ finished = true;
158
+ break;
159
+ }
160
+ }
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
+ }
173
+ catch (e) {
174
+ await supabaseAdmin
175
+ .from("floricode_sync_state")
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" });
183
+ throw e;
184
+ }
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
+ };
195
+ }
196
+ export async function syncAllFloricodeResources(opts) {
197
+ const results = [];
198
+ for (const def of FLORICODE_RESOURCES) {
199
+ try {
200
+ const r = await syncFloricodeResource(def.resource, {
201
+ batchSize: opts.batchSize,
202
+ maxBatches: opts.maxBatchesPerResource ?? 2,
203
+ });
204
+ results.push(r);
205
+ }
206
+ catch (e) {
207
+ results.push({ resource: def.resource, ok: false, error: String(e?.message ?? e) });
208
+ }
209
+ }
210
+ return { ok: true, results };
211
+ }