@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.
- package/dist-lib/_core-safelist.d.ts +2 -0
- package/dist-lib/_core-safelist.d.ts.map +1 -0
- package/dist-lib/_core-safelist.js +15 -0
- package/dist-lib/components/MapsBeheerPage.d.ts +2 -0
- package/dist-lib/components/MapsBeheerPage.d.ts.map +1 -0
- package/dist-lib/components/MapsBeheerPage.js +68 -0
- package/dist-lib/components/MapsInstallatieLogo.d.ts +6 -0
- package/dist-lib/components/MapsInstallatieLogo.d.ts.map +1 -0
- package/dist-lib/components/MapsInstallatieLogo.js +4 -0
- package/dist-lib/components/maps/MapsDiagnosticsPanel.d.ts +2 -0
- package/dist-lib/components/maps/MapsDiagnosticsPanel.d.ts.map +1 -0
- package/dist-lib/components/maps/MapsDiagnosticsPanel.js +81 -0
- package/dist-lib/components/maps/MapsStatusPanel.d.ts +5 -0
- package/dist-lib/components/maps/MapsStatusPanel.d.ts.map +1 -0
- package/dist-lib/components/maps/MapsStatusPanel.js +74 -0
- package/dist-lib/components/settings/MapsSettingsCard.d.ts +2 -0
- package/dist-lib/components/settings/MapsSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/MapsSettingsCard.js +15 -0
- package/dist-lib/index.d.ts +7 -0
- package/dist-lib/index.d.ts.map +1 -0
- package/dist-lib/index.js +20 -0
- package/dist-lib/integrations/supabase/auth-attacher.d.ts +2 -0
- package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/auth-attacher.js +12 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +23365 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/auth-middleware.js +52 -0
- package/dist-lib/integrations/supabase/client.d.ts +23361 -0
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/client.js +29 -0
- package/dist-lib/integrations/supabase/client.server.d.ts +23361 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/client.server.js +30 -0
- package/dist-lib/integrations/supabase/previewAuthStorage.d.ts +6 -0
- package/dist-lib/integrations/supabase/previewAuthStorage.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/previewAuthStorage.js +92 -0
- package/dist-lib/integrations/supabase/types.d.ts +23990 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/types.js +92 -0
- package/dist-lib/lib/central-auth.server.d.ts +60 -0
- package/dist-lib/lib/central-auth.server.d.ts.map +1 -0
- package/dist-lib/lib/central-auth.server.js +439 -0
- package/dist-lib/lib/google-maps.server.d.ts +22 -0
- package/dist-lib/lib/google-maps.server.d.ts.map +1 -0
- package/dist-lib/lib/google-maps.server.js +65 -0
- package/dist-lib/lib/maps-admin.server.d.ts +17 -0
- package/dist-lib/lib/maps-admin.server.d.ts.map +1 -0
- package/dist-lib/lib/maps-admin.server.js +121 -0
- package/dist-lib/lib/maps-diagnostics.server.d.ts +4 -0
- package/dist-lib/lib/maps-diagnostics.server.d.ts.map +1 -0
- package/dist-lib/lib/maps-diagnostics.server.js +137 -0
- package/dist-lib/lib/maps-processor.server.d.ts +16 -0
- package/dist-lib/lib/maps-processor.server.d.ts.map +1 -0
- package/dist-lib/lib/maps-processor.server.js +315 -0
- package/dist-lib/lib/maps.functions.d.ts +186950 -0
- package/dist-lib/lib/maps.functions.d.ts.map +1 -0
- package/dist-lib/lib/maps.functions.js +63 -0
- package/dist-lib/lib/utils.d.ts +3 -0
- package/dist-lib/lib/utils.d.ts.map +1 -0
- package/dist-lib/lib/utils.js +5 -0
- package/dist-lib/lib/validationSchemas.d.ts +15 -0
- package/dist-lib/lib/validationSchemas.d.ts.map +1 -0
- package/dist-lib/lib/validationSchemas.js +25 -0
- package/dist-lib/styles.css +1 -0
- package/package.json +87 -0
- 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
|
+
}
|