@flowselections/floriday-authenticatie-module 1.0.31 → 1.0.35

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 (54) hide show
  1. package/dist-lib/components/TemplatePage.d.ts +1 -1
  2. package/dist-lib/components/TemplatePage.d.ts.map +1 -1
  3. package/dist-lib/components/floriday/FlorydayConnectionPage.d.ts +1 -1
  4. package/dist-lib/components/floriday/FlorydayConnectionPage.d.ts.map +1 -1
  5. package/dist-lib/components/floriday/FlorydaySyncHealthBanner.d.ts +1 -1
  6. package/dist-lib/components/floriday/FlorydaySyncHealthBanner.d.ts.map +1 -1
  7. package/dist-lib/components/floriday/FlorydaySyncJobsCard.d.ts +1 -1
  8. package/dist-lib/components/floriday/FlorydaySyncJobsCard.d.ts.map +1 -1
  9. package/dist-lib/components/floriday/TokenHealthCard.d.ts +1 -1
  10. package/dist-lib/components/floriday/TokenHealthCard.d.ts.map +1 -1
  11. package/dist-lib/components/settings/FloricodeSettingsCard.d.ts +1 -1
  12. package/dist-lib/components/settings/FloricodeSettingsCard.d.ts.map +1 -1
  13. package/dist-lib/components/settings/FlorydaySettingsCard.d.ts +1 -1
  14. package/dist-lib/components/settings/FlorydaySettingsCard.d.ts.map +1 -1
  15. package/dist-lib/integrations/supabase/auth-middleware.d.ts +652 -221
  16. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  17. package/dist-lib/integrations/supabase/client.d.ts +652 -221
  18. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  19. package/dist-lib/integrations/supabase/client.server.d.ts +652 -221
  20. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  21. package/dist-lib/integrations/supabase/types.d.ts +653 -228
  22. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  23. package/dist-lib/integrations/supabase/types.js +1 -12
  24. package/dist-lib/lib/central-auth.server.d.ts +26 -1
  25. package/dist-lib/lib/central-auth.server.d.ts.map +1 -1
  26. package/dist-lib/lib/central-auth.server.js +69 -3
  27. package/dist-lib/lib/cron/index.d.ts +1 -0
  28. package/dist-lib/lib/cron/index.d.ts.map +1 -1
  29. package/dist-lib/lib/cron/index.js +1 -0
  30. package/dist-lib/lib/cron/process-stock-queue.d.ts +43 -0
  31. package/dist-lib/lib/cron/process-stock-queue.d.ts.map +1 -0
  32. package/dist-lib/lib/cron/process-stock-queue.js +218 -0
  33. package/dist-lib/lib/cron/refresh-floriday-tokens.d.ts.map +1 -1
  34. package/dist-lib/lib/cron/refresh-floriday-tokens.js +27 -148
  35. package/dist-lib/lib/error-page.js +30 -30
  36. package/dist-lib/lib/floricode.functions.d.ts +4557 -1540
  37. package/dist-lib/lib/floricode.functions.d.ts.map +1 -1
  38. package/dist-lib/lib/floriday-auction-letters.functions.d.ts +652 -221
  39. package/dist-lib/lib/floriday-auction-letters.functions.d.ts.map +1 -1
  40. package/dist-lib/lib/floriday-labels.functions.d.ts +652 -221
  41. package/dist-lib/lib/floriday-labels.functions.d.ts.map +1 -1
  42. package/dist-lib/lib/floriday-public.functions.d.ts +3912 -1362
  43. package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
  44. package/dist-lib/lib/floriday-stickers.functions.d.ts +652 -221
  45. package/dist-lib/lib/floriday-stickers.functions.d.ts.map +1 -1
  46. package/dist-lib/lib/floriday-sync.functions.d.ts +20181 -6820
  47. package/dist-lib/lib/floriday-sync.functions.d.ts.map +1 -1
  48. package/dist-lib/lib/floriday-writes.functions.d.ts +3906 -1320
  49. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  50. package/dist-lib/lib/floriday.functions.d.ts +32289 -4066
  51. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  52. package/dist-lib/lib/floriday.functions.js +111 -45
  53. package/package.json +483 -475
  54. package/dist-lib/assets/floriday-logo.png.asset.json +0 -11
