@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.
- 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/FloricodeSettingsCard.d.ts +2 -0
- package/dist-lib/components/settings/FloricodeSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/FloricodeSettingsCard.js +235 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +2 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +484 -3
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +484 -3
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +484 -3
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +492 -3
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/floricode-credentials.server.d.ts +21 -0
- package/dist-lib/lib/floricode-credentials.server.d.ts.map +1 -0
- package/dist-lib/lib/floricode-credentials.server.js +158 -0
- package/dist-lib/lib/floricode-product-features.d.ts.map +1 -1
- package/dist-lib/lib/floricode-product-features.js +19 -33
- package/dist-lib/lib/floricode-regulatory-features.d.ts.map +1 -1
- package/dist-lib/lib/floricode-regulatory-features.js +51 -41
- package/dist-lib/lib/floricode-sync.server.d.ts +35 -0
- package/dist-lib/lib/floricode-sync.server.d.ts.map +1 -0
- package/dist-lib/lib/floricode-sync.server.js +211 -0
- package/dist-lib/lib/floricode.functions.d.ts +78555 -0
- package/dist-lib/lib/floricode.functions.d.ts.map +1 -0
- package/dist-lib/lib/floricode.functions.js +178 -0
- package/dist-lib/lib/floriday-auction-letters.functions.d.ts +484 -3
- package/dist-lib/lib/floriday-auction-letters.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-auto-process.server.d.ts +34 -0
- package/dist-lib/lib/floriday-auto-process.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday-auto-process.server.js +365 -0
- package/dist-lib/lib/floriday-fulfillment-lookup.server.d.ts +19 -0
- package/dist-lib/lib/floriday-fulfillment-lookup.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday-fulfillment-lookup.server.js +130 -0
- package/dist-lib/lib/floriday-gateway-auth.d.ts +24 -0
- package/dist-lib/lib/floriday-gateway-auth.d.ts.map +1 -0
- package/dist-lib/lib/floriday-gateway-auth.js +127 -0
- package/dist-lib/lib/floriday-labels.functions.d.ts +484 -3
- package/dist-lib/lib/floriday-labels.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-labels.functions.js +3 -2
- package/dist-lib/lib/floriday-public.functions.d.ts +2904 -18
- package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-rate-limit.server.d.ts +21 -0
- package/dist-lib/lib/floriday-rate-limit.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday-rate-limit.server.js +75 -0
- package/dist-lib/lib/floriday-stickers.functions.d.ts +484 -3
- package/dist-lib/lib/floriday-stickers.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-sync.functions.d.ts +15972 -99
- package/dist-lib/lib/floriday-sync.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-writes.functions.d.ts +2952 -42
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-writes.functions.js +29 -4
- package/dist-lib/lib/floriday.functions.d.ts +9054 -198
- package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday.functions.js +39 -4
- package/dist-lib/lib/planning-helpers.d.ts +30 -0
- package/dist-lib/lib/planning-helpers.d.ts.map +1 -0
- package/dist-lib/lib/planning-helpers.js +62 -0
- package/package.json +3 -2
- 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
|
+
}
|