@flowselections/google-maps-api-installatie-techniek 1.0.2

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 (66) hide show
  1. package/dist-lib/_core-safelist.d.ts +2 -0
  2. package/dist-lib/_core-safelist.d.ts.map +1 -0
  3. package/dist-lib/_core-safelist.js +15 -0
  4. package/dist-lib/components/MapsBeheerPage.d.ts +2 -0
  5. package/dist-lib/components/MapsBeheerPage.d.ts.map +1 -0
  6. package/dist-lib/components/MapsBeheerPage.js +68 -0
  7. package/dist-lib/components/MapsInstallatieLogo.d.ts +6 -0
  8. package/dist-lib/components/MapsInstallatieLogo.d.ts.map +1 -0
  9. package/dist-lib/components/MapsInstallatieLogo.js +4 -0
  10. package/dist-lib/components/maps/MapsDiagnosticsPanel.d.ts +2 -0
  11. package/dist-lib/components/maps/MapsDiagnosticsPanel.d.ts.map +1 -0
  12. package/dist-lib/components/maps/MapsDiagnosticsPanel.js +81 -0
  13. package/dist-lib/components/maps/MapsStatusPanel.d.ts +5 -0
  14. package/dist-lib/components/maps/MapsStatusPanel.d.ts.map +1 -0
  15. package/dist-lib/components/maps/MapsStatusPanel.js +74 -0
  16. package/dist-lib/components/settings/MapsSettingsCard.d.ts +2 -0
  17. package/dist-lib/components/settings/MapsSettingsCard.d.ts.map +1 -0
  18. package/dist-lib/components/settings/MapsSettingsCard.js +15 -0
  19. package/dist-lib/index.d.ts +7 -0
  20. package/dist-lib/index.d.ts.map +1 -0
  21. package/dist-lib/index.js +20 -0
  22. package/dist-lib/integrations/supabase/auth-attacher.d.ts +2 -0
  23. package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -0
  24. package/dist-lib/integrations/supabase/auth-attacher.js +12 -0
  25. package/dist-lib/integrations/supabase/auth-middleware.d.ts +23365 -0
  26. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -0
  27. package/dist-lib/integrations/supabase/auth-middleware.js +52 -0
  28. package/dist-lib/integrations/supabase/client.d.ts +23361 -0
  29. package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
  30. package/dist-lib/integrations/supabase/client.js +29 -0
  31. package/dist-lib/integrations/supabase/client.server.d.ts +23361 -0
  32. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -0
  33. package/dist-lib/integrations/supabase/client.server.js +30 -0
  34. package/dist-lib/integrations/supabase/previewAuthStorage.d.ts +6 -0
  35. package/dist-lib/integrations/supabase/previewAuthStorage.d.ts.map +1 -0
  36. package/dist-lib/integrations/supabase/previewAuthStorage.js +92 -0
  37. package/dist-lib/integrations/supabase/types.d.ts +23990 -0
  38. package/dist-lib/integrations/supabase/types.d.ts.map +1 -0
  39. package/dist-lib/integrations/supabase/types.js +92 -0
  40. package/dist-lib/lib/central-auth.server.d.ts +60 -0
  41. package/dist-lib/lib/central-auth.server.d.ts.map +1 -0
  42. package/dist-lib/lib/central-auth.server.js +439 -0
  43. package/dist-lib/lib/google-maps.server.d.ts +22 -0
  44. package/dist-lib/lib/google-maps.server.d.ts.map +1 -0
  45. package/dist-lib/lib/google-maps.server.js +65 -0
  46. package/dist-lib/lib/maps-admin.server.d.ts +17 -0
  47. package/dist-lib/lib/maps-admin.server.d.ts.map +1 -0
  48. package/dist-lib/lib/maps-admin.server.js +121 -0
  49. package/dist-lib/lib/maps-diagnostics.server.d.ts +4 -0
  50. package/dist-lib/lib/maps-diagnostics.server.d.ts.map +1 -0
  51. package/dist-lib/lib/maps-diagnostics.server.js +137 -0
  52. package/dist-lib/lib/maps-processor.server.d.ts +16 -0
  53. package/dist-lib/lib/maps-processor.server.d.ts.map +1 -0
  54. package/dist-lib/lib/maps-processor.server.js +315 -0
  55. package/dist-lib/lib/maps.functions.d.ts +186950 -0
  56. package/dist-lib/lib/maps.functions.d.ts.map +1 -0
  57. package/dist-lib/lib/maps.functions.js +63 -0
  58. package/dist-lib/lib/utils.d.ts +3 -0
  59. package/dist-lib/lib/utils.d.ts.map +1 -0
  60. package/dist-lib/lib/utils.js +5 -0
  61. package/dist-lib/lib/validationSchemas.d.ts +15 -0
  62. package/dist-lib/lib/validationSchemas.d.ts.map +1 -0
  63. package/dist-lib/lib/validationSchemas.js +25 -0
  64. package/dist-lib/styles.css +1 -0
  65. package/package.json +87 -0
  66. package/public/logo.png +0 -0