@@ -104,65 +104,56 @@ export async function getActiveFlorydayCredentials(supabase, env) {
104
104
  throw new Error(`Kan Floriday-credentials niet laden: ${error.message}`);
105
105
  return data;
106
106
  }
107
+ // In-memory tokencache. Central Auth is de token-broker: die cachet en grant
108
+ // zelf opnieuw. Deze module bewaart daarom niets in de database en heeft geen
109
+ // eigen refresh-timer; hoogstens een procesgebonden cache tot kort voor
110
+ // `expires_at`. Scoped tokens (smallere scope dan de standaard) worden bewust
111
+ // NIET gecacht — die worden per gebruik opgevraagd.
112
+ const tokenCache = new Map();
107
113
  export async function getFlorydayAccessToken(supabase, env, options = {}) {
108
- const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
109
114
  const row = await getActiveFlorydayCredentials(supabase, env);
110
- if (!row)
111
- throw new Error(`Geen actieve Floriday-koppeling voor ${env}`);
112
115
  const requestedScope = options.scope ?? DEFAULT_SCOPE;
113
- const canReuseStoredToken = requestedScope === DEFAULT_SCOPE;
116
+ const isDefaultScope = requestedScope === DEFAULT_SCOPE;
117
+ const cacheKey = `${env}:${requestedScope}`;
114
118
  const now = Date.now();
115
- const expiresAt = row.token_expires_at ? Date.parse(row.token_expires_at) : 0;
116
- if (canReuseStoredToken && !options.forceRefresh && row.access_token && expiresAt - TOKEN_REFRESH_LEEWAY_S * 1000 > now) {
117
- return row.access_token;
118
- }
119
- // Concurrency-guard: vlak voor de tokenrequest opnieuw lezen — een parallelle worker
120
- // kan zojuist een fresh token hebben geschreven en dan hoeven we niet nóg eens te refreshen.
121
- if (canReuseStoredToken && !options.forceRefresh) {
122
- const { data: fresh } = await supabaseAdmin
123
- .from("floriday_connections")
124
- .select("access_token, token_expires_at")
125
- .eq("id", row.id)
126
- .maybeSingle();
127
- const freshExp = fresh?.token_expires_at ? Date.parse(fresh.token_expires_at) : 0;
128
- if (fresh?.access_token && freshExp - TOKEN_REFRESH_LEEWAY_S * 1000 > now) {
129
- return fresh.access_token;
119
+ if (isDefaultScope && !options.forceRefresh) {
120
+ const hit = tokenCache.get(cacheKey);
121
+ if (hit && hit.expiresAt - TOKEN_REFRESH_LEEWAY_S * 1000 > now) {
122
+ return hit.token;
130
123
  }
131
124
  }
132
- let accessToken;
133
- let newExpiresAt;
134
125
  try {
135
126
  const { getFlorydayTokenFromCentral } = await import("./central-auth.server.js");
136
- const result = await getFlorydayTokenFromCentral(env, requestedScope);
127
+ const result = await getFlorydayTokenFromCentral(env, options.scope, "default");
137
128
  if (!result.ok) {
138
129
  throw createFlorydayTokenError(result.raw.status, result.raw.body, requestedScope);
139
130
  }
140
- accessToken = result.access_token;
141
- newExpiresAt = result.expires_at;
142
- if (canReuseStoredToken) {
143
- await supabaseAdmin
144
- .from("floriday_connections")
145
- .update({ access_token: accessToken, token_expires_at: newExpiresAt })
146
- .eq("id", row.id);
131
+ const accessToken = result.access_token;
132
+ const newExpiresAt = result.expires_at;
133
+ if (isDefaultScope) {
134
+ tokenCache.set(cacheKey, {
135
+ token: accessToken,
136
+ expiresAt: Date.parse(newExpiresAt) || now,
137
+ });
147
138
  }
148
139
  // Best-effort logging (faalt nooit hard).
149
140
  await supabase.from("floriday_token_refresh_log").insert({
150
- connection_id: row.id,
141
+ connection_id: row?.id ?? null,
151
142
  environment: env,
152
143
  ok: true,
153
144
  expires_at: newExpiresAt,
154
145
  }).then(() => undefined, () => undefined);
146
+ return accessToken;
155
147
  }
156
148
  catch (e) {
157
149
  await supabase.from("floriday_token_refresh_log").insert({
158
- connection_id: row.id,
150
+ connection_id: row?.id ?? null,
159
151
  environment: env,
160
152
  ok: false,
161
153
  error: String(e?.message ?? e).slice(0, 500),
162
154
  }).then(() => undefined, () => undefined);
163
155
  throw e;
164
156
  }
165
- return accessToken;
166
157
  }
167
158
  function createFlorydayTokenError(status, body, scope) {
168
159
  const normalized = body.toLowerCase();
@@ -181,8 +172,38 @@ function createFlorydayTokenError(status, body, scope) {
181
172
  return new Error(FLORYDAY_CONTRACT_PERMISSION_MESSAGE);
182
173
  }
183
174
  }
175
+ // Central Auth antwoordt opaak en in het Engels; hier vertalen we naar een
176
+ // melding waar een beheerder iets mee kan. Status én body blijven gelogd in
177
+ // central-auth.server.ts.
178
+ const centralMessage = mapCentralAuthError(status, normalized);
179
+ if (centralMessage)
180
+ return new Error(centralMessage);
184
181
  return new Error(`Floriday token-aanvraag mislukt [${status}]: ${body.slice(0, 300)}`);
185
182
  }
183
+ function mapCentralAuthError(status, normalizedBody) {
184
+ if (status === 401) {
185
+ return "Central Auth weigert dit bearer-secret. Controleer CENTRAL_AUTH_SECRET van deze omgeving.";
186
+ }
187
+ if (status === 404) {
188
+ return "Central Auth is niet gevonden op dit adres. Controleer CENTRAL_AUTH_URL (basis-URL van de Central Auth-module, zonder pad).";
189
+ }
190
+ if (status === 409 || normalizedBody.includes("not_connected")) {
191
+ return "Er is nog geen actieve Floriday-credential voor deze omgeving in Central Auth. De beheerder moet die daar invoeren.";
192
+ }
193
+ if (status === 400 && normalizedBody.includes("ambiguous_account")) {
194
+ return "Central Auth vond meerdere Floriday-accounts voor deze omgeving; er moet een specifieke account_key worden meegestuurd.";
195
+ }
196
+ if (status === 400) {
197
+ return "Central Auth wees de aanvraag af als ongeldig (ongeldige omgeving of scope-formaat).";
198
+ }
199
+ if (status === 502 || normalizedBody.includes("upstream_error")) {
200
+ return "Floriday weigerde de tokenaanvraag via Central Auth. Controleer de Floriday-credential van deze omgeving in Central Auth.";
201
+ }
202
+ if (status >= 500) {
203
+ return "Central Auth is tijdelijk niet beschikbaar; de volgende ronde probeert het automatisch opnieuw.";
204
+ }
205
+ return null;
206
+ }
186
207
  async function fetchAndStoreFlorydayOrganizationName(supabase, env, connectionId) {
187
208
  const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
188
209
  try {
@@ -324,9 +345,28 @@ export const getFlorydayTokenHealth = createServerFn({ method: "GET" })
324
345
  .select("connection_id, attempted_at, ok, error, expires_at")
325
346
  .order("attempted_at", { ascending: false })
326
347
  .limit(20);
348
+ // Central Auth is de bron van de tokenstatus. Bij client_credentials
349
+ // betekent een verlopen expiry alleen "recent niet gebruikt" — nooit
350
+ // "kapot" — dus geven we die informatie apart mee.
351
+ const { getFlorydayAccountsFromCentral } = await import("./central-auth.server.js");
352
+ const central = {};
353
+ for (const env of ["staging", "live"]) {
354
+ try {
355
+ const res = await getFlorydayAccountsFromCentral(env);
356
+ central[env] = res.ok
357
+ ? { ok: true, accounts: res.accounts }
358
+ : { ok: false, status: res.raw.status };
359
+ }
360
+ catch (e) {
361
+ central[env] = { ok: false, error: String(e?.message ?? e).slice(0, 200) };
362
+ }
363
+ }
327
364
  return {
328
365
  connections: conns ?? [],
329
366
  recent: log ?? [],
367
+ central,
368
+ broker: "central-auth",
369
+ note: "Tokens worden door Central Auth beheerd; een verlopen expiry betekent alleen dat het token recent niet is gebruikt.",
330
370
  };
331
371
  });
332
372
  export const forceRefreshFlorydayToken = createServerFn({ method: "POST" })
@@ -338,15 +378,17 @@ export const forceRefreshFlorydayToken = createServerFn({ method: "POST" })
338
378
  return input;
339
379
  })
