@7365admin1/core 3.52.2-staging.235 → 3.52.2-staging.236
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/vehicle-anpr-revoke-safety.md +11 -0
- package/dist/index.d.ts +46 -6
- package/dist/index.js +152 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +152 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/anpr-revoke.test.mjs +81 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Vehicle management: make deleting and editing a vehicle safe against the ANPR cameras.
|
|
6
|
+
|
|
7
|
+
Deleting a vehicle no longer reports success when the plate is still on a camera. `bypass` now defaults to false, `removePlateNumber` returns an explicit per-camera outcome instead of swallowing its error, and a delete that cannot be confirmed on every camera holding the plate is refused with the record left intact so it can be retried. A site with no ANPR camera has nothing to revoke and now deletes successfully instead of failing with "No site cameras found."
|
|
8
|
+
|
|
9
|
+
Each camera is asked for its own record id before the removal, because a Dahua `recNo` is a per-device sequence number and the one stored on the vehicle only matches whichever camera was written last. Editing only the vehicle's name now removes the old whitelist record before inserting the new one, instead of leaving the previous entry on the camera for good.
|
|
10
|
+
|
|
11
|
+
Also: `pullVehicleByRecNo` is scoped to the person or site the plate belongs to (it matched on the record id alone, which is not unique across devices), the paginated camera lookups no longer skip their last page, and editing or deleting a vehicle now requires `vehicle-mgmt:update-vehicle` / `vehicle-mgmt:delete-vehicle` on the server, matching the gate the apps already apply in the browser.
|
package/dist/index.d.ts
CHANGED
|
@@ -2505,6 +2505,31 @@ declare function useBuildingUnitController(): {
|
|
|
2505
2505
|
getAllLevelsWithUnits: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
2506
2506
|
};
|
|
2507
2507
|
|
|
2508
|
+
/**
|
|
2509
|
+
* "Was this plate actually taken off the camera?" — the one place that answers
|
|
2510
|
+
* it, for the three paths that revoke an ANPR whitelist/blocklist record:
|
|
2511
|
+
* deleting a vehicle, and the two branches of editing one.
|
|
2512
|
+
*
|
|
2513
|
+
* The reason this file exists: `removePlateNumber` used to `catch`, log, and
|
|
2514
|
+
* return `undefined`, so a device that refused, timed out or was unreachable
|
|
2515
|
+
* came back looking exactly like one that had removed the record. The delete
|
|
2516
|
+
* path then reported "Vehicle deleted successfully." while the plate was still
|
|
2517
|
+
* on the barrier — the vehicle could keep driving in after the record was gone
|
|
2518
|
+
* from the system, and there was nothing on screen to say so.
|
|
2519
|
+
*
|
|
2520
|
+
* Nothing here talks to a device or to the database, so it is testable on its
|
|
2521
|
+
* own — see `test/anpr-revoke.test.mjs`.
|
|
2522
|
+
*/
|
|
2523
|
+
/** One camera's answer to "remove this record". */
|
|
2524
|
+
type AnprRevokeOutcome = {
|
|
2525
|
+
/** True ONLY when the device confirmed the removal, or held nothing to remove. */
|
|
2526
|
+
ok: boolean;
|
|
2527
|
+
/** HTTP status the device answered with, when it answered at all. */
|
|
2528
|
+
statusCode?: number;
|
|
2529
|
+
/** Why it failed, in words an operator can act on. Empty when `ok`. */
|
|
2530
|
+
reason: string;
|
|
2531
|
+
};
|
|
2532
|
+
|
|
2508
2533
|
declare enum CameraType {
|
|
2509
2534
|
IP = "ip",
|
|
2510
2535
|
ANPR = "anpr"
|
|
@@ -2573,7 +2598,7 @@ declare function useDahuaService(): {
|
|
|
2573
2598
|
password: string;
|
|
2574
2599
|
mode: string;
|
|
2575
2600
|
recno: string;
|
|
2576
|
-
}) => Promise<
|
|
2601
|
+
}) => Promise<AnprRevokeOutcome>;
|
|
2577
2602
|
updatePlateNumber: (value: {
|
|
2578
2603
|
host: string;
|
|
2579
2604
|
username: string;
|
|
@@ -2592,6 +2617,18 @@ declare function useDahuaService(): {
|
|
|
2592
2617
|
password: string;
|
|
2593
2618
|
mode: string;
|
|
2594
2619
|
plateNumber: string;
|
|
2620
|
+
/**
|
|
2621
|
+
* Treat a device that did not answer 200 as an error rather than as "the
|
|
2622
|
+
* plate is not on this camera".
|
|
2623
|
+
*
|
|
2624
|
+
* `useDahuaDigest` does not throw on a bad status, so a 401 or a 500 comes
|
|
2625
|
+
* back as an empty body, which `parseDahuaFind` reads as `found=0`. For the
|
|
2626
|
+
* visitor check-in callers that has always meant "not blocklisted" and is
|
|
2627
|
+
* left exactly as it is — a camera fault must not stop a visitor at the
|
|
2628
|
+
* gate. For the vehicle revoke paths it would mean "nothing to remove", and
|
|
2629
|
+
* they must not conclude that from a camera that never answered.
|
|
2630
|
+
*/
|
|
2631
|
+
requireOk?: boolean;
|
|
2595
2632
|
}) => Promise<any>;
|
|
2596
2633
|
bulkInsertPlateNumber: (value: {
|
|
2597
2634
|
host: string;
|
|
@@ -4109,7 +4146,7 @@ declare function useCameraViewService(): {
|
|
|
4109
4146
|
userId?: string;
|
|
4110
4147
|
}) => Promise<{
|
|
4111
4148
|
reason: string;
|
|
4112
|
-
verified: "recorder-video-port"
|
|
4149
|
+
verified: "nothing" | "recorder-video-port";
|
|
4113
4150
|
verifiedDescription: string;
|
|
4114
4151
|
cameraVerified: null;
|
|
4115
4152
|
relayVerified: null;
|
|
@@ -4123,7 +4160,7 @@ declare function useCameraViewService(): {
|
|
|
4123
4160
|
driftSeconds: null;
|
|
4124
4161
|
detailUnavailableReason: string | null;
|
|
4125
4162
|
} | {
|
|
4126
|
-
verified: "recorder-video-port"
|
|
4163
|
+
verified: "nothing" | "recorder-video-port";
|
|
4127
4164
|
verifiedDescription: string;
|
|
4128
4165
|
cameraVerified: null;
|
|
4129
4166
|
relayVerified: null;
|
|
@@ -4166,7 +4203,7 @@ declare function useCameraViewService(): {
|
|
|
4166
4203
|
}) => Promise<{
|
|
4167
4204
|
cameras: ({
|
|
4168
4205
|
reason: string;
|
|
4169
|
-
verified: "recorder-video-port"
|
|
4206
|
+
verified: "nothing" | "recorder-video-port";
|
|
4170
4207
|
verifiedDescription: string;
|
|
4171
4208
|
cameraVerified: null;
|
|
4172
4209
|
relayVerified: null;
|
|
@@ -4180,7 +4217,7 @@ declare function useCameraViewService(): {
|
|
|
4180
4217
|
lastFrameAt: string | null;
|
|
4181
4218
|
unavailableReason: string | null;
|
|
4182
4219
|
} | {
|
|
4183
|
-
verified: "recorder-video-port"
|
|
4220
|
+
verified: "nothing" | "recorder-video-port";
|
|
4184
4221
|
verifiedDescription: string;
|
|
4185
4222
|
cameraVerified: null;
|
|
4186
4223
|
relayVerified: null;
|
|
@@ -5011,7 +5048,10 @@ declare function usePersonRepo(): {
|
|
|
5011
5048
|
plateNumber: string;
|
|
5012
5049
|
recNo: string;
|
|
5013
5050
|
}, session?: ClientSession) => Promise<void>;
|
|
5014
|
-
pullVehicleByRecNo: (recNo: string,
|
|
5051
|
+
pullVehicleByRecNo: (recNo: string, scope: {
|
|
5052
|
+
peopleId?: string | ObjectId;
|
|
5053
|
+
site?: string | ObjectId;
|
|
5054
|
+
}, session?: ClientSession) => Promise<void>;
|
|
5015
5055
|
getByUserId: (userId: string | ObjectId) => Promise<TPerson | null>;
|
|
5016
5056
|
reviewResidentPerson: (_id: ObjectId | string, value: Partial<TPerson>, session?: ClientSession) => Promise<mongodb.UpdateResult<TPerson>>;
|
|
5017
5057
|
addFromVisitor: (value: visitorPersonRepo, session?: ClientSession) => Promise<mongodb.InsertOneResult<TPerson>>;
|
package/dist/index.js
CHANGED
|
@@ -18186,11 +18186,23 @@ function usePersonRepo() {
|
|
|
18186
18186
|
throw new import_node_server_utils41.InternalServerError("Failed to push vehicle plates by nric.");
|
|
18187
18187
|
}
|
|
18188
18188
|
}
|
|
18189
|
-
async function pullVehicleByRecNo(recNo, session) {
|
|
18189
|
+
async function pullVehicleByRecNo(recNo, scope, session) {
|
|
18190
18190
|
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
18191
|
+
const owner = scope?.peopleId ? (0, import_node_server_utils41.toObjectId)(scope.peopleId) : null;
|
|
18192
|
+
const site = scope?.site ? (0, import_node_server_utils41.toObjectId)(scope.site) : null;
|
|
18193
|
+
if (!owner && !site) {
|
|
18194
|
+
throw new import_node_server_utils41.BadRequestError(
|
|
18195
|
+
"Cannot remove a vehicle plate without a person or a site."
|
|
18196
|
+
);
|
|
18197
|
+
}
|
|
18198
|
+
const filter = { "plates.recNo": recNo };
|
|
18199
|
+
if (owner)
|
|
18200
|
+
filter._id = owner;
|
|
18201
|
+
else
|
|
18202
|
+
filter.site = site;
|
|
18191
18203
|
try {
|
|
18192
18204
|
await collection.updateOne(
|
|
18193
|
-
|
|
18205
|
+
filter,
|
|
18194
18206
|
{
|
|
18195
18207
|
$pull: {
|
|
18196
18208
|
plates: {
|
|
@@ -22296,6 +22308,25 @@ function buildPlateRemoveEndpoint(value) {
|
|
|
22296
22308
|
return `/cgi-bin/recordUpdater.cgi?action=remove&recno=${enc(value.recno)}&name=${enc(value.mode)}`;
|
|
22297
22309
|
}
|
|
22298
22310
|
|
|
22311
|
+
// src/utils/anpr-revoke.util.ts
|
|
22312
|
+
function anprRevokeOutcome(response) {
|
|
22313
|
+
const statusCode = response?.statusCode ?? response?.status ?? response?.res?.status;
|
|
22314
|
+
if (statusCode === 200)
|
|
22315
|
+
return { ok: true, statusCode, reason: "" };
|
|
22316
|
+
if (statusCode === void 0) {
|
|
22317
|
+
return { ok: false, reason: "the camera did not answer" };
|
|
22318
|
+
}
|
|
22319
|
+
return { ok: false, statusCode, reason: `the camera answered ${statusCode}` };
|
|
22320
|
+
}
|
|
22321
|
+
function anprRevokeThrew(error) {
|
|
22322
|
+
const message = error?.message ?? String(error ?? "");
|
|
22323
|
+
return { ok: false, reason: message || "the camera could not be reached" };
|
|
22324
|
+
}
|
|
22325
|
+
function anprRevokeFailureMessage(failures, action = "deleted") {
|
|
22326
|
+
const detail = failures.map((failure) => `${failure.camera} (${failure.reason})`).join("; ");
|
|
22327
|
+
return `The plate number could not be removed from ${failures.length} ANPR camera(s): ${detail}. The vehicle has NOT been ${action} \u2014 it would still open the barrier. Check the camera, then try again.`;
|
|
22328
|
+
}
|
|
22329
|
+
|
|
22299
22330
|
// src/services/dahua.service.ts
|
|
22300
22331
|
function notifyCameraFault(camera, message) {
|
|
22301
22332
|
NotificationService.cameraFault({
|
|
@@ -23187,9 +23218,10 @@ function useDahuaService() {
|
|
|
23187
23218
|
mode: value.mode
|
|
23188
23219
|
})
|
|
23189
23220
|
});
|
|
23190
|
-
return response;
|
|
23221
|
+
return anprRevokeOutcome(response);
|
|
23191
23222
|
} catch (error2) {
|
|
23192
23223
|
loggerDahua.error(`[${value.host}] Error removing plate number:`, error2);
|
|
23224
|
+
return anprRevokeThrew(error2);
|
|
23193
23225
|
}
|
|
23194
23226
|
}
|
|
23195
23227
|
async function getPlateNumber(value) {
|
|
@@ -23198,7 +23230,8 @@ function useDahuaService() {
|
|
|
23198
23230
|
username: import_joi24.default.string().required(),
|
|
23199
23231
|
password: import_joi24.default.string().required(),
|
|
23200
23232
|
plateNumber: import_joi24.default.string().required(),
|
|
23201
|
-
mode: import_joi24.default.string().valid(...Object.values(ANPRMode)).required()
|
|
23233
|
+
mode: import_joi24.default.string().valid(...Object.values(ANPRMode)).required(),
|
|
23234
|
+
requireOk: import_joi24.default.boolean().optional()
|
|
23202
23235
|
});
|
|
23203
23236
|
const { error } = validation.validate(value);
|
|
23204
23237
|
if (error) {
|
|
@@ -23211,6 +23244,12 @@ function useDahuaService() {
|
|
|
23211
23244
|
password: value.password,
|
|
23212
23245
|
endpoint: `/cgi-bin/recordFinder.cgi?action=find&name=${value.mode}&condition.PlateNumber=${encodeURIComponent(value.plateNumber)}`
|
|
23213
23246
|
});
|
|
23247
|
+
if (value.requireOk) {
|
|
23248
|
+
const statusCode = getDahuaStatusCode(response);
|
|
23249
|
+
if (statusCode !== 200) {
|
|
23250
|
+
throw new Error(`the camera answered ${statusCode ?? "nothing"}`);
|
|
23251
|
+
}
|
|
23252
|
+
}
|
|
23214
23253
|
const text2 = response?.data?.toString?.("utf-8");
|
|
23215
23254
|
return text2;
|
|
23216
23255
|
} catch (error2) {
|
|
@@ -35215,31 +35254,72 @@ function useVehicleService() {
|
|
|
35215
35254
|
pages = siteCameraReq.pages || 1;
|
|
35216
35255
|
siteCameras.push(...siteCameraReq.items);
|
|
35217
35256
|
page++;
|
|
35218
|
-
} while (page
|
|
35219
|
-
|
|
35220
|
-
|
|
35221
|
-
|
|
35257
|
+
} while (page <= pages);
|
|
35258
|
+
import_node_server_utils99.logger.info(
|
|
35259
|
+
`deleteVehicle resolved ${siteCameras.length} ANPR camera(s) for site ${site}`
|
|
35260
|
+
);
|
|
35261
|
+
const vehicle = await findOne({ _id: new import_mongodb67.ObjectId(_id) });
|
|
35262
|
+
const plateNumber = String(vehicle?.plateNumber ?? "");
|
|
35263
|
+
const failures = [];
|
|
35222
35264
|
for (const camera of siteCameras) {
|
|
35223
35265
|
const host = camera.host;
|
|
35224
35266
|
const username = camera.username;
|
|
35225
35267
|
const password = camera.password;
|
|
35226
|
-
const
|
|
35268
|
+
const label = String(
|
|
35269
|
+
camera?.name || camera?._id || "ANPR camera"
|
|
35270
|
+
);
|
|
35271
|
+
let recnoForCamera = recno;
|
|
35272
|
+
if (plateNumber) {
|
|
35273
|
+
try {
|
|
35274
|
+
const found = parseDahuaFind(
|
|
35275
|
+
await _getPlateNumber({
|
|
35276
|
+
host,
|
|
35277
|
+
username,
|
|
35278
|
+
password,
|
|
35279
|
+
mode: _mode,
|
|
35280
|
+
plateNumber,
|
|
35281
|
+
requireOk: true
|
|
35282
|
+
}) ?? ""
|
|
35283
|
+
);
|
|
35284
|
+
if (!found.exists)
|
|
35285
|
+
continue;
|
|
35286
|
+
if (found.recNo)
|
|
35287
|
+
recnoForCamera = found.recNo;
|
|
35288
|
+
} catch (error) {
|
|
35289
|
+
failures.push({
|
|
35290
|
+
camera: label,
|
|
35291
|
+
reason: anprRevokeThrew(error).reason
|
|
35292
|
+
});
|
|
35293
|
+
continue;
|
|
35294
|
+
}
|
|
35295
|
+
}
|
|
35296
|
+
const outcome = await _removePlateNumber({
|
|
35227
35297
|
host,
|
|
35228
35298
|
username,
|
|
35229
35299
|
password,
|
|
35230
|
-
recno,
|
|
35300
|
+
recno: recnoForCamera,
|
|
35231
35301
|
mode: _mode
|
|
35232
|
-
};
|
|
35233
|
-
|
|
35234
|
-
|
|
35235
|
-
|
|
35236
|
-
|
|
35237
|
-
);
|
|
35302
|
+
});
|
|
35303
|
+
if (!outcome?.ok) {
|
|
35304
|
+
failures.push({
|
|
35305
|
+
camera: label,
|
|
35306
|
+
reason: outcome?.reason || "the camera did not answer"
|
|
35307
|
+
});
|
|
35238
35308
|
}
|
|
35239
35309
|
}
|
|
35310
|
+
if (failures.length) {
|
|
35311
|
+
const message = anprRevokeFailureMessage(failures);
|
|
35312
|
+
if (!bypass)
|
|
35313
|
+
throw new import_node_server_utils99.BadRequestError(message);
|
|
35314
|
+
import_node_server_utils99.logger.warn(`deleteVehicle bypassed a failed ANPR revoke: ${message}`);
|
|
35315
|
+
}
|
|
35240
35316
|
await Promise.all([
|
|
35241
35317
|
_deleteVehicle(_id, session),
|
|
35242
|
-
_pullVehicleByRecNo(
|
|
35318
|
+
_pullVehicleByRecNo(
|
|
35319
|
+
recno,
|
|
35320
|
+
{ peopleId: vehicle?.peopleId, site },
|
|
35321
|
+
session
|
|
35322
|
+
)
|
|
35243
35323
|
]);
|
|
35244
35324
|
await session.commitTransaction();
|
|
35245
35325
|
return `Vehicle plate number deleted from ${type} record successfully.`;
|
|
@@ -35303,7 +35383,7 @@ function useVehicleService() {
|
|
|
35303
35383
|
pages = siteCameraReq.pages || 1;
|
|
35304
35384
|
siteCameras.push(...siteCameraReq.items);
|
|
35305
35385
|
page++;
|
|
35306
|
-
} while (page
|
|
35386
|
+
} while (page <= pages);
|
|
35307
35387
|
if (!siteCameras.length) {
|
|
35308
35388
|
throw new import_node_server_utils99.BadRequestError("No site cameras found.");
|
|
35309
35389
|
}
|
|
@@ -35382,7 +35462,7 @@ function useVehicleService() {
|
|
|
35382
35462
|
pages = siteCameraReq.pages || 1;
|
|
35383
35463
|
siteCameras.push(...siteCameraReq.items);
|
|
35384
35464
|
page++;
|
|
35385
|
-
} while (page
|
|
35465
|
+
} while (page <= pages);
|
|
35386
35466
|
if (!siteCameras.length) {
|
|
35387
35467
|
throw new import_node_server_utils99.BadRequestError("No site cameras found.");
|
|
35388
35468
|
}
|
|
@@ -35460,7 +35540,7 @@ function useVehicleService() {
|
|
|
35460
35540
|
pages = siteCameraReq.pages || 1;
|
|
35461
35541
|
siteCameras.push(...siteCameraReq.items);
|
|
35462
35542
|
page++;
|
|
35463
|
-
} while (page
|
|
35543
|
+
} while (page <= pages);
|
|
35464
35544
|
if (!siteCameras.length) {
|
|
35465
35545
|
throw new import_node_server_utils99.BadRequestError("No site cameras found.");
|
|
35466
35546
|
}
|
|
@@ -35549,7 +35629,7 @@ function useVehicleService() {
|
|
|
35549
35629
|
pages = siteCameraReq.pages || 1;
|
|
35550
35630
|
siteCameras.push(...siteCameraReq.items);
|
|
35551
35631
|
page++;
|
|
35552
|
-
} while (page
|
|
35632
|
+
} while (page <= pages);
|
|
35553
35633
|
if (!siteCameras.length) {
|
|
35554
35634
|
throw new import_node_server_utils99.BadRequestError("No site cameras found.");
|
|
35555
35635
|
}
|
|
@@ -35604,6 +35684,37 @@ function useVehicleService() {
|
|
|
35604
35684
|
}
|
|
35605
35685
|
} else {
|
|
35606
35686
|
console.log("updateVehicleById _plateNumber == plateNumber", _plateNumber, plateNumber);
|
|
35687
|
+
const existing = parseDahuaFind(
|
|
35688
|
+
await _getPlateNumber({
|
|
35689
|
+
host,
|
|
35690
|
+
username,
|
|
35691
|
+
password,
|
|
35692
|
+
mode: _type === "blocklist" /* BLOCKLIST */ ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */,
|
|
35693
|
+
plateNumber,
|
|
35694
|
+
requireOk: true
|
|
35695
|
+
}) ?? ""
|
|
35696
|
+
);
|
|
35697
|
+
if (existing.exists) {
|
|
35698
|
+
const outcome = await _removePlateNumber({
|
|
35699
|
+
host,
|
|
35700
|
+
username,
|
|
35701
|
+
password,
|
|
35702
|
+
mode: _type === "blocklist" /* BLOCKLIST */ ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */,
|
|
35703
|
+
recno: existing.recNo || _recNo
|
|
35704
|
+
});
|
|
35705
|
+
if (!outcome?.ok) {
|
|
35706
|
+
throw new import_node_server_utils99.BadRequestError(
|
|
35707
|
+
anprRevokeFailureMessage([
|
|
35708
|
+
{
|
|
35709
|
+
camera: String(
|
|
35710
|
+
camera?.name || camera?._id || "ANPR camera"
|
|
35711
|
+
),
|
|
35712
|
+
reason: outcome?.reason || "the camera did not answer"
|
|
35713
|
+
}
|
|
35714
|
+
], "updated")
|
|
35715
|
+
);
|
|
35716
|
+
}
|
|
35717
|
+
}
|
|
35607
35718
|
const dahuaPayload = {
|
|
35608
35719
|
host,
|
|
35609
35720
|
username,
|
|
@@ -35785,7 +35896,7 @@ function useVehicleService() {
|
|
|
35785
35896
|
pages = siteCameraReq.pages || 1;
|
|
35786
35897
|
siteCameras.push(...siteCameraReq.items);
|
|
35787
35898
|
page++;
|
|
35788
|
-
} while (page
|
|
35899
|
+
} while (page <= pages);
|
|
35789
35900
|
if (!siteCameras.length) {
|
|
35790
35901
|
throw new import_node_server_utils99.BadRequestError("No site cameras found.");
|
|
35791
35902
|
}
|
|
@@ -35877,7 +35988,7 @@ function useVehicleService() {
|
|
|
35877
35988
|
pages = siteCameraReq.pages || 1;
|
|
35878
35989
|
siteCameras.push(...siteCameraReq.items);
|
|
35879
35990
|
page++;
|
|
35880
|
-
} while (page
|
|
35991
|
+
} while (page <= pages);
|
|
35881
35992
|
if (!siteCameras.length) {
|
|
35882
35993
|
throw new import_node_server_utils99.BadRequestError("No site cameras found.");
|
|
35883
35994
|
}
|
|
@@ -39223,10 +39334,18 @@ function useVehicleController() {
|
|
|
39223
39334
|
getBlocklistedVehicles: _getBlocklistedVehicles,
|
|
39224
39335
|
getVehicleSite: _getVehicleSite
|
|
39225
39336
|
} = useVehicleRepo();
|
|
39226
|
-
async function requireVehicleReach(req, _id) {
|
|
39337
|
+
async function requireVehicleReach(req, _id, permission) {
|
|
39227
39338
|
const vehicle = await _getVehicleSite(_id);
|
|
39228
|
-
await requireSiteReach(req, vehicle?.site);
|
|
39339
|
+
await requireSiteReach(req, vehicle?.site, permission);
|
|
39229
39340
|
}
|
|
39341
|
+
const VEHICLE_UPDATE = {
|
|
39342
|
+
permissions: ["vehicle-mgmt:update-vehicle"],
|
|
39343
|
+
message: "You do not have permission to edit vehicles."
|
|
39344
|
+
};
|
|
39345
|
+
const VEHICLE_DELETE = {
|
|
39346
|
+
permissions: ["vehicle-mgmt:delete-vehicle"],
|
|
39347
|
+
message: "You do not have permission to delete vehicles."
|
|
39348
|
+
};
|
|
39230
39349
|
function normalizeRow(row) {
|
|
39231
39350
|
return Object.fromEntries(
|
|
39232
39351
|
Object.entries(row).map(([key, value]) => [
|
|
@@ -39595,7 +39714,7 @@ function useVehicleController() {
|
|
|
39595
39714
|
return;
|
|
39596
39715
|
}
|
|
39597
39716
|
const { _id, ...rest } = value;
|
|
39598
|
-
await requireVehicleReach(req, _id);
|
|
39717
|
+
await requireVehicleReach(req, _id, VEHICLE_UPDATE);
|
|
39599
39718
|
await _updateVehicleById(_id, rest);
|
|
39600
39719
|
res.json({ message: "Successfully updated vehicle." });
|
|
39601
39720
|
return;
|
|
@@ -39617,7 +39736,11 @@ function useVehicleController() {
|
|
|
39617
39736
|
recno: import_joi55.default.string().required(),
|
|
39618
39737
|
site: import_joi55.default.string().hex().length(24).required(),
|
|
39619
39738
|
type: import_joi55.default.string().valid(...Object.values(VehicleType)).required(),
|
|
39620
|
-
|
|
39739
|
+
// Fail closed. Defaulted to TRUE, and neither the web app nor the MA
|
|
39740
|
+
// app sends the field, so EVERY delete skipped the camera-revoke guard
|
|
39741
|
+
// and reported success whatever the cameras answered. Bypassing is only
|
|
39742
|
+
// ever honoured now when a caller asks for it explicitly.
|
|
39743
|
+
bypass: import_joi55.default.boolean().optional().default(false)
|
|
39621
39744
|
});
|
|
39622
39745
|
const { error, value } = deleteVehicleSchema.validate({
|
|
39623
39746
|
_id: req.params.id,
|
|
@@ -39631,7 +39754,7 @@ function useVehicleController() {
|
|
|
39631
39754
|
}
|
|
39632
39755
|
const { recno, site, type, bypass, _id } = value;
|
|
39633
39756
|
try {
|
|
39634
|
-
await requireVehicleReach(req, _id);
|
|
39757
|
+
await requireVehicleReach(req, _id, VEHICLE_DELETE);
|
|
39635
39758
|
const data = await _deleteVehicle(_id, recno, site, type, bypass);
|
|
39636
39759
|
res.json({
|
|
39637
39760
|
message: "Vehicle deleted successfully.",
|
|
@@ -39946,7 +40069,7 @@ function useSiteCameraService() {
|
|
|
39946
40069
|
pages = siteCameraReq.pages || 1;
|
|
39947
40070
|
siteCameras.push(...siteCameraReq.items);
|
|
39948
40071
|
page++;
|
|
39949
|
-
} while (page
|
|
40072
|
+
} while (page <= pages);
|
|
39950
40073
|
if (!siteCameras.length) {
|
|
39951
40074
|
throw new import_node_server_utils114.BadRequestError("No ANPR cameras found.");
|
|
39952
40075
|
}
|