@7365admin1/core 3.52.5-staging.251 → 3.52.6-staging.252
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/.changeset/bulk-upsert-fail-closed.md +5 -0
- package/CHANGELOG.md +52 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +89 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +89 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/bulk-upsert-fail-closed.test.mjs +134 -0
- package/.changeset/expired-vehicle-cron-per-site-fail-closed.md +0 -27
- package/.changeset/visitor-dahua-cron-fail-closed.md +0 -27
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Spreadsheet vehicle import is fail-closed. `bulkUpsertVehicles` wrote every row to the database whatever the ANPR cameras answered, so an imported blocklist plate that no camera accepted was saved as blocked while the car still opened the barrier. Rows are now run through the shared `sweepExpiredVehicles` decision — every camera must confirm — and only confirmed rows are saved; the rest are rolled back off any camera that did take them and returned in `failedRows` with the camera and the reason.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# @iservice365/core
|
|
2
2
|
|
|
3
|
+
## 3.52.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9bad426: Revoke each expired vehicle's plate only from its own site's cameras, and fail closed
|
|
8
|
+
|
|
9
|
+
The hourly expired-vehicle sweep declared its `siteCameras` array outside the
|
|
10
|
+
per-vehicle loop and only ever pushed to it, so the list grew as the run went
|
|
11
|
+
on: the second vehicle was revoked from its own site's cameras AND from the
|
|
12
|
+
first vehicle's. `recNo` is a per-device sequence number, so those extra calls
|
|
13
|
+
did not simply miss - they addressed whatever record carried that number on the
|
|
14
|
+
other site's camera and took an unrelated, still-valid plate off the barrier.
|
|
15
|
+
Cameras are now resolved per vehicle, cached by site id so each site is still
|
|
16
|
+
paged only once per run.
|
|
17
|
+
|
|
18
|
+
The sweep also marked every expired vehicle deleted with one blanket
|
|
19
|
+
`updateMany`, regardless of what the cameras answered, so a record could read
|
|
20
|
+
"deleted" while its plate still opened the gate. Only vehicles whose every
|
|
21
|
+
camera confirmed are now marked deleted; the rest are left expired, logged at
|
|
22
|
+
error level naming the vehicle, plate, site and camera, and retried on the next
|
|
23
|
+
hourly run. `deleteExpiredVehicles` now requires the list of ids.
|
|
24
|
+
|
|
25
|
+
Also: each camera is asked for its own record id before the removal (the same
|
|
26
|
+
thing manual delete does), the ANPR list is chosen from the vehicle's type
|
|
27
|
+
instead of always TRAFFIC_REDLIST, a site with no ANPR camera no longer aborts
|
|
28
|
+
the whole run, and the transaction that wrapped nothing - the only write was
|
|
29
|
+
issued without the session - has been removed.
|
|
30
|
+
|
|
31
|
+
- 082a865: Fail closed in the visitor ANPR sweep, and remove the dead invitation transaction
|
|
32
|
+
|
|
33
|
+
`processTransactionDahuaStatus`, the hourly job that takes an expired or
|
|
34
|
+
checked-out visitor's plate off the site's ANPR camera, marked the transaction
|
|
35
|
+
`dahuaSyncStatus: "removed"` on the line after the camera call and only skipped
|
|
36
|
+
on a thrown error. `removePlateNumber` does not throw for a device failure - it
|
|
37
|
+
returns an outcome object - so a camera answering 401 or 500, or not answering
|
|
38
|
+
at all, still had the transaction recorded as removed while the visitor's plate
|
|
39
|
+
was still on the barrier. Nothing retried it either, because the query that
|
|
40
|
+
feeds the sweep skips anything already marked removed.
|
|
41
|
+
|
|
42
|
+
It now uses the same decision logic as the expired-vehicle sweep: only a
|
|
43
|
+
transaction every camera confirmed is marked removed, and the rest are logged at
|
|
44
|
+
error level naming the plate, the site and the camera, then retried on the next
|
|
45
|
+
run. Each camera is also asked for its own record id before the removal, which
|
|
46
|
+
is what the old "not found" message sniff was reaching for and what stops a
|
|
47
|
+
transaction whose record is already gone from being retried for ever.
|
|
48
|
+
|
|
49
|
+
`checkExpiredInvitation` opened a session and a transaction and then issued
|
|
50
|
+
every write without that session, never committing: the transaction protected no
|
|
51
|
+
write and the rollback rolled back nothing. Each invitation is independent, so
|
|
52
|
+
there is no invariant spanning them and nothing for a transaction to protect -
|
|
53
|
+
it is removed rather than wired up.
|
|
54
|
+
|
|
3
55
|
## 3.52.4
|
|
4
56
|
|
|
5
57
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -2910,11 +2910,21 @@ declare function useVehicleService(): {
|
|
|
2910
2910
|
reactivateVehicleById: (id: string, orgId: string, siteId: string) => Promise<string>;
|
|
2911
2911
|
updateVehicleById: (_id: string, value: TVehicle) => Promise<void>;
|
|
2912
2912
|
bulkUpsertVehicles: (values: TVehicle[], site: string, org: string) => Promise<{
|
|
2913
|
+
savedCount: number;
|
|
2914
|
+
failedRows: {
|
|
2915
|
+
plateNumber: string;
|
|
2916
|
+
message: string;
|
|
2917
|
+
}[];
|
|
2913
2918
|
matchedCount: number;
|
|
2914
2919
|
modifiedCount: number;
|
|
2915
2920
|
upsertedCount: number;
|
|
2916
2921
|
upsertedIds?: undefined;
|
|
2917
2922
|
} | {
|
|
2923
|
+
savedCount: number;
|
|
2924
|
+
failedRows: {
|
|
2925
|
+
plateNumber: string;
|
|
2926
|
+
message: string;
|
|
2927
|
+
}[];
|
|
2918
2928
|
matchedCount: number;
|
|
2919
2929
|
modifiedCount: number;
|
|
2920
2930
|
upsertedCount: number;
|
|
@@ -4206,7 +4216,7 @@ declare function useCameraViewService(): {
|
|
|
4206
4216
|
cameraVerified: null;
|
|
4207
4217
|
relayVerified: null;
|
|
4208
4218
|
reachable: boolean;
|
|
4209
|
-
health: "
|
|
4219
|
+
health: "ok" | "unreachable" | "drifted";
|
|
4210
4220
|
reason: string | null;
|
|
4211
4221
|
camera: Record<string, any>;
|
|
4212
4222
|
snapshotSupported: boolean;
|
|
@@ -4263,7 +4273,7 @@ declare function useCameraViewService(): {
|
|
|
4263
4273
|
cameraVerified: null;
|
|
4264
4274
|
relayVerified: null;
|
|
4265
4275
|
reachable: boolean;
|
|
4266
|
-
health: "
|
|
4276
|
+
health: "ok" | "unreachable" | "drifted";
|
|
4267
4277
|
reason: string | null;
|
|
4268
4278
|
capabilities: CameraCapabilityDescriptor;
|
|
4269
4279
|
firmwareVersion: null;
|
package/dist/index.js
CHANGED
|
@@ -36381,34 +36381,99 @@ function useVehicleService() {
|
|
|
36381
36381
|
if (!siteCameras.length) {
|
|
36382
36382
|
throw new Error("No site cameras found.");
|
|
36383
36383
|
}
|
|
36384
|
-
|
|
36385
|
-
|
|
36386
|
-
|
|
36387
|
-
|
|
36388
|
-
|
|
36389
|
-
|
|
36390
|
-
|
|
36391
|
-
|
|
36392
|
-
|
|
36393
|
-
|
|
36394
|
-
|
|
36395
|
-
|
|
36396
|
-
|
|
36397
|
-
|
|
36398
|
-
|
|
36399
|
-
|
|
36400
|
-
|
|
36401
|
-
|
|
36402
|
-
|
|
36384
|
+
const written = /* @__PURE__ */ new Map();
|
|
36385
|
+
const sweepRows = sanitizedValues.map((value, index) => ({
|
|
36386
|
+
_id: String(index),
|
|
36387
|
+
site,
|
|
36388
|
+
plateNumber: value.plateNumber
|
|
36389
|
+
}));
|
|
36390
|
+
const { deletableIds: savableIds, skipped } = await sweepExpiredVehicles(
|
|
36391
|
+
sweepRows,
|
|
36392
|
+
{
|
|
36393
|
+
camerasForSite: async () => siteCameras,
|
|
36394
|
+
cameraLabel,
|
|
36395
|
+
revoke: async (camera, row) => {
|
|
36396
|
+
const rowId = String(row._id);
|
|
36397
|
+
const vehicleValue = sanitizedValues[Number(rowId)];
|
|
36398
|
+
const plateNumber = vehicleValue.plateNumber;
|
|
36399
|
+
const { host, username, password } = camera;
|
|
36400
|
+
const dahuaPayload = {
|
|
36401
|
+
host,
|
|
36402
|
+
username,
|
|
36403
|
+
password,
|
|
36404
|
+
plateNumber,
|
|
36405
|
+
mode: vehicleValue.type === "whitelist" /* WHITELIST */ ? "TrafficRedList" /* TRAFFIC_REDLIST */ : "TrafficBlackList" /* TRAFFIC_BLACKLIST */,
|
|
36406
|
+
...vehicleValue.start ? { start: String(vehicleValue.start) } : {},
|
|
36407
|
+
...vehicleValue.end ? { end: String(vehicleValue.end) } : {},
|
|
36408
|
+
...vehicleValue.name ? { owner: vehicleValue.name } : {},
|
|
36409
|
+
...vehicleValue.vehicleModel ? { vehicleType: vehicleValue.vehicleModel } : {},
|
|
36410
|
+
...vehicleValue.vehicleColor ? { vehicleColor: vehicleValue.vehicleColor } : {}
|
|
36411
|
+
};
|
|
36412
|
+
try {
|
|
36403
36413
|
const dahuaResponse = await _bulkInsertPlateNumber(dahuaPayload);
|
|
36414
|
+
const responseStatus = dahuaResponse?.statusCode || dahuaResponse?.status || dahuaResponse?.res?.status || "unknown";
|
|
36404
36415
|
const responseData = dahuaResponse?.data?.toString("utf-8") ?? "";
|
|
36405
|
-
|
|
36406
|
-
|
|
36416
|
+
if (responseStatus !== 200) {
|
|
36417
|
+
logCameraFailure(
|
|
36418
|
+
"bulk add",
|
|
36419
|
+
camera,
|
|
36420
|
+
`HTTP ${responseStatus}: ${responseData.slice(0, 500)}`
|
|
36421
|
+
);
|
|
36422
|
+
return {
|
|
36423
|
+
ok: false,
|
|
36424
|
+
reason: anprWriteReason(`HTTP ${responseStatus}`)
|
|
36425
|
+
};
|
|
36426
|
+
}
|
|
36427
|
+
const recNo = String(responseData.split("=")[1]?.trim() ?? "");
|
|
36428
|
+
if (recNo) {
|
|
36429
|
+
vehicleValue.recNo = vehicleValue.recNo || recNo;
|
|
36430
|
+
const entries = written.get(rowId) ?? [];
|
|
36431
|
+
entries.push({ camera, recno: recNo });
|
|
36432
|
+
written.set(rowId, entries);
|
|
36433
|
+
}
|
|
36434
|
+
return { ok: true, reason: "" };
|
|
36435
|
+
} catch (error) {
|
|
36436
|
+
logCameraFailure("bulk add", camera, error);
|
|
36437
|
+
return { ok: false, reason: anprWriteReason(error) };
|
|
36438
|
+
}
|
|
36439
|
+
}
|
|
36440
|
+
}
|
|
36441
|
+
);
|
|
36442
|
+
const failedRows = [];
|
|
36443
|
+
for (const entry of skipped) {
|
|
36444
|
+
const stranded = [];
|
|
36445
|
+
for (const write of written.get(entry.id) ?? []) {
|
|
36446
|
+
const outcome = await _removePlateNumber({
|
|
36447
|
+
host: write.camera.host,
|
|
36448
|
+
username: write.camera.username,
|
|
36449
|
+
password: write.camera.password,
|
|
36450
|
+
recno: write.recno,
|
|
36451
|
+
mode: sanitizedValues[Number(entry.id)]?.type === "whitelist" /* WHITELIST */ ? "TrafficRedList" /* TRAFFIC_REDLIST */ : "TrafficBlackList" /* TRAFFIC_BLACKLIST */
|
|
36452
|
+
}).catch((removeError) => anprRevokeThrew(removeError));
|
|
36453
|
+
if (!outcome?.ok)
|
|
36454
|
+
stranded.push(cameraLabel(write.camera));
|
|
36455
|
+
}
|
|
36456
|
+
let message = anprWriteFailureMessage(entry.failures);
|
|
36457
|
+
if (stranded.length) {
|
|
36458
|
+
import_node_server_utils99.logger.error(
|
|
36459
|
+
`bulkUpsertVehicles could not take the plate back off: ${stranded.join(", ")} (plate ${JSON.stringify(entry.plateNumber)})`
|
|
36407
36460
|
);
|
|
36408
|
-
|
|
36409
|
-
}
|
|
36461
|
+
message += ` WARNING: the plate number was written to ${stranded.join(", ")} and could not be removed again - it WILL still open the barrier. Remove it on the camera by hand.`;
|
|
36462
|
+
}
|
|
36463
|
+
import_node_server_utils99.logger.error(
|
|
36464
|
+
`bulkUpsertVehicles NOT saving plate ${JSON.stringify(entry.plateNumber)} (site ${entry.site}): ` + entry.failures.map((failure) => `${failure.camera} (${failure.reason})`).join(", ")
|
|
36465
|
+
);
|
|
36466
|
+
failedRows.push({
|
|
36467
|
+
plateNumber: String(entry.plateNumber ?? ""),
|
|
36468
|
+
message
|
|
36469
|
+
});
|
|
36470
|
+
}
|
|
36471
|
+
const savable = new Set(savableIds);
|
|
36472
|
+
const toSave = sanitizedValues.filter(
|
|
36473
|
+
(_, index) => savable.has(String(index))
|
|
36410
36474
|
);
|
|
36411
|
-
|
|
36475
|
+
const result = toSave.length ? await _bulkUpsertVehicles(toSave) : { matchedCount: 0, modifiedCount: 0, upsertedCount: 0 };
|
|
36476
|
+
return { ...result, savedCount: toSave.length, failedRows };
|
|
36412
36477
|
} catch (error) {
|
|
36413
36478
|
throw error;
|
|
36414
36479
|
}
|