340
380
  .handler(async ({ data, context }) => {
341
- const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
381
+ // Vraagt Central Auth om een fris token; niets wordt in de database bewaard.
342
382
  await getFlorydayAccessToken(context.supabase, data.environment, { forceRefresh: true });
343
- const { data: row } = await supabaseAdmin
344
- .from("floriday_connections")
345
- .select("token_expires_at")
383
+ const { data: log } = await context.supabase
384
+ .from("floriday_token_refresh_log")
385
+ .select("expires_at")
346
386
  .eq("environment", data.environment)
347
- .eq("is_active", true)
387
+ .eq("ok", true)
388
+ .order("attempted_at", { ascending: false })
389
+ .limit(1)
348
390
  .maybeSingle();
349
- return { ok: true, expires_at: row?.token_expires_at ?? null };
391
+ return { ok: true, expires_at: log?.expires_at ?? null };
350
392
  });
351
393
  export const upsertFlorydayConnection = createServerFn({ method: "POST" })
352
394
  .middleware([requireSupabaseAuth])
@@ -533,14 +575,15 @@ export const testFlorydayConnection = createServerFn({ method: "POST" })
533
575
  * Volgorde (eerste hit wint):
534
576
  * 1. `FLORIDAY_CRON_BASE_URL` — custom domain of override