@@ -0,0 +1,315 @@
1
+ // Server-only wachtrijverwerking voor de kaarten-module.
2
+ // Leest maps_request_queue, geocodet adressen en haalt reistijden op.
3
+ import { createClient } from "@supabase/supabase-js";
4
+ import { computeRouteMatrix, geocodeAddress } from "./google-maps.server";
5
+ const BATCH_SIZE = 50;
6
+ const MAX_ATTEMPTS = 3;
7
+ const MAX_MATRIX_SIDE = 25;
8
+ /** SHA-256 hash via Web Crypto (browser, Node 15+ en Deno). */
9
+ export async function addressHash(raw) {
10
+ const normalized = raw.trim().toLowerCase().replace(/\s+/g, " ");
11
+ const buf = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(normalized));
12
+ return Array.from(new Uint8Array(buf))
13
+ .map((b) => b.toString(16).padStart(2, "0"))
14
+ .join("");
15
+ }
16
+ function adminClient() {
17
+ const url = process.env["SUPABASE_URL"];
18
+ const key = process.env["SERVICE_ROLE_KEY"] ?? process.env["SUPABASE_SERVICE_ROLE_KEY"];
19
+ if (!url || !key)
20
+ throw new Error("Supabase serverconfiguratie ontbreekt.");
21
+ return createClient(url, key, {
22
+ auth: { persistSession: false, autoRefreshToken: false },
23
+ });
24
+ }
25
+ const LEASE_JOB = "maps_queue";
26
+ const LEASE_MINUTES = 5;
27
+ /** Claimt de verwerkingslease. Geeft false als er al een run bezig is. */
28
+ export async function claimQueueLease() {
29
+ const db = adminClient();
30
+ const now = new Date();
31
+ const { data } = await db
32
+ .from("maps_job_lease")
33
+ .update({ locked_until: new Date(now.getTime() + LEASE_MINUTES * 60000).toISOString() })
34
+ .eq("job", LEASE_JOB)
35
+ .lt("locked_until", now.toISOString())
36
+ .select("job");
37
+ return Array.isArray(data) && data.length > 0;
38
+ }
39
+ /** Geeft de verwerkingslease weer vrij. */
40
+ export async function releaseQueueLease() {
41
+ const db = adminClient();
42
+ await db
43
+ .from("maps_job_lease")
44
+ .update({ locked_until: new Date(Date.now() - 1000).toISOString() })
45
+ .eq("job", LEASE_JOB);
46
+ }
47
+ async function getSettings(db) {
48
+ const { data } = await db
49
+ .from("maps_settings")
50
+ .select("*")
51
+ .limit(1)
52
+ .maybeSingle();
53
+ if (data)
54
+ return data;
55
+ const { data: created } = await db
56
+ .from("maps_settings")
57
+ .insert({})
58
+ .select("*")
59
+ .single();
60
+ return created;
61
+ }
62
+ async function callsToday(db) {
63
+ // Alleen echte externe aanroepen tellen mee voor de daglimiet.
64
+ const { data } = await db
65
+ .from("maps_usage_log")
66
+ .select("elements")
67
+ .in("call_type", ["geocode", "matrix"])
68
+ .gte("created_at", new Date(Date.now() - 24 * 3600 * 1000).toISOString());
69
+ const rows = Array.isArray(data) ? data : [];
70
+ return rows.reduce((sum, row) => sum + (row.elements ?? 0), 0);
71
+ }
72
+ async function logUsage(db, callType, elements, outcome, detail) {
73
+ await db.from("maps_usage_log").insert({
74
+ call_type: callType,
75
+ elements,
76
+ outcome,
77
+ detail: detail ?? null,
78
+ });
79
+ }
80
+ async function ensureAddress(db, raw) {
81
+ const hash = await addressHash(raw);
82
+ const { data: existing } = await db
83
+ .from("maps_addresses")
84
+ .select("id, lat, lng, status")
85
+ .eq("address_hash", hash)
86
+ .maybeSingle();
87
+ if (existing && existing.status === "resolved" && existing.lat != null) {
88
+ return { id: existing.id, lat: existing.lat, lng: existing.lng };
89
+ }
90
+ if (existing && existing.status === "failed")
91
+ return null;
92
+ const id = existing?.id ??
93
+ (await db
94
+ .from("maps_addresses")
95
+ .insert({ address_hash: hash, raw_address: raw, status: "pending" })
96
+ .select("id")
97
+ .single()).data?.id;
98
+ if (!id)
99
+ return null;
100
+ try {
101
+ const result = await geocodeAddress(raw);
102
+ if (!result) {
103
+ await db
104
+ .from("maps_addresses")
105
+ .update({
106
+ status: "failed",
107
+ error_reason: "Geen resultaat van Google",
108
+ updated_at: new Date().toISOString(),
109
+ })
110
+ .eq("id", id);
111
+ await logUsage(db, "geocode", 1, "no_result", raw);
112
+ return null;
113
+ }
114
+ await db
115
+ .from("maps_addresses")
116
+ .update({
117
+ formatted_address: result.formattedAddress,
118
+ lat: result.lat,
119
+ lng: result.lng,
120
+ place_id: result.placeId,
121
+ status: "resolved",
122
+ error_reason: null,
123
+ resolved_at: new Date().toISOString(),
124
+ updated_at: new Date().toISOString(),
125
+ })
126
+ .eq("id", id);
127
+ await logUsage(db, "geocode", 1, "ok");
128
+ return { id, lat: result.lat, lng: result.lng };
129
+ }
130
+ catch (error) {
131
+ const message = error instanceof Error ? error.message : String(error);
132
+ await db
133
+ .from("maps_addresses")
134
+ .update({ status: "failed", error_reason: message, updated_at: new Date().toISOString() })
135
+ .eq("id", id);
136
+ await logUsage(db, "geocode", 1, "error", message);
137
+ throw error;
138
+ }
139
+ }
140
+ /** Verwerkt een batch uit de wachtrij. Veilig om vaker aan te roepen. */
141
+ export async function processMapsQueueBatch() {
142
+ const db = adminClient();
143
+ const settings = await getSettings(db);
144
+ const result = {
145
+ processed: 0,
146
+ geocoded: 0,
147
+ travelTimes: 0,
148
+ failed: 0,
149
+ skipped: null,
150
+ };
151
+ const used = await callsToday(db);
152
+ if (settings && used >= settings.daily_call_limit) {
153
+ await logUsage(db, "limit", 0, "skipped", "Daglimiet bereikt");
154
+ result.skipped = "Daglimiet bereikt";
155
+ return result;
156
+ }
157
+ const { data: queue } = await db
158
+ .from("maps_request_queue")
159
+ .select("*")
160
+ .eq("status", "pending")
161
+ .lt("attempts", MAX_ATTEMPTS)
162
+ .order("created_at", { ascending: true })
163
+ .limit(BATCH_SIZE);
164
+ const items = Array.isArray(queue) ? queue : [];
165
+ if (items.length === 0)
166
+ return result;
167
+ const ids = items.map((item) => item.id);
168
+ await db
169
+ .from("maps_request_queue")
170
+ .update({ status: "processing", updated_at: new Date().toISOString() })
171
+ .in("id", ids);
172
+ const ttlDays = settings?.cache_ttl_days ?? 30;
173
+ const expiresAt = new Date(Date.now() + ttlDays * 86400000).toISOString();
174
+ const pairs = [];
175
+ for (const item of items) {
176
+ try {
177
+ if (item.kind === "geocode") {
178
+ const raw = item.raw_origin ?? item.raw_destination;
179
+ if (!raw)
180
+ throw new Error("Geen adres in wachtrijregel");
181
+ const address = await ensureAddress(db, raw);
182
+ if (address)
183
+ result.geocoded += 1;
184
+ else
185
+ result.failed += 1;
186
+ await db
187
+ .from("maps_request_queue")
188
+ .update({
189
+ status: address ? "done" : "failed",
190
+ attempts: (item.attempts ?? 0) + 1,
191
+ last_error: address ? null : "Adres niet gevonden",
192
+ processed_at: new Date().toISOString(),
193
+ updated_at: new Date().toISOString(),
194
+ })
195
+ .eq("id", item.id);
196
+ result.processed += 1;
197
+ continue;
198
+ }
199
+ const origin = item.raw_origin ? await ensureAddress(db, item.raw_origin) : null;
200
+ const destination = item.raw_destination
201
+ ? await ensureAddress(db, item.raw_destination)
202
+ : null;
203
+ if (!origin || !destination) {
204
+ result.failed += 1;
205
+ await db
206
+ .from("maps_request_queue")
207
+ .update({
208
+ status: "failed",
209
+ attempts: (item.attempts ?? 0) + 1,
210
+ last_error: "Adres kon niet worden omgezet",
211
+ processed_at: new Date().toISOString(),
212
+ updated_at: new Date().toISOString(),
213
+ })
214
+ .eq("id", item.id);
215
+ result.processed += 1;
216
+ continue;
217
+ }
218
+ pairs.push({ item, origin, destination });
219
+ }
220
+ catch (error) {
221
+ const message = error instanceof Error ? error.message : String(error);
222
+ result.failed += 1;
223
+ await db
224
+ .from("maps_request_queue")
225
+ .update({
226
+ status: (item.attempts ?? 0) + 1 >= MAX_ATTEMPTS ? "failed" : "pending",
227
+ attempts: (item.attempts ?? 0) + 1,
228
+ last_error: message,
229
+ updated_at: new Date().toISOString(),
230
+ })
231
+ .eq("id", item.id);
232
+ await db
233
+ .from("maps_settings")
234
+ .update({ last_error: message, last_error_at: new Date().toISOString() })
235
+ .eq("id", settings.id);
236
+ result.processed += 1;
237
+ }
238
+ }
239
+ for (let index = 0; index < pairs.length; index += MAX_MATRIX_SIDE) {
240
+ const chunk = pairs.slice(index, index + MAX_MATRIX_SIDE);
241
+ const origins = chunk.map((pair) => pair.origin);
242
+ const destinations = chunk.map((pair) => pair.destination);
243
+ const mode = chunk[0]?.item.mode ?? "drive";
244
+ try {
245
+ const elements = await computeRouteMatrix(origins, destinations, mode);
246
+ await logUsage(db, "matrix", chunk.length, "ok");
247
+ for (let position = 0; position < chunk.length; position += 1) {
248
+ const pair = chunk[position];
249
+ const element = elements.find((row) => row.originIndex === position && row.destinationIndex === position);
250
+ if (!element || element.status !== "ok" || element.durationSec == null) {
251
+ result.failed += 1;
252
+ await db
253
+ .from("maps_request_queue")
254
+ .update({
255
+ status: "failed",
256
+ attempts: (pair.item.attempts ?? 0) + 1,
257
+ last_error: "Geen route gevonden",
258
+ processed_at: new Date().toISOString(),
259
+ updated_at: new Date().toISOString(),
260
+ })
261
+ .eq("id", pair.item.id);
262
+ result.processed += 1;
263
+ continue;
264
+ }
265
+ await db.from("maps_travel_times").upsert({
266
+ origin_id: pair.origin.id,
267
+ destination_id: pair.destination.id,
268
+ mode: pair.item.mode ?? "drive",
269
+ duration_sec: element.durationSec,
270
+ distance_m: element.distanceM,
271
+ source: "google",
272
+ expires_at: expiresAt,
273
+ }, { onConflict: "origin_id,destination_id,mode" });
274
+ result.travelTimes += 1;
275
+ result.processed += 1;
276
+ await db
277
+ .from("maps_request_queue")
278
+ .update({
279
+ status: "done",
280
+ attempts: (pair.item.attempts ?? 0) + 1,
281
+ last_error: null,
282
+ processed_at: new Date().toISOString(),
283
+ updated_at: new Date().toISOString(),
284
+ })
285
+ .eq("id", pair.item.id);
286
+ }
287
+ await db
288
+ .from("maps_settings")
289
+ .update({ last_success_at: new Date().toISOString() })
290
+ .eq("id", settings.id);
291
+ }
292
+ catch (error) {
293
+ const message = error instanceof Error ? error.message : String(error);
294
+ await logUsage(db, "matrix", chunk.length, "error", message);
295
+ await db
296
+ .from("maps_settings")
297
+ .update({ last_error: message, last_error_at: new Date().toISOString() })
298
+ .eq("id", settings.id);
299
+ for (const pair of chunk) {
300
+ result.failed += 1;
301
+ result.processed += 1;
302
+ await db
303
+ .from("maps_request_queue")
304
+ .update({
305
+ status: (pair.item.attempts ?? 0) + 1 >= MAX_ATTEMPTS ? "failed" : "pending",
306
+ attempts: (pair.item.attempts ?? 0) + 1,
307
+ last_error: message,
308
+ updated_at: new Date().toISOString(),
309
+ })
310
+ .eq("id", pair.item.id);
311
+ }
312
+ }
313
+ }
314
+ return result;
315
+ }