535
577
  * 2. `LOVABLE_PROJECT_ID` → `https://project--{id}.lovable.app`
536
- * 3. Hardcoded fallback voor dít project
578
+ *
579
+ * Geen hardcoded fallback: in een nieuwe shell moet dit zichtbaar falen in
580
+ * plaats van stil naar een ander project te schrijven.
537
581
  *
538
582
  * Bewust NIET terugvallen op `VITE_SUPABASE_PROJECT_ID` /
539
583
  * `SUPABASE_PROJECT_ID`: die bevatten de Supabase project-ref, niet de
540
584
  * Lovable project-ID — gebruik daarvan was de oorzaak van een stille
541
585
  * cron-mismatch waardoor tokens nooit werden ververst.
542
586
  */
543
- const HARDCODED_LOVABLE_PROJECT_ID = "27b6733b-2e2f-4518-b204-681958559055";
544
587
  const MANAGED_FLORYDAY_CRON_JOB_NAMES = new Set([
545
588
  "refresh-floriday-tokens-hourly",
546
589
  "floriday-connections-sync-twice-daily",
@@ -569,12 +612,14 @@ const MANAGED_FLORYDAY_CRON_JOB_NAMES = new Set([
569
612
  "floriday-contract-trade-item-groups-sync-hourly",
570
613
  // Referentiedata (schema + code-lijsten)
571
614
  "floriday-reference-data-refresh-daily",
615
+ // Voorraad-wachtrij
616
+ "floriday-stock-queue-process",
572
617
  ]);
573
618
  export function resolveFlorydayCronBaseUrl() {
574
619
  const custom = process.env.FLORIDAY_CRON_BASE_URL?.trim();
575
620
  if (custom)
576
621
  return custom.replace(/\/+$/, "");
577
- const lovableId = process.env.LOVABLE_PROJECT_ID?.trim() || HARDCODED_LOVABLE_PROJECT_ID;
622
+ const lovableId = process.env.LOVABLE_PROJECT_ID?.trim();
578
623
  if (!lovableId) {
579
624
  throw new Error("Kan cron-URL niet bepalen: zet FLORIDAY_CRON_BASE_URL of LOVABLE_PROJECT_ID");
580
625
  }
@@ -599,7 +644,10 @@ export const setupFlorydayTokenRefreshCron = createServerFn({ method: "POST" })
599
644
  export const getFlorydaySettings = createServerFn({ method: "GET" })
600
645
  .middleware([requireSupabaseAuth])
601
646
  .handler(async ({ context }) => {
602
- const env = await getActiveFlorydayEnvironment(context.supabase);
647
+ // `floriday_settings` staat onder default-deny RLS; lezen met de user-client
648
+ // levert stil 0 rijen op (en dus de "staging"-fallback). Service-role lezen.
649
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
650
+ const env = await getActiveFlorydayEnvironment(supabaseAdmin);
603
651
  return { active_environment: env };
604
652
  });
605
653
  export const setFlorydayActiveEnvironment = createServerFn({ method: "POST" })
@@ -611,7 +659,8 @@ export const setFlorydayActiveEnvironment = createServerFn({ method: "POST" })
611
659
  return input;
612
660
  })
613
661
  .handler(async ({ data, context }) => {
614
- const { error } = await context.supabase
662
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
663
+ const { error } = await supabaseAdmin
615
664
  .from("floriday_settings")
616
665
  .upsert({ id: 1, active_environment: data.environment });
617
666
  if (error)
@@ -996,6 +1045,22 @@ export const setupFlorydayConnectionsSyncCron = createServerFn({ method: "POST"
996
1045
  const { jobid, url } = await scheduleFlorydayConnectionsSyncInternal(context.supabase);
997
1046
  return { ok: true, jobid, url };
998
1047
  });
1048
+ async function scheduleFlorydayStockQueueInternal(supabase) {
1049
+ const apikey = process.env.SUPABASE_PUBLISHABLE_KEY ?? process.env.VITE_SUPABASE_PUBLISHABLE_KEY;
1050
+ if (!apikey)
1051
+ throw new Error("Supabase anon key ontbreekt in server-env");
1052
+ const url = `${resolveFlorydayCronBaseUrl()}/api/public/cron/process-stock-queue`;
1053
+ const { data, error } = await supabase.rpc("schedule_floriday_stock_queue_process", { p_url: url, p_apikey: apikey });
1054
+ if (error)
1055
+ throw new Error(error.message);
1056
+ return { jobid: data, url };
1057
+ }
1058
+ export const setupFlorydayStockQueueCron = createServerFn({ method: "POST" })
1059
+ .middleware([requireSupabaseAuth])
1060
+ .handler(async ({ context }) => {
1061
+ const { jobid, url } = await scheduleFlorydayStockQueueInternal(context.supabase);
1062
+ return { ok: true, jobid, url };
1063
+ });
999
1064
  /**
1000
1065
  * Geeft een snapshot van de geplande Floriday-cron jobs zodat de UI kan
1001
1066
  * tonen welke URL is ingepland (zo zien we direct als er een verkeerde
@@ -1021,7 +1086,7 @@ export const getFlorydayCronStatus = createServerFn({ method: "GET" })
1021
1086
  return { ok: true, jobs, expectedBase };
1022
1087
  });
1023
1088
  /**
1024
- * Herinstalleert beide Floriday-crons in één call met de correct
1089
+ * Herinstalleert de Floriday-crons in één call met de correct
1025
1090
  * gedetecteerde base-URL. Idempotent: bestaande jobs worden overschreven.
1026
1091
  */
1027
1092
  export const reinstallFlorydayCrons = createServerFn({ method: "POST" })
@@ -1029,7 +1094,8 @@ export const reinstallFlorydayCrons = createServerFn({ method: "POST" })
1029
1094
  .handler(async ({ context }) => {
1030
1095
  const token = await scheduleFlorydayTokenRefreshInternal(context.supabase);
1031
1096
  const conn = await scheduleFlorydayConnectionsSyncInternal(context.supabase);
1032
- return { ok: true, token, connections: conn };
1097
+ const stockQueue = await scheduleFlorydayStockQueueInternal(context.supabase);
1098
+ return { ok: true, token, connections: conn, stockQueue };
1033
1099
  });
1034
1100
  function redactHeadersForWriteLog(headers) {
1035
1101
  const out = {};