@7365admin1/core 3.68.0 → 3.68.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/CHANGELOG.md +38 -0
- package/dist/index.d.ts +122 -6
- package/dist/index.js +2041 -858
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2219 -1035
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/anpr-plate.test.mjs +272 -0
- package/test/dahua-protocol.util.test.mjs +137 -0
- package/test/member-duplicate-error.test.mjs +30 -0
- package/test/people-create-gate.test.mjs +71 -0
package/dist/index.js
CHANGED
|
@@ -6145,6 +6145,7 @@ __export(src_exports, {
|
|
|
6145
6145
|
cameraGrant: () => cameraGrant,
|
|
6146
6146
|
cameraHealthClaim: () => cameraHealthClaim,
|
|
6147
6147
|
cameraHealthSummary: () => cameraHealthSummary,
|
|
6148
|
+
cameraListClearConfirmation: () => cameraListClearConfirmation,
|
|
6148
6149
|
cameraManagePermissions: () => cameraManagePermissions,
|
|
6149
6150
|
cameraProbeCacheKey: () => cameraProbeCacheKey,
|
|
6150
6151
|
cameraTransports: () => cameraTransports,
|
|
@@ -6208,6 +6209,7 @@ __export(src_exports, {
|
|
|
6208
6209
|
incidents_namespace_collection: () => incidents_namespace_collection,
|
|
6209
6210
|
invitationOffersRole: () => invitationOffersRole,
|
|
6210
6211
|
isCameraEntitled: () => isCameraEntitled,
|
|
6212
|
+
isDuplicateMemberError: () => isDuplicateMemberError,
|
|
6211
6213
|
isDuplicateVersionError: () => isDuplicateVersionError,
|
|
6212
6214
|
isLikelySelfServiceEmail: () => isLikelySelfServiceEmail,
|
|
6213
6215
|
isPatrolCctvCamera: () => isPatrolCctvCamera,
|
|
@@ -9830,6 +9832,9 @@ var import_node_server_utils13 = require("@7365admin1/node-server-utils");
|
|
|
9830
9832
|
var import_joi7 = __toESM(require("joi"));
|
|
9831
9833
|
var import_mongodb12 = require("mongodb");
|
|
9832
9834
|
var PLATFORM_STAFF_MEMBER_TYPE = "admin";
|
|
9835
|
+
function isDuplicateMemberError(error) {
|
|
9836
|
+
return error?.code === 11e3 || /E11000|duplicate key/i.test(error?.message ?? "");
|
|
9837
|
+
}
|
|
9833
9838
|
var MEMBER_TYPES = [
|
|
9834
9839
|
PLATFORM_STAFF_MEMBER_TYPE,
|
|
9835
9840
|
"organization",
|
|
@@ -9983,8 +9988,7 @@ function useMemberRepo() {
|
|
|
9983
9988
|
});
|
|
9984
9989
|
return "Successfully added member.";
|
|
9985
9990
|
} catch (error) {
|
|
9986
|
-
|
|
9987
|
-
if (isDuplicated && error.message.includes("org_1_user_1"))
|
|
9991
|
+
if (isDuplicateMemberError(error))
|
|
9988
9992
|
throw new import_node_server_utils14.BadRequestError(
|
|
9989
9993
|
"The user is already a member of this organization."
|
|
9990
9994
|
);
|
|
@@ -21860,6 +21864,7 @@ var import_fs2 = require("fs");
|
|
|
21860
21864
|
var import_joi26 = __toESM(require("joi"));
|
|
21861
21865
|
var path2 = __toESM(require("path"));
|
|
21862
21866
|
var import_urllib = require("urllib");
|
|
21867
|
+
var import_crypto2 = require("crypto");
|
|
21863
21868
|
|
|
21864
21869
|
// src/repositories/visitor-transaction.repo.ts
|
|
21865
21870
|
var import_node_server_utils56 = require("@7365admin1/node-server-utils");
|
|
@@ -24360,6 +24365,69 @@ function useVehicleRepo() {
|
|
|
24360
24365
|
throw error;
|
|
24361
24366
|
}
|
|
24362
24367
|
}
|
|
24368
|
+
async function getLivePlatesBySite(site) {
|
|
24369
|
+
let siteId;
|
|
24370
|
+
try {
|
|
24371
|
+
siteId = new import_mongodb47.ObjectId(site);
|
|
24372
|
+
} catch (error) {
|
|
24373
|
+
throw new import_node_server_utils58.BadRequestError("Invalid site ID format.");
|
|
24374
|
+
}
|
|
24375
|
+
try {
|
|
24376
|
+
return await collection.aggregate([
|
|
24377
|
+
{
|
|
24378
|
+
$match: {
|
|
24379
|
+
site: siteId,
|
|
24380
|
+
status: "active" /* ACTIVE */,
|
|
24381
|
+
plateNumber: { $nin: [null, ""] }
|
|
24382
|
+
}
|
|
24383
|
+
},
|
|
24384
|
+
{ $sort: { name: 1, plateNumber: 1 } },
|
|
24385
|
+
{
|
|
24386
|
+
$lookup: {
|
|
24387
|
+
from: "buildings",
|
|
24388
|
+
localField: "block",
|
|
24389
|
+
foreignField: "_id",
|
|
24390
|
+
pipeline: [{ $project: { name: 1 } }],
|
|
24391
|
+
as: "blockDoc"
|
|
24392
|
+
}
|
|
24393
|
+
},
|
|
24394
|
+
{
|
|
24395
|
+
$lookup: {
|
|
24396
|
+
from: "building-levels",
|
|
24397
|
+
localField: "level",
|
|
24398
|
+
foreignField: "_id",
|
|
24399
|
+
pipeline: [{ $project: { name: 1 } }],
|
|
24400
|
+
as: "levelDoc"
|
|
24401
|
+
}
|
|
24402
|
+
},
|
|
24403
|
+
{
|
|
24404
|
+
$lookup: {
|
|
24405
|
+
from: "building-units",
|
|
24406
|
+
localField: "unit",
|
|
24407
|
+
foreignField: "_id",
|
|
24408
|
+
pipeline: [{ $project: { name: 1 } }],
|
|
24409
|
+
as: "unitDoc"
|
|
24410
|
+
}
|
|
24411
|
+
},
|
|
24412
|
+
{
|
|
24413
|
+
$project: {
|
|
24414
|
+
plateNumber: 1,
|
|
24415
|
+
name: 1,
|
|
24416
|
+
start: 1,
|
|
24417
|
+
end: 1,
|
|
24418
|
+
type: 1,
|
|
24419
|
+
blockedFrom: 1,
|
|
24420
|
+
seasonPassType: 1,
|
|
24421
|
+
blockName: { $ifNull: [{ $arrayElemAt: ["$blockDoc.name", 0] }, ""] },
|
|
24422
|
+
levelName: { $ifNull: [{ $arrayElemAt: ["$levelDoc.name", 0] }, ""] },
|
|
24423
|
+
unitName: { $ifNull: [{ $arrayElemAt: ["$unitDoc.name", 0] }, ""] }
|
|
24424
|
+
}
|
|
24425
|
+
}
|
|
24426
|
+
]).toArray();
|
|
24427
|
+
} catch (error) {
|
|
24428
|
+
throw new import_node_server_utils58.InternalServerError("Failed to list the site's vehicles.");
|
|
24429
|
+
}
|
|
24430
|
+
}
|
|
24363
24431
|
async function getAllExpiredVehicles() {
|
|
24364
24432
|
try {
|
|
24365
24433
|
const items = await collection.find(expiredVehicleSweepFilter()).toArray();
|
|
@@ -24564,6 +24632,7 @@ function useVehicleRepo() {
|
|
|
24564
24632
|
getVehicleByPlateNumber,
|
|
24565
24633
|
getVehiclesByNRIC,
|
|
24566
24634
|
deleteExpiredVehicles,
|
|
24635
|
+
getLivePlatesBySite,
|
|
24567
24636
|
getAllVehiclesByUnitId,
|
|
24568
24637
|
getAllExpiredVehicles,
|
|
24569
24638
|
bulkUpsertVehicles,
|
|
@@ -24763,6 +24832,73 @@ function classifyDahuaFailure(message) {
|
|
|
24763
24832
|
}
|
|
24764
24833
|
return "transient";
|
|
24765
24834
|
}
|
|
24835
|
+
var DAHUA_COMMAND_ATTEMPTS = 10;
|
|
24836
|
+
var DAHUA_COMMAND_DEADLINE_MS = 45e3;
|
|
24837
|
+
var DAHUA_LOGIN_PROVEN_FOR_MS = 10 * 6e4;
|
|
24838
|
+
var COMMAND_RETRY_DELAYS_MS = [1e3, 2e3, 3e3, 4e3, 5e3];
|
|
24839
|
+
function dahuaCommandRetryDelayMs(failedAttempt) {
|
|
24840
|
+
const index = Math.min(
|
|
24841
|
+
Math.max(Math.floor(failedAttempt), 1),
|
|
24842
|
+
COMMAND_RETRY_DELAYS_MS.length
|
|
24843
|
+
);
|
|
24844
|
+
return COMMAND_RETRY_DELAYS_MS[index - 1];
|
|
24845
|
+
}
|
|
24846
|
+
var NOT_SENT_PATTERN = /ECONNREFUSED|EHOSTUNREACH|ENETUNREACH|EAI_AGAIN|ENOTFOUND|UND_ERR_CONNECT_TIMEOUT|connect ?timeout|ConnectTimeout/i;
|
|
24847
|
+
function classifyDahuaAttempt(attempt) {
|
|
24848
|
+
if (attempt.error !== void 0 && attempt.error !== null) {
|
|
24849
|
+
const error = attempt.error;
|
|
24850
|
+
const message = [error?.name, error?.code, error?.cause?.code, error?.message ?? error].filter(Boolean).join(" ");
|
|
24851
|
+
if (/invalid authority|\b401\b|unauthori[sz]ed/i.test(message)) {
|
|
24852
|
+
return "login-refused";
|
|
24853
|
+
}
|
|
24854
|
+
if (/\b403\b|forbidden/i.test(message))
|
|
24855
|
+
return "forbidden";
|
|
24856
|
+
if (NOT_SENT_PATTERN.test(message))
|
|
24857
|
+
return "not-sent";
|
|
24858
|
+
return "maybe-sent";
|
|
24859
|
+
}
|
|
24860
|
+
const status = attempt.statusCode;
|
|
24861
|
+
if (status === 401 || /invalid authority/i.test(String(attempt.text ?? ""))) {
|
|
24862
|
+
return "login-refused";
|
|
24863
|
+
}
|
|
24864
|
+
if (status === 403)
|
|
24865
|
+
return "forbidden";
|
|
24866
|
+
if (status === 502 || status === 503 || status === 504)
|
|
24867
|
+
return "busy";
|
|
24868
|
+
if (status === void 0)
|
|
24869
|
+
return "maybe-sent";
|
|
24870
|
+
return "answered";
|
|
24871
|
+
}
|
|
24872
|
+
function shouldRetryDahuaCommand(options) {
|
|
24873
|
+
const {
|
|
24874
|
+
kind,
|
|
24875
|
+
attempt,
|
|
24876
|
+
loginFailures,
|
|
24877
|
+
loginProven,
|
|
24878
|
+
repeatable,
|
|
24879
|
+
elapsedMs,
|
|
24880
|
+
nextDelayMs,
|
|
24881
|
+
maxAttempts = DAHUA_COMMAND_ATTEMPTS,
|
|
24882
|
+
deadlineMs = DAHUA_COMMAND_DEADLINE_MS
|
|
24883
|
+
} = options;
|
|
24884
|
+
if (kind === "answered" || kind === "forbidden")
|
|
24885
|
+
return false;
|
|
24886
|
+
if (attempt >= maxAttempts)
|
|
24887
|
+
return false;
|
|
24888
|
+
if (elapsedMs + nextDelayMs > deadlineMs)
|
|
24889
|
+
return false;
|
|
24890
|
+
switch (kind) {
|
|
24891
|
+
case "login-refused":
|
|
24892
|
+
return loginProven || loginFailures < DAHUA_AUTH_ATTEMPTS_BEFORE_ALERT;
|
|
24893
|
+
case "busy":
|
|
24894
|
+
case "not-sent":
|
|
24895
|
+
return true;
|
|
24896
|
+
case "maybe-sent":
|
|
24897
|
+
return repeatable;
|
|
24898
|
+
default:
|
|
24899
|
+
return false;
|
|
24900
|
+
}
|
|
24901
|
+
}
|
|
24766
24902
|
var enc = (value) => encodeURIComponent(String(value));
|
|
24767
24903
|
function buildPlateUpdateEndpoint(value) {
|
|
24768
24904
|
return `/cgi-bin/recordUpdater.cgi?action=update&name=${enc(value.mode)}&recno=${enc(value.recno)}&PlateNumber=${enc(value.plateNumber)}&BeginTime=${enc(value.start)}&CancelTime=${enc(value.end)}&+OpenGate=${value.openGate}&MasterOfCar=${enc(value.owner)}`;
|
|
@@ -24773,6 +24909,13 @@ function buildPlateInsertEndpoint(value) {
|
|
|
24773
24909
|
function buildPlateRemoveEndpoint(value) {
|
|
24774
24910
|
return `/cgi-bin/recordUpdater.cgi?action=remove&recno=${enc(value.recno)}&name=${enc(value.mode)}`;
|
|
24775
24911
|
}
|
|
24912
|
+
var CLEARABLE_PLATE_LISTS = ["TrafficRedList", "TrafficBlackList"];
|
|
24913
|
+
function buildPlateClearEndpoint(value) {
|
|
24914
|
+
if (!CLEARABLE_PLATE_LISTS.includes(value.mode)) {
|
|
24915
|
+
throw new Error(`Refusing to clear record set ${JSON.stringify(value.mode)}.`);
|
|
24916
|
+
}
|
|
24917
|
+
return `/cgi-bin/recordUpdater.cgi?action=clear&name=${enc(value.mode)}`;
|
|
24918
|
+
}
|
|
24776
24919
|
|
|
24777
24920
|
// src/utils/anpr-revoke.util.ts
|
|
24778
24921
|
function anprRevokeOutcome(response) {
|
|
@@ -24898,6 +25041,148 @@ function anprNoCameraRefusal(outcome) {
|
|
|
24898
25041
|
return `There is no ANPR camera set up at this site, so the plate cannot be registered on a barrier. The vehicle has NOT been ${outcome}. Add a camera in Settings first, then try again.`;
|
|
24899
25042
|
}
|
|
24900
25043
|
|
|
25044
|
+
// src/utils/anpr-plate.util.ts
|
|
25045
|
+
var ALLOW_CAMERA_DIRECTIONS = ["entry", "both", "residents"];
|
|
25046
|
+
var BLOCK_CAMERA_DIRECTIONS = ["entry", "both", "residents", "visitors"];
|
|
25047
|
+
function isAllowCameraDirection(direction) {
|
|
25048
|
+
return ALLOW_CAMERA_DIRECTIONS.includes(
|
|
25049
|
+
String(direction ?? "").trim().toLowerCase()
|
|
25050
|
+
);
|
|
25051
|
+
}
|
|
25052
|
+
function isBlockCameraDirection(direction) {
|
|
25053
|
+
return BLOCK_CAMERA_DIRECTIONS.includes(
|
|
25054
|
+
String(direction ?? "").trim().toLowerCase()
|
|
25055
|
+
);
|
|
25056
|
+
}
|
|
25057
|
+
function listsFilledByDirectionChange(from, to) {
|
|
25058
|
+
const before = String(from ?? "").trim().toLowerCase();
|
|
25059
|
+
const after = String(to ?? "").trim().toLowerCase();
|
|
25060
|
+
if (!after || after === before)
|
|
25061
|
+
return { allow: false, block: false };
|
|
25062
|
+
return {
|
|
25063
|
+
allow: isAllowCameraDirection(after) && !isAllowCameraDirection(before),
|
|
25064
|
+
block: isBlockCameraDirection(after) && !isBlockCameraDirection(before)
|
|
25065
|
+
};
|
|
25066
|
+
}
|
|
25067
|
+
function listsClearedByDirectionChange(from, to) {
|
|
25068
|
+
const before = String(from ?? "").trim().toLowerCase();
|
|
25069
|
+
const after = String(to ?? "").trim().toLowerCase();
|
|
25070
|
+
if (!after || after === before)
|
|
25071
|
+
return { allow: false, block: false };
|
|
25072
|
+
if (after === "exit")
|
|
25073
|
+
return { allow: true, block: true };
|
|
25074
|
+
return {
|
|
25075
|
+
allow: isAllowCameraDirection(before) && !isAllowCameraDirection(after),
|
|
25076
|
+
block: false
|
|
25077
|
+
};
|
|
25078
|
+
}
|
|
25079
|
+
function mustRevokeFrom(direction, list, vehicleIsBlocked = false) {
|
|
25080
|
+
if (list === "allowed")
|
|
25081
|
+
return isAllowCameraDirection(direction);
|
|
25082
|
+
if (isBlockCameraDirection(direction))
|
|
25083
|
+
return true;
|
|
25084
|
+
return vehicleIsBlocked;
|
|
25085
|
+
}
|
|
25086
|
+
function normalisePlate(plate) {
|
|
25087
|
+
return String(plate ?? "").replace(/\s+/g, "").toUpperCase();
|
|
25088
|
+
}
|
|
25089
|
+
function samePlate(a, b) {
|
|
25090
|
+
const left = normalisePlate(a);
|
|
25091
|
+
return left !== "" && left === normalisePlate(b);
|
|
25092
|
+
}
|
|
25093
|
+
function parseDahuaFindRecords(text2) {
|
|
25094
|
+
const body = String(text2 ?? "");
|
|
25095
|
+
const found = Number(body.match(/found=(\d+)/i)?.[1] || 0);
|
|
25096
|
+
const byIndex = /* @__PURE__ */ new Map();
|
|
25097
|
+
const pattern = /records\[(\d+)\]\.(RecNo|PlateNumber|BeginTime|CancelTime|MasterOfCar)=([^\r\n]*)/gi;
|
|
25098
|
+
for (const match of body.matchAll(pattern)) {
|
|
25099
|
+
const index = Number(match[1]);
|
|
25100
|
+
const key = match[2].toLowerCase();
|
|
25101
|
+
const value = match[3].trim();
|
|
25102
|
+
const record = byIndex.get(index) ?? { recNo: "", plateNumber: "" };
|
|
25103
|
+
if (key === "recno")
|
|
25104
|
+
record.recNo = value;
|
|
25105
|
+
else if (key === "platenumber")
|
|
25106
|
+
record.plateNumber = value;
|
|
25107
|
+
else if (key === "begintime")
|
|
25108
|
+
record.beginTime = value;
|
|
25109
|
+
else if (key === "canceltime")
|
|
25110
|
+
record.cancelTime = value;
|
|
25111
|
+
else
|
|
25112
|
+
record.owner = value;
|
|
25113
|
+
byIndex.set(index, record);
|
|
25114
|
+
}
|
|
25115
|
+
const records = Array.from(byIndex.entries()).sort(([a], [b]) => a - b).map(([, record]) => record).filter((record) => record.recNo !== "");
|
|
25116
|
+
return { found, records };
|
|
25117
|
+
}
|
|
25118
|
+
function matchingPlateRecords(text2, plateNumber) {
|
|
25119
|
+
return parseDahuaFindRecords(text2).records.filter(
|
|
25120
|
+
(record) => record.plateNumber === "" || samePlate(record.plateNumber, plateNumber)
|
|
25121
|
+
);
|
|
25122
|
+
}
|
|
25123
|
+
function listsHeldBy(vehicle) {
|
|
25124
|
+
const type = String(vehicle?.type ?? "");
|
|
25125
|
+
return {
|
|
25126
|
+
allow: type === "whitelist" || String(vehicle?.blockedFrom ?? "") === "whitelist",
|
|
25127
|
+
block: type === "blocklist"
|
|
25128
|
+
};
|
|
25129
|
+
}
|
|
25130
|
+
function listsToRevoke(othersStillLive) {
|
|
25131
|
+
const others = (othersStillLive ?? []).map(listsHeldBy);
|
|
25132
|
+
return {
|
|
25133
|
+
allow: !others.some((held) => held.allow),
|
|
25134
|
+
block: !others.some((held) => held.block)
|
|
25135
|
+
};
|
|
25136
|
+
}
|
|
25137
|
+
var DEFAULT_CAMERA_TIMEZONE = "Asia/Singapore";
|
|
25138
|
+
function usableTimezone(timezone) {
|
|
25139
|
+
const zone = String(timezone ?? "").trim();
|
|
25140
|
+
if (!zone)
|
|
25141
|
+
return DEFAULT_CAMERA_TIMEZONE;
|
|
25142
|
+
try {
|
|
25143
|
+
new Intl.DateTimeFormat("en-US", { timeZone: zone });
|
|
25144
|
+
return zone;
|
|
25145
|
+
} catch {
|
|
25146
|
+
return DEFAULT_CAMERA_TIMEZONE;
|
|
25147
|
+
}
|
|
25148
|
+
}
|
|
25149
|
+
var WALL_CLOCK = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})(?::(\d{2}))?$/;
|
|
25150
|
+
function dahuaWallClock(value, timezone) {
|
|
25151
|
+
if (value === null || value === void 0 || value === "")
|
|
25152
|
+
return null;
|
|
25153
|
+
if (typeof value === "string") {
|
|
25154
|
+
const wall = value.trim().match(WALL_CLOCK);
|
|
25155
|
+
if (wall) {
|
|
25156
|
+
const [, y, mo, d, h, mi, se] = wall;
|
|
25157
|
+
return `${y}-${mo}-${d} ${h}:${mi}:${se ?? "00"}`;
|
|
25158
|
+
}
|
|
25159
|
+
}
|
|
25160
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
25161
|
+
if (Number.isNaN(date.getTime()))
|
|
25162
|
+
return null;
|
|
25163
|
+
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
25164
|
+
timeZone: usableTimezone(timezone),
|
|
25165
|
+
year: "numeric",
|
|
25166
|
+
month: "2-digit",
|
|
25167
|
+
day: "2-digit",
|
|
25168
|
+
hour: "2-digit",
|
|
25169
|
+
minute: "2-digit",
|
|
25170
|
+
second: "2-digit",
|
|
25171
|
+
hourCycle: "h23"
|
|
25172
|
+
}).formatToParts(date);
|
|
25173
|
+
const part = (type) => parts.find((entry) => entry.type === type)?.value ?? "00";
|
|
25174
|
+
return `${part("year")}-${part("month")}-${part("day")} ${part("hour")}:${part("minute")}:${part("second")}`;
|
|
25175
|
+
}
|
|
25176
|
+
function plateWindowMatches(record, wanted) {
|
|
25177
|
+
const same = (held, want) => {
|
|
25178
|
+
const a = dahuaWallClock(held);
|
|
25179
|
+
return a !== null && a === dahuaWallClock(want);
|
|
25180
|
+
};
|
|
25181
|
+
if (!wanted.start || !wanted.end)
|
|
25182
|
+
return false;
|
|
25183
|
+
return same(record.beginTime, wanted.start) && same(record.cancelTime, wanted.end);
|
|
25184
|
+
}
|
|
25185
|
+
|
|
24901
25186
|
// src/services/dahua.service.ts
|
|
24902
25187
|
function notifyCameraFault(camera, message) {
|
|
24903
25188
|
NotificationService.cameraFault({
|
|
@@ -24969,12 +25254,14 @@ async function useDahuaDigestWithRetry({
|
|
|
24969
25254
|
const statusCode = getDahuaStatusCode(response);
|
|
24970
25255
|
if (streaming) {
|
|
24971
25256
|
if (statusCode === 200) {
|
|
25257
|
+
markLoginProven(host, username, password);
|
|
24972
25258
|
return response;
|
|
24973
25259
|
}
|
|
24974
25260
|
} else {
|
|
24975
25261
|
const text2 = getDahuaResponseText(response);
|
|
24976
25262
|
const isOk = statusCode === 200 && text2.includes("OK");
|
|
24977
25263
|
if (isOk) {
|
|
25264
|
+
markLoginProven(host, username, password);
|
|
24978
25265
|
return response;
|
|
24979
25266
|
}
|
|
24980
25267
|
}
|
|
@@ -25008,6 +25295,123 @@ async function useDahuaDigestWithRetry({
|
|
|
25008
25295
|
}
|
|
25009
25296
|
throw lastError;
|
|
25010
25297
|
}
|
|
25298
|
+
var provenLogins = /* @__PURE__ */ new Map();
|
|
25299
|
+
function loginKey(host, username, password) {
|
|
25300
|
+
return (0, import_crypto2.createHash)("sha256").update(`${host ?? ""}
|
|
25301
|
+
${username ?? ""}
|
|
25302
|
+
${password ?? ""}`).digest("hex");
|
|
25303
|
+
}
|
|
25304
|
+
function markLoginProven(host, username, password) {
|
|
25305
|
+
provenLogins.set(loginKey(host, username, password), Date.now());
|
|
25306
|
+
}
|
|
25307
|
+
function forgetLogin(host, username, password) {
|
|
25308
|
+
provenLogins.delete(loginKey(host, username, password));
|
|
25309
|
+
}
|
|
25310
|
+
function isLoginProven(host, username, password) {
|
|
25311
|
+
const at2 = provenLogins.get(loginKey(host, username, password));
|
|
25312
|
+
return at2 !== void 0 && Date.now() - at2 < DAHUA_LOGIN_PROVEN_FOR_MS;
|
|
25313
|
+
}
|
|
25314
|
+
var ATTEMPT_WORDS = {
|
|
25315
|
+
"login-refused": "login refused",
|
|
25316
|
+
forbidden: "credentials refused",
|
|
25317
|
+
busy: "camera busy",
|
|
25318
|
+
"not-sent": "could not connect",
|
|
25319
|
+
"maybe-sent": "connection lost"
|
|
25320
|
+
};
|
|
25321
|
+
async function sendCameraCommand({
|
|
25322
|
+
host,
|
|
25323
|
+
username,
|
|
25324
|
+
password,
|
|
25325
|
+
endpoint,
|
|
25326
|
+
timeout = 1e4,
|
|
25327
|
+
repeatable = true,
|
|
25328
|
+
label = "command",
|
|
25329
|
+
deadlineMs = DAHUA_COMMAND_DEADLINE_MS
|
|
25330
|
+
}) {
|
|
25331
|
+
const startedAt = Date.now();
|
|
25332
|
+
let loginFailures = 0;
|
|
25333
|
+
for (let attempt = 1; ; attempt++) {
|
|
25334
|
+
let response;
|
|
25335
|
+
let error;
|
|
25336
|
+
try {
|
|
25337
|
+
response = await useDahuaDigest({
|
|
25338
|
+
host,
|
|
25339
|
+
username,
|
|
25340
|
+
password,
|
|
25341
|
+
endpoint,
|
|
25342
|
+
headers: { Connection: "close" },
|
|
25343
|
+
timeout
|
|
25344
|
+
});
|
|
25345
|
+
} catch (thrown) {
|
|
25346
|
+
error = thrown ?? new Error("the request failed with no error");
|
|
25347
|
+
}
|
|
25348
|
+
const kind = classifyDahuaAttempt(
|
|
25349
|
+
error ? { error } : {
|
|
25350
|
+
statusCode: getDahuaStatusCode(response),
|
|
25351
|
+
text: getDahuaResponseText(response)
|
|
25352
|
+
}
|
|
25353
|
+
);
|
|
25354
|
+
if (kind === "answered") {
|
|
25355
|
+
markLoginProven(host, username, password);
|
|
25356
|
+
if (attempt > 1) {
|
|
25357
|
+
loggerDahua.info(`[${host}] ${label}: went through on try ${attempt}`);
|
|
25358
|
+
}
|
|
25359
|
+
return response;
|
|
25360
|
+
}
|
|
25361
|
+
if (kind === "login-refused")
|
|
25362
|
+
loginFailures++;
|
|
25363
|
+
if (kind === "forbidden")
|
|
25364
|
+
forgetLogin(host, username, password);
|
|
25365
|
+
const nextDelayMs = dahuaCommandRetryDelayMs(attempt);
|
|
25366
|
+
const retry = shouldRetryDahuaCommand({
|
|
25367
|
+
kind,
|
|
25368
|
+
attempt,
|
|
25369
|
+
loginFailures,
|
|
25370
|
+
loginProven: isLoginProven(host, username, password),
|
|
25371
|
+
repeatable,
|
|
25372
|
+
elapsedMs: Date.now() - startedAt,
|
|
25373
|
+
nextDelayMs,
|
|
25374
|
+
deadlineMs
|
|
25375
|
+
});
|
|
25376
|
+
const why = ATTEMPT_WORDS[kind] ?? kind;
|
|
25377
|
+
if (!retry) {
|
|
25378
|
+
if (attempt > 1 || kind !== "forbidden") {
|
|
25379
|
+
loggerDahua.warn(
|
|
25380
|
+
`[${host}] ${label}: ${why} on try ${attempt}/${DAHUA_COMMAND_ATTEMPTS}; giving up`
|
|
25381
|
+
);
|
|
25382
|
+
}
|
|
25383
|
+
if (error)
|
|
25384
|
+
throw error;
|
|
25385
|
+
return response;
|
|
25386
|
+
}
|
|
25387
|
+
loggerDahua.warn(
|
|
25388
|
+
`[${host}] ${label}: ${why} on try ${attempt}/${DAHUA_COMMAND_ATTEMPTS}; trying again in ${nextDelayMs} ms`
|
|
25389
|
+
);
|
|
25390
|
+
await sleep(nextDelayMs);
|
|
25391
|
+
}
|
|
25392
|
+
}
|
|
25393
|
+
async function sendCameraCommandExpectingOk(options) {
|
|
25394
|
+
const response = await sendCameraCommand(options);
|
|
25395
|
+
const statusCode = getDahuaStatusCode(response);
|
|
25396
|
+
const text2 = getDahuaResponseText(response);
|
|
25397
|
+
if (statusCode === 200 && text2.includes("OK"))
|
|
25398
|
+
return response;
|
|
25399
|
+
const error = new Error(`Dahua response: ${text2 || `HTTP ${statusCode}`}`);
|
|
25400
|
+
error.answeredByCamera = true;
|
|
25401
|
+
throw error;
|
|
25402
|
+
}
|
|
25403
|
+
function anprOwnerName(owner) {
|
|
25404
|
+
return String(owner ?? "").replace(/['"]/g, "").replace(/[\/\\]/g, " - ").substring(0, 15).trim() || "unknown";
|
|
25405
|
+
}
|
|
25406
|
+
function plateWindowOnCamera(value) {
|
|
25407
|
+
const tenMinutesAgo = new Date(Date.now() - 10 * 6e4);
|
|
25408
|
+
const tenYearsOn = new Date(tenMinutesAgo);
|
|
25409
|
+
tenYearsOn.setFullYear(tenYearsOn.getFullYear() + 10);
|
|
25410
|
+
return {
|
|
25411
|
+
start: dahuaWallClock(value.start, value.timezone) ?? dahuaWallClock(tenMinutesAgo, value.timezone),
|
|
25412
|
+
end: dahuaWallClock(value.end, value.timezone) ?? dahuaWallClock(tenYearsOn, value.timezone)
|
|
25413
|
+
};
|
|
25414
|
+
}
|
|
25011
25415
|
function createFileNameFromEvent(utcSeconds, utcMilliseconds) {
|
|
25012
25416
|
const date = new Date(utcSeconds * 1e3 + utcMilliseconds);
|
|
25013
25417
|
const year = date.getUTCFullYear();
|
|
@@ -25609,111 +26013,115 @@ function useDahuaService() {
|
|
|
25609
26013
|
start: import_joi26.default.string().isoDate().optional().allow("", null),
|
|
25610
26014
|
end: import_joi26.default.string().isoDate().optional().allow("", null),
|
|
25611
26015
|
owner: import_joi26.default.string().optional().allow("", null),
|
|
25612
|
-
isOpenGate: import_joi26.default.boolean().optional().allow(null)
|
|
26016
|
+
isOpenGate: import_joi26.default.boolean().optional().allow(null),
|
|
26017
|
+
timezone: import_joi26.default.string().optional().allow("", null)
|
|
25613
26018
|
});
|
|
25614
26019
|
const { error: validationError } = validation.validate(value);
|
|
25615
26020
|
if (validationError) {
|
|
25616
26021
|
throw new import_node_server_utils60.BadRequestError(`Validation error: ${validationError.message}`);
|
|
25617
26022
|
}
|
|
25618
26023
|
try {
|
|
25619
|
-
value.owner =
|
|
25620
|
-
const
|
|
25621
|
-
const date = dateStr ? new Date(dateStr) : /* @__PURE__ */ new Date();
|
|
25622
|
-
if (!dateStr) {
|
|
25623
|
-
date.setMinutes(date.getMinutes() - 10);
|
|
25624
|
-
}
|
|
25625
|
-
if (fallbackYearsAhead > 0 && !dateStr) {
|
|
25626
|
-
date.setFullYear(date.getFullYear() + fallbackYearsAhead);
|
|
25627
|
-
}
|
|
25628
|
-
const pad = (num) => String(num).padStart(2, "0");
|
|
25629
|
-
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
25630
|
-
};
|
|
25631
|
-
const formattedStart = formatDahuaDate3(value.start);
|
|
25632
|
-
const formattedEnd = formatDahuaDate3(value.end, 10);
|
|
26024
|
+
value.owner = anprOwnerName(value.owner);
|
|
26025
|
+
const { start: formattedStart, end: formattedEnd } = plateWindowOnCamera(value);
|
|
25633
26026
|
const beginTime = encodeURIComponent(formattedStart);
|
|
25634
26027
|
const cancelTime = encodeURIComponent(formattedEnd);
|
|
25635
26028
|
const plateNumber = encodeURIComponent(value.plateNumber);
|
|
25636
26029
|
const ownerName = encodeURIComponent(value.owner);
|
|
25637
26030
|
const endpoint = `/cgi-bin/recordUpdater.cgi?action=insert&name=${value.mode}&PlateNumber=${plateNumber}&BeginTime=${beginTime}&CancelTime=${cancelTime}&MasterOfCar=${ownerName}`;
|
|
25638
|
-
|
|
25639
|
-
|
|
25640
|
-
|
|
25641
|
-
|
|
25642
|
-
|
|
25643
|
-
|
|
25644
|
-
|
|
25645
|
-
|
|
25646
|
-
|
|
25647
|
-
|
|
25648
|
-
|
|
25649
|
-
|
|
25650
|
-
|
|
25651
|
-
statusCode: 200,
|
|
25652
|
-
data: Buffer.from(`recno=${insertedId}`)
|
|
25653
|
-
};
|
|
25654
|
-
} catch (insertError) {
|
|
25655
|
-
const errorMessage = insertError?.message || String(insertError);
|
|
25656
|
-
if (/recno=/i.test(errorMessage)) {
|
|
25657
|
-
loggerDahua.info(`[${value.host}] Insert confirmed successfully via raw RecNo response text.`);
|
|
25658
|
-
const insertedId = errorMessage.match(/\d+/)?.[0] || "unknown";
|
|
26031
|
+
const INSERT_ROUNDS = 3;
|
|
26032
|
+
for (let round = 1; ; round++) {
|
|
26033
|
+
try {
|
|
26034
|
+
const insertResponse = await sendCameraCommandExpectingOk({
|
|
26035
|
+
host: value.host,
|
|
26036
|
+
username: value.username,
|
|
26037
|
+
password: value.password,
|
|
26038
|
+
endpoint,
|
|
26039
|
+
repeatable: false,
|
|
26040
|
+
label: `add plate to ${value.mode}`
|
|
26041
|
+
});
|
|
26042
|
+
const insertText = getDahuaResponseText(insertResponse);
|
|
26043
|
+
const insertedId = insertText.match(/\d+/)?.[0] || "unknown";
|
|
25659
26044
|
return {
|
|
26045
|
+
...insertResponse,
|
|
25660
26046
|
statusCode: 200,
|
|
25661
26047
|
data: Buffer.from(`recno=${insertedId}`)
|
|
25662
26048
|
};
|
|
25663
|
-
}
|
|
25664
|
-
|
|
25665
|
-
|
|
25666
|
-
|
|
25667
|
-
|
|
25668
|
-
|
|
25669
|
-
|
|
25670
|
-
|
|
25671
|
-
|
|
25672
|
-
endpoint: findEndpoint,
|
|
25673
|
-
retries: 5
|
|
25674
|
-
});
|
|
25675
|
-
const textData = getDahuaResponseText(findResponse);
|
|
25676
|
-
recno = extractRecNo(textData);
|
|
25677
|
-
} catch (findError) {
|
|
25678
|
-
const findErrorMessage = findError?.message || String(findError);
|
|
25679
|
-
recno = extractRecNo(findErrorMessage);
|
|
25680
|
-
if (!recno) {
|
|
25681
|
-
loggerDahua.error(`[${value.host}] Failed handling existing duplicate plate flow`, findError);
|
|
25682
|
-
}
|
|
26049
|
+
} catch (insertError) {
|
|
26050
|
+
const errorMessage = insertError?.message || String(insertError);
|
|
26051
|
+
if (/recno=/i.test(errorMessage)) {
|
|
26052
|
+
loggerDahua.info(`[${value.host}] Insert confirmed successfully via raw RecNo response text.`);
|
|
26053
|
+
const insertedId = errorMessage.match(/\d+/)?.[0] || "unknown";
|
|
26054
|
+
return {
|
|
26055
|
+
statusCode: 200,
|
|
26056
|
+
data: Buffer.from(`recno=${insertedId}`)
|
|
26057
|
+
};
|
|
25683
26058
|
}
|
|
25684
|
-
|
|
25685
|
-
|
|
25686
|
-
|
|
26059
|
+
const duplicate = errorMessage.includes("Bad Request") || errorMessage.includes("Dahua response: Error");
|
|
26060
|
+
const lostInFlight = !insertError?.answeredByCamera && classifyDahuaAttempt({ error: insertError }) === "maybe-sent";
|
|
26061
|
+
if (duplicate || lostInFlight) {
|
|
26062
|
+
loggerDahua.info(
|
|
26063
|
+
`[${value.host}] Insert ${duplicate ? "failed (duplicate)" : "lost its connection"}. Checking if the plate is already on ${value.mode}...`
|
|
26064
|
+
);
|
|
26065
|
+
const findEndpoint = `/cgi-bin/recordFinder.cgi?action=find&name=${value.mode}&condition.PlateNumber=${plateNumber}`;
|
|
25687
26066
|
try {
|
|
25688
|
-
const
|
|
26067
|
+
const findResponse = await sendCameraCommand({
|
|
25689
26068
|
host: value.host,
|
|
25690
26069
|
username: value.username,
|
|
25691
26070
|
password: value.password,
|
|
25692
|
-
endpoint:
|
|
25693
|
-
|
|
26071
|
+
endpoint: findEndpoint,
|
|
26072
|
+
label: `find plate on ${value.mode}`
|
|
25694
26073
|
});
|
|
25695
|
-
|
|
25696
|
-
|
|
25697
|
-
|
|
25698
|
-
|
|
25699
|
-
|
|
25700
|
-
|
|
25701
|
-
|
|
25702
|
-
|
|
25703
|
-
|
|
25704
|
-
|
|
25705
|
-
|
|
25706
|
-
|
|
26074
|
+
if (getDahuaStatusCode(findResponse) === 200) {
|
|
26075
|
+
recno = extractRecNo(getDahuaResponseText(findResponse));
|
|
26076
|
+
} else {
|
|
26077
|
+
loggerDahua.error(
|
|
26078
|
+
`[${value.host}] Plate lookup after a failed insert answered HTTP ${getDahuaStatusCode(findResponse)}`
|
|
26079
|
+
);
|
|
26080
|
+
}
|
|
26081
|
+
} catch (findError) {
|
|
26082
|
+
loggerDahua.error(`[${value.host}] Failed handling existing duplicate plate flow`, findError);
|
|
26083
|
+
}
|
|
26084
|
+
if (recno) {
|
|
26085
|
+
loggerDahua.info(`[${value.host}] Found existing record ID: ${recno}. Updating registration instead...`);
|
|
26086
|
+
const updateEndpoint = `/cgi-bin/recordUpdater.cgi?action=update&name=${value.mode}&recno=${recno}&PlateNumber=${plateNumber}&BeginTime=${beginTime}&CancelTime=${cancelTime}&MasterOfCar=${ownerName}`;
|
|
26087
|
+
try {
|
|
26088
|
+
const updateResponse = await sendCameraCommandExpectingOk({
|
|
26089
|
+
host: value.host,
|
|
26090
|
+
username: value.username,
|
|
26091
|
+
password: value.password,
|
|
26092
|
+
endpoint: updateEndpoint,
|
|
26093
|
+
label: `update plate on ${value.mode}`
|
|
26094
|
+
});
|
|
25707
26095
|
return {
|
|
26096
|
+
...updateResponse,
|
|
25708
26097
|
statusCode: 200,
|
|
25709
26098
|
data: Buffer.from(`recno=${recno}`)
|
|
25710
26099
|
};
|
|
26100
|
+
} catch (updateError) {
|
|
26101
|
+
const updateErrorMessage = updateError?.message || String(updateError);
|
|
26102
|
+
if (/recno=/i.test(updateErrorMessage) || /ok/i.test(updateErrorMessage)) {
|
|
26103
|
+
loggerDahua.info(`[${value.host}] Update confirmed successfully despite wrapper omission.`);
|
|
26104
|
+
if (!recno) {
|
|
26105
|
+
recno = extractRecNo(updateErrorMessage);
|
|
26106
|
+
}
|
|
26107
|
+
return {
|
|
26108
|
+
statusCode: 200,
|
|
26109
|
+
data: Buffer.from(`recno=${recno}`)
|
|
26110
|
+
};
|
|
26111
|
+
}
|
|
26112
|
+
throw updateError;
|
|
25711
26113
|
}
|
|
25712
|
-
|
|
26114
|
+
}
|
|
26115
|
+
if (lostInFlight && round < INSERT_ROUNDS) {
|
|
26116
|
+
loggerDahua.warn(
|
|
26117
|
+
`[${value.host}] Plate not found after a lost insert; sending the insert again (round ${round + 1}/${INSERT_ROUNDS})`
|
|
26118
|
+
);
|
|
26119
|
+
await sleep(dahuaCommandRetryDelayMs(round));
|
|
26120
|
+
continue;
|
|
25713
26121
|
}
|
|
25714
26122
|
}
|
|
26123
|
+
throw insertError;
|
|
25715
26124
|
}
|
|
25716
|
-
throw insertError;
|
|
25717
26125
|
}
|
|
25718
26126
|
} catch (finalError) {
|
|
25719
26127
|
loggerDahua.error(
|
|
@@ -25733,7 +26141,8 @@ function useDahuaService() {
|
|
|
25733
26141
|
start: import_joi26.default.string().isoDate().optional().allow("", null),
|
|
25734
26142
|
end: import_joi26.default.string().isoDate().optional().allow("", null),
|
|
25735
26143
|
owner: import_joi26.default.string().optional().allow("", null),
|
|
25736
|
-
isOpenGate: import_joi26.default.boolean().optional()
|
|
26144
|
+
isOpenGate: import_joi26.default.boolean().optional(),
|
|
26145
|
+
timezone: import_joi26.default.string().optional().allow("", null)
|
|
25737
26146
|
});
|
|
25738
26147
|
const { error } = validation.validate(value);
|
|
25739
26148
|
if (error) {
|
|
@@ -25749,17 +26158,20 @@ function useDahuaService() {
|
|
|
25749
26158
|
mode: value.mode,
|
|
25750
26159
|
recno: value.recno,
|
|
25751
26160
|
plateNumber: value.plateNumber,
|
|
25752
|
-
|
|
25753
|
-
|
|
26161
|
+
// On the camera's clock (see `dahuaWallClock`); a time already written
|
|
26162
|
+
// for the camera passes through unchanged.
|
|
26163
|
+
start: dahuaWallClock(value.start, value.timezone) ?? value.start,
|
|
26164
|
+
end: dahuaWallClock(value.end, value.timezone) ?? value.end,
|
|
25754
26165
|
owner: value.owner,
|
|
25755
26166
|
openGate: _isOpenGate
|
|
25756
26167
|
});
|
|
25757
26168
|
try {
|
|
25758
|
-
const response = await
|
|
26169
|
+
const response = await sendCameraCommand({
|
|
25759
26170
|
host: value.host,
|
|
25760
26171
|
username: value.username,
|
|
25761
26172
|
password: value.password,
|
|
25762
|
-
endpoint
|
|
26173
|
+
endpoint,
|
|
26174
|
+
label: `update plate on ${value.mode}`
|
|
25763
26175
|
});
|
|
25764
26176
|
return response;
|
|
25765
26177
|
} catch (error2) {
|
|
@@ -25782,14 +26194,16 @@ function useDahuaService() {
|
|
|
25782
26194
|
throw new import_node_server_utils60.BadRequestError(`Validation error: ${error.message}`);
|
|
25783
26195
|
}
|
|
25784
26196
|
try {
|
|
25785
|
-
const response = await
|
|
26197
|
+
const response = await sendCameraCommand({
|
|
25786
26198
|
host: value.host,
|
|
25787
26199
|
username: value.username,
|
|
25788
26200
|
password: value.password,
|
|
25789
26201
|
endpoint: buildPlateRemoveEndpoint({
|
|
25790
26202
|
recno: value.recno,
|
|
25791
26203
|
mode: value.mode
|
|
25792
|
-
})
|
|
26204
|
+
}),
|
|
26205
|
+
repeatable: false,
|
|
26206
|
+
label: `remove plate from ${value.mode}`
|
|
25793
26207
|
});
|
|
25794
26208
|
return anprRevokeOutcome(response);
|
|
25795
26209
|
} catch (error2) {
|
|
@@ -25811,11 +26225,18 @@ function useDahuaService() {
|
|
|
25811
26225
|
throw new import_node_server_utils60.BadRequestError(`Validation error: ${error.message}`);
|
|
25812
26226
|
}
|
|
25813
26227
|
try {
|
|
25814
|
-
const
|
|
26228
|
+
const findEndpoint = `/cgi-bin/recordFinder.cgi?action=find&name=${value.mode}&condition.PlateNumber=${encodeURIComponent(value.plateNumber)}`;
|
|
26229
|
+
const response = value.requireOk ? await sendCameraCommand({
|
|
26230
|
+
host: value.host,
|
|
26231
|
+
username: value.username,
|
|
26232
|
+
password: value.password,
|
|
26233
|
+
endpoint: findEndpoint,
|
|
26234
|
+
label: `find plate on ${value.mode}`
|
|
26235
|
+
}) : await useDahuaDigest({
|
|
25815
26236
|
host: value.host,
|
|
25816
26237
|
username: value.username,
|
|
25817
26238
|
password: value.password,
|
|
25818
|
-
endpoint:
|
|
26239
|
+
endpoint: findEndpoint
|
|
25819
26240
|
});
|
|
25820
26241
|
if (value.requireOk) {
|
|
25821
26242
|
const statusCode = getDahuaStatusCode(response);
|
|
@@ -25842,7 +26263,8 @@ function useDahuaService() {
|
|
|
25842
26263
|
owner: import_joi26.default.string().optional().allow("", null),
|
|
25843
26264
|
isOpenGate: import_joi26.default.boolean().optional().allow(null),
|
|
25844
26265
|
vehicleType: import_joi26.default.string().optional().allow("", null),
|
|
25845
|
-
vehicleColor: import_joi26.default.string().optional().allow("", null)
|
|
26266
|
+
vehicleColor: import_joi26.default.string().optional().allow("", null),
|
|
26267
|
+
timezone: import_joi26.default.string().optional().allow("", null)
|
|
25846
26268
|
});
|
|
25847
26269
|
const { error } = validation.validate(value);
|
|
25848
26270
|
if (error) {
|
|
@@ -25858,17 +26280,20 @@ function useDahuaService() {
|
|
|
25858
26280
|
plateNumber: value.plateNumber,
|
|
25859
26281
|
vehicleType: value.vehicleType,
|
|
25860
26282
|
vehicleColor: value.vehicleColor,
|
|
25861
|
-
|
|
25862
|
-
|
|
26283
|
+
// On the camera's clock (see `dahuaWallClock`).
|
|
26284
|
+
start: dahuaWallClock(value.start, value.timezone) ?? value.start,
|
|
26285
|
+
end: dahuaWallClock(value.end, value.timezone) ?? value.end,
|
|
25863
26286
|
owner: value.owner,
|
|
25864
26287
|
openGate: isOpenGateString
|
|
25865
26288
|
});
|
|
25866
26289
|
try {
|
|
25867
|
-
const response = await
|
|
26290
|
+
const response = await sendCameraCommand({
|
|
25868
26291
|
host: value.host,
|
|
25869
26292
|
username: value.username,
|
|
25870
26293
|
password: value.password,
|
|
25871
|
-
endpoint
|
|
26294
|
+
endpoint,
|
|
26295
|
+
repeatable: false,
|
|
26296
|
+
label: `bulk add plate to ${value.mode}`
|
|
25872
26297
|
});
|
|
25873
26298
|
return response;
|
|
25874
26299
|
} catch (error2) {
|
|
@@ -25922,11 +26347,91 @@ function useDahuaService() {
|
|
|
25922
26347
|
throw error2;
|
|
25923
26348
|
}
|
|
25924
26349
|
}
|
|
26350
|
+
async function setPlateWindow(value) {
|
|
26351
|
+
const validation = import_joi26.default.object({
|
|
26352
|
+
host: import_joi26.default.string().required(),
|
|
26353
|
+
username: import_joi26.default.string().required(),
|
|
26354
|
+
password: import_joi26.default.string().required(),
|
|
26355
|
+
mode: import_joi26.default.string().valid(...Object.values(ANPRMode)).required(),
|
|
26356
|
+
recno: import_joi26.default.string().required(),
|
|
26357
|
+
plateNumber: import_joi26.default.string().required(),
|
|
26358
|
+
owner: import_joi26.default.string().optional().allow("", null),
|
|
26359
|
+
start: import_joi26.default.string().isoDate().optional().allow("", null),
|
|
26360
|
+
end: import_joi26.default.string().isoDate().optional().allow("", null),
|
|
26361
|
+
timezone: import_joi26.default.string().optional().allow("", null)
|
|
26362
|
+
});
|
|
26363
|
+
const { error } = validation.validate(value);
|
|
26364
|
+
if (error) {
|
|
26365
|
+
throw new import_node_server_utils60.BadRequestError(`Validation error: ${error.message}`);
|
|
26366
|
+
}
|
|
26367
|
+
const window = plateWindowOnCamera(value);
|
|
26368
|
+
const endpoint = `/cgi-bin/recordUpdater.cgi?action=update&name=${encodeURIComponent(value.mode)}&recno=${encodeURIComponent(value.recno)}&PlateNumber=${encodeURIComponent(value.plateNumber)}&BeginTime=${encodeURIComponent(window.start)}&CancelTime=${encodeURIComponent(window.end)}&MasterOfCar=${encodeURIComponent(anprOwnerName(value.owner))}`;
|
|
26369
|
+
try {
|
|
26370
|
+
return await sendCameraCommandExpectingOk({
|
|
26371
|
+
host: value.host,
|
|
26372
|
+
username: value.username,
|
|
26373
|
+
password: value.password,
|
|
26374
|
+
endpoint,
|
|
26375
|
+
label: `set plate window on ${value.mode}`
|
|
26376
|
+
});
|
|
26377
|
+
} catch (error2) {
|
|
26378
|
+
loggerDahua.error(
|
|
26379
|
+
`[${value.host}] Error setting plate window: ${error2?.message || error2}`
|
|
26380
|
+
);
|
|
26381
|
+
throw new import_node_server_utils60.BadRequestError(anprWriteReason(error2));
|
|
26382
|
+
}
|
|
26383
|
+
}
|
|
26384
|
+
async function clearPlateList(value) {
|
|
26385
|
+
const validation = import_joi26.default.object({
|
|
26386
|
+
host: import_joi26.default.string().required(),
|
|
26387
|
+
username: import_joi26.default.string().required(),
|
|
26388
|
+
password: import_joi26.default.string().required(),
|
|
26389
|
+
mode: import_joi26.default.string().valid(...Object.values(ANPRMode)).required()
|
|
26390
|
+
});
|
|
26391
|
+
const { error } = validation.validate(value);
|
|
26392
|
+
if (error) {
|
|
26393
|
+
return anprRevokeThrew(`Validation error: ${error.message}`);
|
|
26394
|
+
}
|
|
26395
|
+
loggerDahua.info(`clearPlateList called for host ${value.host} mode ${value.mode}`);
|
|
26396
|
+
try {
|
|
26397
|
+
const response = await sendCameraCommand({
|
|
26398
|
+
host: value.host,
|
|
26399
|
+
username: value.username,
|
|
26400
|
+
password: value.password,
|
|
26401
|
+
endpoint: buildPlateClearEndpoint({ mode: value.mode }),
|
|
26402
|
+
// A long list takes the device a while to empty.
|
|
26403
|
+
timeout: 6e4,
|
|
26404
|
+
deadlineMs: 9e4,
|
|
26405
|
+
label: `clear ${value.mode}`
|
|
26406
|
+
});
|
|
26407
|
+
const outcome = anprRevokeOutcome(response);
|
|
26408
|
+
if (!outcome.ok)
|
|
26409
|
+
return outcome;
|
|
26410
|
+
const text2 = getDahuaResponseText(response);
|
|
26411
|
+
if (!/\bOK\b/i.test(text2)) {
|
|
26412
|
+
loggerDahua.error(
|
|
26413
|
+
`[${value.host}] clearPlateList ${value.mode} answered 200 without OK: ${text2.slice(0, 200)}`
|
|
26414
|
+
);
|
|
26415
|
+
return {
|
|
26416
|
+
ok: false,
|
|
26417
|
+
statusCode: 200,
|
|
26418
|
+
reason: "the camera did not accept the command to clear its list",
|
|
26419
|
+
detail: `the camera answered 200 without OK: ${text2.slice(0, 200)}`
|
|
26420
|
+
};
|
|
26421
|
+
}
|
|
26422
|
+
return outcome;
|
|
26423
|
+
} catch (error2) {
|
|
26424
|
+
loggerDahua.error(`[${value.host}] Error clearing ${value.mode}:`, error2);
|
|
26425
|
+
return anprRevokeThrew(error2);
|
|
26426
|
+
}
|
|
26427
|
+
}
|
|
25925
26428
|
return {
|
|
25926
26429
|
getSnapshot,
|
|
25927
26430
|
listenToCamera,
|
|
25928
26431
|
addPlateNumber,
|
|
25929
26432
|
removePlateNumber,
|
|
26433
|
+
clearPlateList,
|
|
26434
|
+
setPlateWindow,
|
|
25930
26435
|
updatePlateNumber,
|
|
25931
26436
|
getPlateNumber,
|
|
25932
26437
|
bulkInsertPlateNumber,
|
|
@@ -26368,6 +26873,11 @@ function useSiteCameraRepo() {
|
|
|
26368
26873
|
{
|
|
26369
26874
|
$match: query2
|
|
26370
26875
|
},
|
|
26876
|
+
// A fixed order, so paging through a site's cameras 20 at a time
|
|
26877
|
+
// cannot skip one camera and return another twice.
|
|
26878
|
+
{
|
|
26879
|
+
$sort: { _id: 1 }
|
|
26880
|
+
},
|
|
26371
26881
|
{
|
|
26372
26882
|
$skip: value.page * value.limit
|
|
26373
26883
|
},
|
|
@@ -38087,6 +38597,7 @@ function useVehicleService() {
|
|
|
38087
38597
|
getVehicleByPlateNumber: _getVehicleByPlateNumber,
|
|
38088
38598
|
getAllExpiredVehicles: _getAllExpiredVehicles,
|
|
38089
38599
|
bulkUpsertVehicles: _bulkUpsertVehicles,
|
|
38600
|
+
getLivePlatesBySite: _getLivePlatesBySite,
|
|
38090
38601
|
findOne
|
|
38091
38602
|
} = useVehicleRepo();
|
|
38092
38603
|
const {
|
|
@@ -38097,12 +38608,441 @@ function useVehicleService() {
|
|
|
38097
38608
|
bulkInsertPlateNumber: _bulkInsertPlateNumber
|
|
38098
38609
|
} = useDahuaService();
|
|
38099
38610
|
const { getAllCameraWithPassword: _getAllSiteCameras } = useSiteCameraRepo();
|
|
38611
|
+
const { setPlateWindow: _setPlateWindow } = useDahuaService();
|
|
38100
38612
|
const { getById: _getById } = useOrgRepo();
|
|
38101
38613
|
const {
|
|
38102
38614
|
pushVehicleById: _pushVehicleById,
|
|
38103
38615
|
pullVehicleByRecNo: _pullVehicleByRecNo
|
|
38104
38616
|
} = usePersonRepo();
|
|
38105
38617
|
const { getUnitByBlockLevelUnitNumber: _getUnitByBlockLevelUnitNumber } = useBuildingUnitRepo();
|
|
38618
|
+
async function camerasAt(site, options = {}) {
|
|
38619
|
+
const siteId = String(site ?? "");
|
|
38620
|
+
if (!siteId)
|
|
38621
|
+
return [];
|
|
38622
|
+
const cameras = [];
|
|
38623
|
+
let page = 1;
|
|
38624
|
+
let pages = 1;
|
|
38625
|
+
const limit = 20;
|
|
38626
|
+
do {
|
|
38627
|
+
const siteCameraReq = await _getAllSiteCameras({
|
|
38628
|
+
site: siteId,
|
|
38629
|
+
type: "anpr",
|
|
38630
|
+
...options.directions ? { direction: options.directions } : {},
|
|
38631
|
+
...options.activeOnly ? { status: "active" } : {},
|
|
38632
|
+
page,
|
|
38633
|
+
limit
|
|
38634
|
+
});
|
|
38635
|
+
pages = siteCameraReq.pages || 1;
|
|
38636
|
+
cameras.push(...siteCameraReq.items);
|
|
38637
|
+
page++;
|
|
38638
|
+
} while (page <= pages);
|
|
38639
|
+
return cameras;
|
|
38640
|
+
}
|
|
38641
|
+
function allowCamerasAt(site) {
|
|
38642
|
+
return camerasAt(site, {
|
|
38643
|
+
directions: ALLOW_CAMERA_DIRECTIONS,
|
|
38644
|
+
activeOnly: true
|
|
38645
|
+
});
|
|
38646
|
+
}
|
|
38647
|
+
function blockCamerasAt(site) {
|
|
38648
|
+
return camerasAt(site, {
|
|
38649
|
+
directions: BLOCK_CAMERA_DIRECTIONS,
|
|
38650
|
+
activeOnly: true
|
|
38651
|
+
});
|
|
38652
|
+
}
|
|
38653
|
+
function listOf(mode) {
|
|
38654
|
+
return mode === "TrafficBlackList" /* TRAFFIC_BLACKLIST */ ? "blocked" : "allowed";
|
|
38655
|
+
}
|
|
38656
|
+
function listMode(type) {
|
|
38657
|
+
return type === "blocklist" /* BLOCKLIST */ ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */;
|
|
38658
|
+
}
|
|
38659
|
+
function validDate(value) {
|
|
38660
|
+
if (value === null || value === void 0 || value === "")
|
|
38661
|
+
return null;
|
|
38662
|
+
const date = new Date(value);
|
|
38663
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
38664
|
+
}
|
|
38665
|
+
function cameraWindow(start, end) {
|
|
38666
|
+
const startDate = validDate(start);
|
|
38667
|
+
const endDate = validDate(end);
|
|
38668
|
+
return {
|
|
38669
|
+
...startDate ? { start: startDate.toISOString() } : {},
|
|
38670
|
+
...endDate ? { end: endDate.toISOString() } : {}
|
|
38671
|
+
};
|
|
38672
|
+
}
|
|
38673
|
+
const siteZones = /* @__PURE__ */ new Map();
|
|
38674
|
+
async function siteTimezone(site) {
|
|
38675
|
+
const key = String(site ?? "");
|
|
38676
|
+
const cached = siteZones.get(key);
|
|
38677
|
+
if (cached && Date.now() - cached.at < 5 * 6e4)
|
|
38678
|
+
return cached.zone;
|
|
38679
|
+
let zone = resolveSiteTimezone(null);
|
|
38680
|
+
if (key) {
|
|
38681
|
+
try {
|
|
38682
|
+
const found = await useSiteRepo().getSiteById(key);
|
|
38683
|
+
zone = resolveSiteTimezone(found);
|
|
38684
|
+
} catch {
|
|
38685
|
+
zone = resolveSiteTimezone(null);
|
|
38686
|
+
}
|
|
38687
|
+
}
|
|
38688
|
+
siteZones.set(key, { zone, at: Date.now() });
|
|
38689
|
+
return zone;
|
|
38690
|
+
}
|
|
38691
|
+
function blockWindow(vehicleEnd) {
|
|
38692
|
+
const now = /* @__PURE__ */ new Date();
|
|
38693
|
+
let end = validDate(vehicleEnd);
|
|
38694
|
+
if (!end || end.getTime() <= now.getTime()) {
|
|
38695
|
+
end = new Date(now);
|
|
38696
|
+
end.setFullYear(end.getFullYear() + 10);
|
|
38697
|
+
}
|
|
38698
|
+
return { start: now.toISOString(), end: end.toISOString() };
|
|
38699
|
+
}
|
|
38700
|
+
async function findPlateOnCamera(camera, mode, plateNumber) {
|
|
38701
|
+
const text2 = await _getPlateNumber({
|
|
38702
|
+
host: camera.host,
|
|
38703
|
+
username: camera.username,
|
|
38704
|
+
password: camera.password,
|
|
38705
|
+
mode,
|
|
38706
|
+
plateNumber,
|
|
38707
|
+
requireOk: true
|
|
38708
|
+
});
|
|
38709
|
+
return matchingPlateRecords(text2 ?? "", plateNumber);
|
|
38710
|
+
}
|
|
38711
|
+
async function removePlateFromCamera(camera, mode, plateNumber) {
|
|
38712
|
+
if (!normalisePlate(plateNumber)) {
|
|
38713
|
+
return { ...ANPR_NOTHING_TO_REVOKE, changed: false };
|
|
38714
|
+
}
|
|
38715
|
+
let changed = false;
|
|
38716
|
+
const MAX_PASSES = 3;
|
|
38717
|
+
for (let pass = 0; pass <= MAX_PASSES; pass++) {
|
|
38718
|
+
let records;
|
|
38719
|
+
try {
|
|
38720
|
+
records = await findPlateOnCamera(camera, mode, plateNumber);
|
|
38721
|
+
} catch (error) {
|
|
38722
|
+
const outcome = anprRevokeThrew(error);
|
|
38723
|
+
logCameraFailure("plate lookup", camera, outcome.detail);
|
|
38724
|
+
return { ...outcome, changed };
|
|
38725
|
+
}
|
|
38726
|
+
if (!records.length)
|
|
38727
|
+
return { ok: true, reason: "", changed };
|
|
38728
|
+
if (pass === MAX_PASSES)
|
|
38729
|
+
break;
|
|
38730
|
+
for (const record of records) {
|
|
38731
|
+
const outcome = await _removePlateNumber({
|
|
38732
|
+
host: camera.host,
|
|
38733
|
+
username: camera.username,
|
|
38734
|
+
password: camera.password,
|
|
38735
|
+
recno: record.recNo,
|
|
38736
|
+
mode
|
|
38737
|
+
}).catch((error) => anprRevokeThrew(error));
|
|
38738
|
+
if (!outcome?.ok) {
|
|
38739
|
+
logCameraFailure(
|
|
38740
|
+
"plate remove",
|
|
38741
|
+
camera,
|
|
38742
|
+
outcome?.detail ?? "no outcome returned"
|
|
38743
|
+
);
|
|
38744
|
+
return {
|
|
38745
|
+
...outcome ?? anprRevokeThrew("no outcome returned"),
|
|
38746
|
+
changed
|
|
38747
|
+
};
|
|
38748
|
+
}
|
|
38749
|
+
changed = true;
|
|
38750
|
+
}
|
|
38751
|
+
}
|
|
38752
|
+
logCameraFailure(
|
|
38753
|
+
"plate remove",
|
|
38754
|
+
camera,
|
|
38755
|
+
`plate still listed after ${MAX_PASSES} rounds of removal`
|
|
38756
|
+
);
|
|
38757
|
+
return {
|
|
38758
|
+
ok: false,
|
|
38759
|
+
reason: "the camera did not remove the plate number",
|
|
38760
|
+
detail: `plate still listed after ${MAX_PASSES} rounds of removal`,
|
|
38761
|
+
changed
|
|
38762
|
+
};
|
|
38763
|
+
}
|
|
38764
|
+
async function insertPlateOnCamera(camera, write) {
|
|
38765
|
+
const response = await _addPlateNumber({
|
|
38766
|
+
host: camera.host,
|
|
38767
|
+
username: camera.username,
|
|
38768
|
+
password: camera.password,
|
|
38769
|
+
plateNumber: write.plateNumber,
|
|
38770
|
+
mode: write.mode,
|
|
38771
|
+
owner: write.owner,
|
|
38772
|
+
...write.start ? { start: write.start } : {},
|
|
38773
|
+
...write.end ? { end: write.end } : {},
|
|
38774
|
+
timezone: await siteTimezone(camera.site)
|
|
38775
|
+
});
|
|
38776
|
+
const status = response?.statusCode ?? response?.status ?? response?.res?.status;
|
|
38777
|
+
if (status !== 200)
|
|
38778
|
+
throw new Error(`HTTP ${status ?? "none"}`);
|
|
38779
|
+
return String(
|
|
38780
|
+
response?.data?.toString("utf-8")?.split("=")[1]?.trim() ?? ""
|
|
38781
|
+
);
|
|
38782
|
+
}
|
|
38783
|
+
async function ensurePlateOnCamera(camera, write) {
|
|
38784
|
+
const existing = await findPlateOnCamera(
|
|
38785
|
+
camera,
|
|
38786
|
+
write.mode,
|
|
38787
|
+
write.plateNumber
|
|
38788
|
+
);
|
|
38789
|
+
if (!existing.length) {
|
|
38790
|
+
await insertPlateOnCamera(camera, write);
|
|
38791
|
+
return { inserted: true };
|
|
38792
|
+
}
|
|
38793
|
+
const timezone = await siteTimezone(camera.site);
|
|
38794
|
+
const [keep, ...extra] = existing;
|
|
38795
|
+
const now = dahuaWallClock(/* @__PURE__ */ new Date(), timezone);
|
|
38796
|
+
const wanted = {
|
|
38797
|
+
start: dahuaWallClock(write.start, timezone),
|
|
38798
|
+
end: dahuaWallClock(write.end, timezone)
|
|
38799
|
+
};
|
|
38800
|
+
const windowIsRight = wanted.start && wanted.end ? plateWindowMatches(keep, wanted) : (
|
|
38801
|
+
// No dates of its own: any copy that lets it in now will do.
|
|
38802
|
+
Boolean(
|
|
38803
|
+
keep.beginTime && keep.cancelTime && now && (dahuaWallClock(keep.beginTime) ?? "") <= now && (dahuaWallClock(keep.cancelTime) ?? "") > now
|
|
38804
|
+
)
|
|
38805
|
+
);
|
|
38806
|
+
if (!windowIsRight) {
|
|
38807
|
+
await _setPlateWindow({
|
|
38808
|
+
host: camera.host,
|
|
38809
|
+
username: camera.username,
|
|
38810
|
+
password: camera.password,
|
|
38811
|
+
mode: write.mode,
|
|
38812
|
+
recno: keep.recNo,
|
|
38813
|
+
plateNumber: write.plateNumber,
|
|
38814
|
+
owner: write.owner,
|
|
38815
|
+
...write.start ? { start: write.start } : {},
|
|
38816
|
+
...write.end ? { end: write.end } : {},
|
|
38817
|
+
timezone
|
|
38818
|
+
});
|
|
38819
|
+
}
|
|
38820
|
+
for (const copy of extra) {
|
|
38821
|
+
const outcome = await _removePlateNumber({
|
|
38822
|
+
host: camera.host,
|
|
38823
|
+
username: camera.username,
|
|
38824
|
+
password: camera.password,
|
|
38825
|
+
recno: copy.recNo,
|
|
38826
|
+
mode: write.mode
|
|
38827
|
+
}).catch((error) => anprRevokeThrew(error));
|
|
38828
|
+
if (!outcome?.ok) {
|
|
38829
|
+
throw new Error(
|
|
38830
|
+
outcome?.reason || "the camera did not remove an old copy of the plate"
|
|
38831
|
+
);
|
|
38832
|
+
}
|
|
38833
|
+
}
|
|
38834
|
+
return { inserted: false };
|
|
38835
|
+
}
|
|
38836
|
+
function cameraJournal() {
|
|
38837
|
+
const steps = [];
|
|
38838
|
+
return {
|
|
38839
|
+
/** A plate was inserted; undo = take it off again. */
|
|
38840
|
+
inserted(camera, mode, plateNumber) {
|
|
38841
|
+
steps.push({
|
|
38842
|
+
camera,
|
|
38843
|
+
undo: () => removePlateFromCamera(camera, mode, plateNumber).then(
|
|
38844
|
+
(outcome) => outcome.ok,
|
|
38845
|
+
() => false
|
|
38846
|
+
)
|
|
38847
|
+
});
|
|
38848
|
+
},
|
|
38849
|
+
/** A plate was removed; undo = put it back as it was. */
|
|
38850
|
+
removed(camera, previous) {
|
|
38851
|
+
steps.push({
|
|
38852
|
+
camera,
|
|
38853
|
+
undo: () => insertPlateOnCamera(camera, previous).then(
|
|
38854
|
+
() => true,
|
|
38855
|
+
() => false
|
|
38856
|
+
)
|
|
38857
|
+
});
|
|
38858
|
+
},
|
|
38859
|
+
/**
|
|
38860
|
+
* Undo everything, newest first. Never throws. Returns the cameras that
|
|
38861
|
+
* could not be put back, so the operator can be told which barrier is
|
|
38862
|
+
* now wrong.
|
|
38863
|
+
*/
|
|
38864
|
+
async rollback() {
|
|
38865
|
+
const stranded = [];
|
|
38866
|
+
for (const step of steps.reverse()) {
|
|
38867
|
+
if (!await step.undo())
|
|
38868
|
+
stranded.push(cameraLabel(step.camera));
|
|
38869
|
+
}
|
|
38870
|
+
steps.length = 0;
|
|
38871
|
+
return Array.from(new Set(stranded));
|
|
38872
|
+
}
|
|
38873
|
+
};
|
|
38874
|
+
}
|
|
38875
|
+
function strandedWarning(stranded) {
|
|
38876
|
+
if (!stranded.length)
|
|
38877
|
+
return "";
|
|
38878
|
+
return ` WARNING: ${stranded.join(", ")} could not be put back the way ${stranded.length === 1 ? "it was" : "they were"} - check this plate on ${stranded.length === 1 ? "that camera" : "those cameras"} by hand.`;
|
|
38879
|
+
}
|
|
38880
|
+
async function rollbackCameras(journal, error) {
|
|
38881
|
+
const stranded = await journal.rollback().catch(() => []);
|
|
38882
|
+
if (stranded.length) {
|
|
38883
|
+
import_node_server_utils106.logger.error(
|
|
38884
|
+
`vehicle camera rollback could not restore: ${stranded.join(", ")}`
|
|
38885
|
+
);
|
|
38886
|
+
if (error && typeof error === "object") {
|
|
38887
|
+
error.message = `${error.message ?? ""}${strandedWarning(stranded)}`;
|
|
38888
|
+
}
|
|
38889
|
+
}
|
|
38890
|
+
}
|
|
38891
|
+
async function revokableModes(site, plateNumber, exclude) {
|
|
38892
|
+
const others = [];
|
|
38893
|
+
let siteId = null;
|
|
38894
|
+
try {
|
|
38895
|
+
siteId = new import_mongodb72.ObjectId(String(site));
|
|
38896
|
+
} catch {
|
|
38897
|
+
siteId = null;
|
|
38898
|
+
}
|
|
38899
|
+
if (siteId && plateNumber) {
|
|
38900
|
+
const excludeIds = exclude.map((id) => {
|
|
38901
|
+
try {
|
|
38902
|
+
return new import_mongodb72.ObjectId(String(id));
|
|
38903
|
+
} catch {
|
|
38904
|
+
return null;
|
|
38905
|
+
}
|
|
38906
|
+
}).filter((id) => id !== null);
|
|
38907
|
+
const base = {
|
|
38908
|
+
site: siteId,
|
|
38909
|
+
plateNumber,
|
|
38910
|
+
status: "active" /* ACTIVE */,
|
|
38911
|
+
_id: { $nin: excludeIds }
|
|
38912
|
+
};
|
|
38913
|
+
const [allowHolder, blockHolder] = await Promise.all([
|
|
38914
|
+
findOne({
|
|
38915
|
+
...base,
|
|
38916
|
+
$or: [
|
|
38917
|
+
{ type: "whitelist" /* WHITELIST */ },
|
|
38918
|
+
{ blockedFrom: "whitelist" /* WHITELIST */ }
|
|
38919
|
+
]
|
|
38920
|
+
}),
|
|
38921
|
+
findOne({ ...base, type: "blocklist" /* BLOCKLIST */ })
|
|
38922
|
+
]);
|
|
38923
|
+
if (allowHolder)
|
|
38924
|
+
others.push(allowHolder);
|
|
38925
|
+
if (blockHolder)
|
|
38926
|
+
others.push(blockHolder);
|
|
38927
|
+
}
|
|
38928
|
+
const lists = listsToRevoke(others);
|
|
38929
|
+
return [
|
|
38930
|
+
...lists.allow ? ["TrafficRedList" /* TRAFFIC_REDLIST */] : [],
|
|
38931
|
+
...lists.block ? ["TrafficBlackList" /* TRAFFIC_BLACKLIST */] : []
|
|
38932
|
+
];
|
|
38933
|
+
}
|
|
38934
|
+
async function removeFromCameras(cameras, modes, plateNumber, previous, journal, vehicleIsBlocked = false) {
|
|
38935
|
+
const failures = [];
|
|
38936
|
+
for (const camera of cameras) {
|
|
38937
|
+
for (const mode of modes) {
|
|
38938
|
+
if (!mustRevokeFrom(camera.direction, listOf(mode), vehicleIsBlocked)) {
|
|
38939
|
+
continue;
|
|
38940
|
+
}
|
|
38941
|
+
const outcome = await removePlateFromCamera(camera, mode, plateNumber);
|
|
38942
|
+
if (outcome.changed) {
|
|
38943
|
+
journal.removed(camera, { ...previous, plateNumber, mode });
|
|
38944
|
+
}
|
|
38945
|
+
if (!outcome.ok) {
|
|
38946
|
+
failures.push({
|
|
38947
|
+
camera: cameraLabel(camera),
|
|
38948
|
+
reason: outcome.reason || "the camera could not be reached"
|
|
38949
|
+
});
|
|
38950
|
+
}
|
|
38951
|
+
}
|
|
38952
|
+
}
|
|
38953
|
+
return failures;
|
|
38954
|
+
}
|
|
38955
|
+
async function writeToCameras(cameras, write, journal, options = {}) {
|
|
38956
|
+
const failures = [];
|
|
38957
|
+
let recNo = "";
|
|
38958
|
+
for (const camera of cameras) {
|
|
38959
|
+
try {
|
|
38960
|
+
if (options.onlyIfMissing) {
|
|
38961
|
+
const { inserted } = await ensurePlateOnCamera(camera, write);
|
|
38962
|
+
if (inserted)
|
|
38963
|
+
journal.inserted(camera, write.mode, write.plateNumber);
|
|
38964
|
+
} else {
|
|
38965
|
+
recNo = await insertPlateOnCamera(camera, write) || recNo;
|
|
38966
|
+
journal.inserted(camera, write.mode, write.plateNumber);
|
|
38967
|
+
}
|
|
38968
|
+
} catch (error) {
|
|
38969
|
+
logCameraFailure(`write ${write.mode}`, camera, error);
|
|
38970
|
+
failures.push({
|
|
38971
|
+
camera: cameraLabel(camera),
|
|
38972
|
+
reason: anprWriteReason(error)
|
|
38973
|
+
});
|
|
38974
|
+
}
|
|
38975
|
+
}
|
|
38976
|
+
return { failures, recNo };
|
|
38977
|
+
}
|
|
38978
|
+
async function blockOnCameras(vehicle, site, owner, journal) {
|
|
38979
|
+
const plateNumber = String(vehicle.plateNumber ?? "");
|
|
38980
|
+
if (!plateNumber)
|
|
38981
|
+
return;
|
|
38982
|
+
const cameras = await blockCamerasAt(site);
|
|
38983
|
+
if (!cameras.length) {
|
|
38984
|
+
throw new import_node_server_utils106.BadRequestError(anprNoCameraRefusal("blocked"));
|
|
38985
|
+
}
|
|
38986
|
+
const { failures } = await writeToCameras(
|
|
38987
|
+
cameras,
|
|
38988
|
+
{
|
|
38989
|
+
plateNumber,
|
|
38990
|
+
mode: "TrafficBlackList" /* TRAFFIC_BLACKLIST */,
|
|
38991
|
+
owner,
|
|
38992
|
+
...blockWindow(vehicle.end)
|
|
38993
|
+
},
|
|
38994
|
+
journal,
|
|
38995
|
+
{ onlyIfMissing: true }
|
|
38996
|
+
);
|
|
38997
|
+
if (failures.length) {
|
|
38998
|
+
throw new import_node_server_utils106.BadRequestError(anprBlockFailureMessage(failures));
|
|
38999
|
+
}
|
|
39000
|
+
}
|
|
39001
|
+
async function unblockOnCameras(vehicle, site, options, journal) {
|
|
39002
|
+
const plateNumber = String(vehicle.plateNumber ?? "");
|
|
39003
|
+
if (!plateNumber)
|
|
39004
|
+
return;
|
|
39005
|
+
const window = cameraWindow(vehicle.start, vehicle.end);
|
|
39006
|
+
if (options.restore) {
|
|
39007
|
+
const allowCameras = await allowCamerasAt(site);
|
|
39008
|
+
const { failures: failures2 } = await writeToCameras(
|
|
39009
|
+
allowCameras,
|
|
39010
|
+
{
|
|
39011
|
+
plateNumber,
|
|
39012
|
+
mode: "TrafficRedList" /* TRAFFIC_REDLIST */,
|
|
39013
|
+
owner: options.owner,
|
|
39014
|
+
...window
|
|
39015
|
+
},
|
|
39016
|
+
journal,
|
|
39017
|
+
{ onlyIfMissing: true }
|
|
39018
|
+
);
|
|
39019
|
+
if (failures2.length) {
|
|
39020
|
+
throw new import_node_server_utils106.BadRequestError(
|
|
39021
|
+
cameraFailureSentence({
|
|
39022
|
+
failures: failures2,
|
|
39023
|
+
operation: "added to",
|
|
39024
|
+
outcome: "unblocked",
|
|
39025
|
+
consequence: "it is still blocked at the barrier"
|
|
39026
|
+
})
|
|
39027
|
+
);
|
|
39028
|
+
}
|
|
39029
|
+
}
|
|
39030
|
+
const revokable = await revokableModes(site, plateNumber, [vehicle._id]);
|
|
39031
|
+
const modes = options.restore ? revokable.filter((mode) => mode === "TrafficBlackList" /* TRAFFIC_BLACKLIST */) : revokable;
|
|
39032
|
+
if (!modes.length)
|
|
39033
|
+
return;
|
|
39034
|
+
const failures = await removeFromCameras(
|
|
39035
|
+
await camerasAt(site, { activeOnly: true }),
|
|
39036
|
+
modes,
|
|
39037
|
+
plateNumber,
|
|
39038
|
+
{ owner: options.owner, ...window },
|
|
39039
|
+
journal,
|
|
39040
|
+
true
|
|
39041
|
+
);
|
|
39042
|
+
if (failures.length) {
|
|
39043
|
+
throw new import_node_server_utils106.BadRequestError(anprUnblockFailureMessage(failures));
|
|
39044
|
+
}
|
|
39045
|
+
}
|
|
38106
39046
|
async function add(value, session, user) {
|
|
38107
39047
|
const isExternalSession = !!session;
|
|
38108
39048
|
if (!session) {
|
|
@@ -38157,18 +39097,18 @@ function useVehicleService() {
|
|
|
38157
39097
|
endDate.setFullYear(endDate.getFullYear() + 10);
|
|
38158
39098
|
start = startDate.toISOString();
|
|
38159
39099
|
end = endDate.toISOString();
|
|
38160
|
-
startDateDahua =
|
|
38161
|
-
endDateDahua =
|
|
39100
|
+
startDateDahua = start;
|
|
39101
|
+
endDateDahua = end;
|
|
38162
39102
|
} else {
|
|
38163
39103
|
if (hasStart) {
|
|
38164
39104
|
startDate = new Date(value.start);
|
|
38165
39105
|
start = startDate.toISOString();
|
|
38166
|
-
startDateDahua =
|
|
39106
|
+
startDateDahua = start;
|
|
38167
39107
|
}
|
|
38168
39108
|
if (hasEnd) {
|
|
38169
39109
|
endDate = new Date(value.end);
|
|
38170
39110
|
end = endDate.toISOString();
|
|
38171
|
-
endDateDahua =
|
|
39111
|
+
endDateDahua = end;
|
|
38172
39112
|
}
|
|
38173
39113
|
}
|
|
38174
39114
|
const owner = value.name;
|
|
@@ -38181,22 +39121,7 @@ function useVehicleService() {
|
|
|
38181
39121
|
let message = "Vehicle plate number needs approval from property management.";
|
|
38182
39122
|
let siteCameras = [];
|
|
38183
39123
|
if (value.status && value.status !== "pending" /* PENDING */) {
|
|
38184
|
-
|
|
38185
|
-
let pages = 1;
|
|
38186
|
-
const limit = 20;
|
|
38187
|
-
do {
|
|
38188
|
-
const siteCameraReq = await _getAllSiteCameras({
|
|
38189
|
-
site: value.site,
|
|
38190
|
-
type: "anpr",
|
|
38191
|
-
direction: ["both", "entry", "residents"],
|
|
38192
|
-
page,
|
|
38193
|
-
limit,
|
|
38194
|
-
status: "active"
|
|
38195
|
-
});
|
|
38196
|
-
pages = siteCameraReq.pages || 1;
|
|
38197
|
-
siteCameras.push(...siteCameraReq.items);
|
|
38198
|
-
page++;
|
|
38199
|
-
} while (page <= pages);
|
|
39124
|
+
siteCameras = isBlocklist ? await blockCamerasAt(value.site) : await allowCamerasAt(value.site);
|
|
38200
39125
|
import_node_server_utils106.logger.info(
|
|
38201
39126
|
`addVehicle resolved ${siteCameras.length} site camera(s) for site ${value?.site}`
|
|
38202
39127
|
);
|
|
@@ -38231,7 +39156,10 @@ function useVehicleService() {
|
|
|
38231
39156
|
mode: _mode,
|
|
38232
39157
|
owner,
|
|
38233
39158
|
...startDateDahua ? { start: startDateDahua } : {},
|
|
38234
|
-
...endDateDahua ? { end: endDateDahua } : {}
|
|
39159
|
+
...endDateDahua ? { end: endDateDahua } : {},
|
|
39160
|
+
// The camera enforces the season pass window on its own clock,
|
|
39161
|
+
// in the site's zone.
|
|
39162
|
+
timezone: await siteTimezone(camera.site ?? value.site)
|
|
38235
39163
|
};
|
|
38236
39164
|
try {
|
|
38237
39165
|
const dahuaResponse = await _addPlateNumber(dahuaPayload);
|
|
@@ -38251,8 +39179,7 @@ function useVehicleService() {
|
|
|
38251
39179
|
}
|
|
38252
39180
|
const recNo = String(responseData.split("=")[1]?.trim() ?? "");
|
|
38253
39181
|
vehicleValue.recNo = recNo;
|
|
38254
|
-
|
|
38255
|
-
written.push({ camera, recno: recNo, mode: _mode });
|
|
39182
|
+
written.push({ camera, recno: recNo, mode: _mode, plateNumber: plateNumber2 });
|
|
38256
39183
|
if (value.peopleId && recNo) {
|
|
38257
39184
|
await _pushVehicleById(
|
|
38258
39185
|
value.peopleId,
|
|
@@ -38282,13 +39209,18 @@ function useVehicleService() {
|
|
|
38282
39209
|
} catch (error) {
|
|
38283
39210
|
const stranded = [];
|
|
38284
39211
|
for (const entry of written) {
|
|
38285
|
-
const
|
|
39212
|
+
const usableId = entry.recno && /^\d+$/.test(entry.recno);
|
|
39213
|
+
const outcome = usableId ? await _removePlateNumber({
|
|
38286
39214
|
host: entry.camera.host,
|
|
38287
39215
|
username: entry.camera.username,
|
|
38288
39216
|
password: entry.camera.password,
|
|
38289
39217
|
recno: entry.recno,
|
|
38290
39218
|
mode: entry.mode
|
|
38291
|
-
}).catch((removeError) => anprRevokeThrew(removeError))
|
|
39219
|
+
}).catch((removeError) => anprRevokeThrew(removeError)) : await removePlateFromCamera(
|
|
39220
|
+
entry.camera,
|
|
39221
|
+
entry.mode,
|
|
39222
|
+
entry.plateNumber
|
|
39223
|
+
);
|
|
38292
39224
|
if (!outcome?.ok)
|
|
38293
39225
|
stranded.push(cameraLabel(entry.camera));
|
|
38294
39226
|
}
|
|
@@ -38314,80 +39246,28 @@ function useVehicleService() {
|
|
|
38314
39246
|
if (!session) {
|
|
38315
39247
|
throw new Error("Unable to start session for vehicle service.");
|
|
38316
39248
|
}
|
|
38317
|
-
const
|
|
39249
|
+
const journal = cameraJournal();
|
|
38318
39250
|
try {
|
|
38319
39251
|
session.startTransaction();
|
|
38320
|
-
const siteCameras = [];
|
|
38321
|
-
let page = 1;
|
|
38322
|
-
let pages = 1;
|
|
38323
|
-
const limit = 20;
|
|
38324
|
-
do {
|
|
38325
|
-
const siteCameraReq = await _getAllSiteCameras({
|
|
38326
|
-
site,
|
|
38327
|
-
type: "anpr",
|
|
38328
|
-
page,
|
|
38329
|
-
limit
|
|
38330
|
-
});
|
|
38331
|
-
pages = siteCameraReq.pages || 1;
|
|
38332
|
-
siteCameras.push(...siteCameraReq.items);
|
|
38333
|
-
page++;
|
|
38334
|
-
} while (page <= pages);
|
|
38335
|
-
import_node_server_utils106.logger.info(
|
|
38336
|
-
`deleteVehicle resolved ${siteCameras.length} ANPR camera(s) for site ${site}`
|
|
38337
|
-
);
|
|
38338
39252
|
const vehicle = await findOne({ _id: new import_mongodb72.ObjectId(_id) });
|
|
38339
39253
|
const plateNumber = String(vehicle?.plateNumber ?? "");
|
|
38340
|
-
const
|
|
38341
|
-
|
|
38342
|
-
|
|
38343
|
-
|
|
38344
|
-
|
|
38345
|
-
|
|
38346
|
-
|
|
38347
|
-
|
|
38348
|
-
|
|
38349
|
-
|
|
38350
|
-
|
|
38351
|
-
|
|
38352
|
-
|
|
38353
|
-
|
|
38354
|
-
|
|
38355
|
-
|
|
38356
|
-
|
|
38357
|
-
}) ?? ""
|
|
38358
|
-
);
|
|
38359
|
-
if (!found.exists)
|
|
38360
|
-
continue;
|
|
38361
|
-
if (found.recNo)
|
|
38362
|
-
recnoForCamera = found.recNo;
|
|
38363
|
-
} catch (error) {
|
|
38364
|
-
const outcome2 = anprRevokeThrew(error);
|
|
38365
|
-
logCameraFailure("deleteVehicle lookup", camera, outcome2.detail);
|
|
38366
|
-
failures.push({ camera: label, reason: outcome2.reason });
|
|
38367
|
-
continue;
|
|
38368
|
-
}
|
|
38369
|
-
}
|
|
38370
|
-
if (!recnoForCamera)
|
|
38371
|
-
continue;
|
|
38372
|
-
const outcome = await _removePlateNumber({
|
|
38373
|
-
host,
|
|
38374
|
-
username,
|
|
38375
|
-
password,
|
|
38376
|
-
recno: recnoForCamera,
|
|
38377
|
-
mode: _mode
|
|
38378
|
-
});
|
|
38379
|
-
if (!outcome?.ok) {
|
|
38380
|
-
logCameraFailure(
|
|
38381
|
-
"deleteVehicle revoke",
|
|
38382
|
-
camera,
|
|
38383
|
-
outcome?.detail ?? "no outcome returned"
|
|
38384
|
-
);
|
|
38385
|
-
failures.push({
|
|
38386
|
-
camera: label,
|
|
38387
|
-
reason: outcome?.reason || "the camera could not be reached"
|
|
38388
|
-
});
|
|
38389
|
-
}
|
|
38390
|
-
}
|
|
39254
|
+
const cameraSite = vehicle?.site ?? site;
|
|
39255
|
+
const siteCameras = await camerasAt(cameraSite, { activeOnly: true });
|
|
39256
|
+
import_node_server_utils106.logger.info(
|
|
39257
|
+
`deleteVehicle resolved ${siteCameras.length} ANPR camera(s) for site ${cameraSite}`
|
|
39258
|
+
);
|
|
39259
|
+
const modes = plateNumber ? await revokableModes(cameraSite, plateNumber, [_id]) : [];
|
|
39260
|
+
const failures = await removeFromCameras(
|
|
39261
|
+
siteCameras,
|
|
39262
|
+
modes,
|
|
39263
|
+
plateNumber,
|
|
39264
|
+
{
|
|
39265
|
+
owner: String(vehicle?.name ?? ""),
|
|
39266
|
+
...cameraWindow(vehicle?.start, vehicle?.end)
|
|
39267
|
+
},
|
|
39268
|
+
journal,
|
|
39269
|
+
vehicle?.type === "blocklist" /* BLOCKLIST */
|
|
39270
|
+
);
|
|
38391
39271
|
if (failures.length) {
|
|
38392
39272
|
const message = anprRevokeFailureMessage(failures);
|
|
38393
39273
|
if (!bypass)
|
|
@@ -38397,13 +39277,14 @@ function useVehicleService() {
|
|
|
38397
39277
|
await _deleteVehicle(_id, session);
|
|
38398
39278
|
await _pullVehicleByRecNo(
|
|
38399
39279
|
recno,
|
|
38400
|
-
{ peopleId: vehicle?.peopleId, site, plateNumber },
|
|
39280
|
+
{ peopleId: vehicle?.peopleId, site: cameraSite, plateNumber },
|
|
38401
39281
|
session
|
|
38402
39282
|
);
|
|
38403
39283
|
await session.commitTransaction();
|
|
38404
39284
|
return `Vehicle plate number deleted from ${type} record successfully.`;
|
|
38405
39285
|
} catch (error) {
|
|
38406
39286
|
import_node_server_utils106.logger.error("Error in vehicle service delete:", error);
|
|
39287
|
+
await rollbackCameras(journal, error);
|
|
38407
39288
|
await session.abortTransaction();
|
|
38408
39289
|
throw error;
|
|
38409
39290
|
} finally {
|
|
@@ -38418,13 +39299,14 @@ function useVehicleService() {
|
|
|
38418
39299
|
const org = await _getById(orgId);
|
|
38419
39300
|
if (!org)
|
|
38420
39301
|
throw new import_node_server_utils106.BadRequestError("Org not found");
|
|
38421
|
-
const vehicle = await
|
|
38422
|
-
const plate = vehicle.plates.find((p) => p._id.toString() === id);
|
|
38423
|
-
const _plateNumber = plate?.plateNumber;
|
|
39302
|
+
const vehicle = await findOne({ _id: new import_mongodb72.ObjectId(id) });
|
|
38424
39303
|
if (!vehicle) {
|
|
38425
|
-
throw new import_node_server_utils106.
|
|
39304
|
+
throw new import_node_server_utils106.NotFoundError("Vehicle not found.");
|
|
38426
39305
|
}
|
|
39306
|
+
const _plateNumber = String(vehicle.plateNumber ?? "");
|
|
38427
39307
|
const owner = vehicle.name;
|
|
39308
|
+
const isBlocklist = vehicle.type === "blocklist" /* BLOCKLIST */;
|
|
39309
|
+
const mode = listMode(vehicle.type);
|
|
38428
39310
|
const hasStart = typeof vehicle.start === "string" ? vehicle.start.trim() !== "" : !!vehicle.start;
|
|
38429
39311
|
const hasEnd = typeof vehicle.end === "string" ? vehicle.end.trim() !== "" : !!vehicle.end;
|
|
38430
39312
|
let startDate = null;
|
|
@@ -38442,70 +39324,48 @@ function useVehicleService() {
|
|
|
38442
39324
|
endDate.setFullYear(endDate.getFullYear() + 10);
|
|
38443
39325
|
}
|
|
38444
39326
|
}
|
|
38445
|
-
const startDahua = startDate ?
|
|
38446
|
-
const endDahua = endDate ?
|
|
39327
|
+
const startDahua = startDate ? startDate.toISOString() : "";
|
|
39328
|
+
const endDahua = endDate ? endDate.toISOString() : "";
|
|
39329
|
+
const journal = cameraJournal();
|
|
38447
39330
|
try {
|
|
38448
39331
|
session.startTransaction();
|
|
38449
|
-
const
|
|
38450
|
-
|
|
38451
|
-
let pages = 1;
|
|
38452
|
-
const limit = 20;
|
|
38453
|
-
let value = {};
|
|
38454
|
-
do {
|
|
38455
|
-
const siteCameraReq = await _getAllSiteCameras({
|
|
38456
|
-
site: siteId,
|
|
38457
|
-
type: "anpr",
|
|
38458
|
-
direction: ["both", "entry", "residents"],
|
|
38459
|
-
page,
|
|
38460
|
-
limit
|
|
38461
|
-
});
|
|
38462
|
-
pages = siteCameraReq.pages || 1;
|
|
38463
|
-
siteCameras.push(...siteCameraReq.items);
|
|
38464
|
-
page++;
|
|
38465
|
-
} while (page <= pages);
|
|
39332
|
+
const cameraSite = vehicle.site ?? siteId;
|
|
39333
|
+
const siteCameras = isBlocklist ? await blockCamerasAt(cameraSite) : await allowCamerasAt(cameraSite);
|
|
38466
39334
|
if (!siteCameras.length) {
|
|
38467
39335
|
throw new import_node_server_utils106.BadRequestError(anprNoCameraRefusal("approved"));
|
|
38468
39336
|
}
|
|
38469
|
-
|
|
38470
|
-
|
|
38471
|
-
|
|
38472
|
-
host,
|
|
38473
|
-
username,
|
|
38474
|
-
password,
|
|
39337
|
+
const { failures, recNo } = await writeToCameras(
|
|
39338
|
+
siteCameras,
|
|
39339
|
+
{
|
|
38475
39340
|
plateNumber: _plateNumber,
|
|
38476
|
-
mode
|
|
39341
|
+
mode,
|
|
39342
|
+
owner,
|
|
38477
39343
|
start: startDahua,
|
|
38478
|
-
end: endDahua
|
|
38479
|
-
|
|
38480
|
-
|
|
38481
|
-
|
|
38482
|
-
|
|
38483
|
-
|
|
38484
|
-
|
|
38485
|
-
|
|
38486
|
-
|
|
38487
|
-
|
|
38488
|
-
|
|
38489
|
-
|
|
38490
|
-
|
|
38491
|
-
camera: cameraLabel(camera),
|
|
38492
|
-
reason: anprWriteReason(
|
|
38493
|
-
`HTTP ${dahuaResponse?.statusCode ?? "none"}`
|
|
38494
|
-
)
|
|
38495
|
-
}
|
|
38496
|
-
])
|
|
38497
|
-
);
|
|
38498
|
-
}
|
|
38499
|
-
const responseData = dahuaResponse?.data.toString("utf-8");
|
|
38500
|
-
value.recNo = responseData.split("=")[1]?.trim();
|
|
39344
|
+
end: endDahua
|
|
39345
|
+
},
|
|
39346
|
+
journal
|
|
39347
|
+
);
|
|
39348
|
+
if (failures.length) {
|
|
39349
|
+
throw new import_node_server_utils106.BadRequestError(
|
|
39350
|
+
cameraFailureSentence({
|
|
39351
|
+
failures,
|
|
39352
|
+
operation: "added to",
|
|
39353
|
+
outcome: "approved",
|
|
39354
|
+
consequence: isBlocklist ? "it would still open the barrier" : "it would not open the barrier"
|
|
39355
|
+
})
|
|
39356
|
+
);
|
|
38501
39357
|
}
|
|
38502
|
-
value
|
|
38503
|
-
|
|
39358
|
+
const value = {
|
|
39359
|
+
status: "active" /* ACTIVE */,
|
|
39360
|
+
// Kept for older readers only - one camera's number, never replayed.
|
|
39361
|
+
...recNo ? { recNo } : {}
|
|
39362
|
+
};
|
|
39363
|
+
if (vehicle.peopleId && _plateNumber) {
|
|
38504
39364
|
await _pushVehicleById(
|
|
38505
39365
|
vehicle.peopleId,
|
|
38506
39366
|
{
|
|
38507
39367
|
plateNumber: _plateNumber,
|
|
38508
|
-
recNo
|
|
39368
|
+
recNo
|
|
38509
39369
|
},
|
|
38510
39370
|
session
|
|
38511
39371
|
);
|
|
@@ -38520,6 +39380,7 @@ function useVehicleService() {
|
|
|
38520
39380
|
return "Vehicle plate number approved and added successfully.";
|
|
38521
39381
|
} catch (error) {
|
|
38522
39382
|
import_node_server_utils106.logger.error("Error in vehicle service approving and adding:", error);
|
|
39383
|
+
await rollbackCameras(journal, error);
|
|
38523
39384
|
await session.abortTransaction();
|
|
38524
39385
|
throw error;
|
|
38525
39386
|
} finally {
|
|
@@ -38528,71 +39389,38 @@ function useVehicleService() {
|
|
|
38528
39389
|
}
|
|
38529
39390
|
async function processDeletingExpiredVehicles() {
|
|
38530
39391
|
const vehicles = await _getAllExpiredVehicles();
|
|
39392
|
+
const sweptIds = vehicles.map((vehicle) => vehicle?._id);
|
|
39393
|
+
const modesByVehicle = /* @__PURE__ */ new Map();
|
|
38531
39394
|
const { deletableIds, skipped } = await sweepExpiredVehicles(vehicles, {
|
|
38532
|
-
|
|
38533
|
-
|
|
38534
|
-
|
|
38535
|
-
|
|
38536
|
-
const limit = 20;
|
|
38537
|
-
do {
|
|
38538
|
-
const siteCameraReq = await _getAllSiteCameras({
|
|
38539
|
-
site,
|
|
38540
|
-
type: "anpr",
|
|
38541
|
-
page,
|
|
38542
|
-
limit
|
|
38543
|
-
});
|
|
38544
|
-
pages = siteCameraReq.pages || 1;
|
|
38545
|
-
cameras.push(...siteCameraReq.items);
|
|
38546
|
-
page++;
|
|
38547
|
-
} while (page <= pages);
|
|
38548
|
-
return cameras;
|
|
38549
|
-
},
|
|
39395
|
+
// Every ACTIVE ANPR camera at the site (an inactive one is skipped, as
|
|
39396
|
+
// by every add and update); which of them each list is taken off is
|
|
39397
|
+
// `mustRevokeFrom`.
|
|
39398
|
+
camerasForSite: (site) => camerasAt(site, { activeOnly: true }),
|
|
38550
39399
|
revoke: async (camera, vehicle) => {
|
|
38551
|
-
const host = camera.host;
|
|
38552
|
-
const username = camera.username;
|
|
38553
|
-
const password = camera.password;
|
|
38554
|
-
const mode = vehicle?.type !== "whitelist" /* WHITELIST */ ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */;
|
|
38555
39400
|
const plateNumber = String(vehicle?.plateNumber ?? "");
|
|
38556
|
-
|
|
38557
|
-
if (plateNumber) {
|
|
38558
|
-
try {
|
|
38559
|
-
const found = parseDahuaFind(
|
|
38560
|
-
await _getPlateNumber({
|
|
38561
|
-
host,
|
|
38562
|
-
username,
|
|
38563
|
-
password,
|
|
38564
|
-
mode,
|
|
38565
|
-
plateNumber,
|
|
38566
|
-
requireOk: true
|
|
38567
|
-
}) ?? ""
|
|
38568
|
-
);
|
|
38569
|
-
if (!found.exists)
|
|
38570
|
-
return ANPR_NOTHING_TO_REVOKE;
|
|
38571
|
-
if (found.recNo)
|
|
38572
|
-
recnoForCamera = found.recNo;
|
|
38573
|
-
} catch (error) {
|
|
38574
|
-
const outcome2 = anprRevokeThrew(error);
|
|
38575
|
-
logCameraFailure("expiredVehicle lookup", camera, outcome2.detail);
|
|
38576
|
-
return outcome2;
|
|
38577
|
-
}
|
|
38578
|
-
}
|
|
38579
|
-
if (!recnoForCamera)
|
|
39401
|
+
if (!plateNumber)
|
|
38580
39402
|
return ANPR_NOTHING_TO_REVOKE;
|
|
38581
|
-
const
|
|
38582
|
-
|
|
38583
|
-
|
|
38584
|
-
|
|
38585
|
-
|
|
38586
|
-
|
|
38587
|
-
|
|
38588
|
-
|
|
38589
|
-
|
|
38590
|
-
|
|
38591
|
-
|
|
38592
|
-
|
|
38593
|
-
|
|
39403
|
+
const key = String(vehicle?._id ?? plateNumber);
|
|
39404
|
+
let modes = modesByVehicle.get(key);
|
|
39405
|
+
if (!modes) {
|
|
39406
|
+
modes = revokableModes(vehicle?.site, plateNumber, sweptIds);
|
|
39407
|
+
modesByVehicle.set(key, modes);
|
|
39408
|
+
}
|
|
39409
|
+
let resolved;
|
|
39410
|
+
try {
|
|
39411
|
+
resolved = await modes;
|
|
39412
|
+
} catch (error) {
|
|
39413
|
+
return anprRevokeThrew(error);
|
|
39414
|
+
}
|
|
39415
|
+
for (const mode of resolved) {
|
|
39416
|
+
const blocked = vehicle?.type === "blocklist" /* BLOCKLIST */;
|
|
39417
|
+
if (!mustRevokeFrom(camera.direction, listOf(mode), blocked))
|
|
39418
|
+
continue;
|
|
39419
|
+
const outcome = await removePlateFromCamera(camera, mode, plateNumber);
|
|
39420
|
+
if (!outcome.ok)
|
|
39421
|
+
return outcome;
|
|
38594
39422
|
}
|
|
38595
|
-
return
|
|
39423
|
+
return ANPR_NOTHING_TO_REVOKE;
|
|
38596
39424
|
},
|
|
38597
39425
|
cameraLabel
|
|
38598
39426
|
});
|
|
@@ -38619,90 +39447,83 @@ function useVehicleService() {
|
|
|
38619
39447
|
"Only property management can reactivate vehicles."
|
|
38620
39448
|
);
|
|
38621
39449
|
}
|
|
38622
|
-
const vehicle = await
|
|
38623
|
-
const plate = vehicle.plates.find((p) => p._id.toString() === id);
|
|
38624
|
-
const _plateNumber = plate?.plateNumber;
|
|
39450
|
+
const vehicle = await findOne({ _id: new import_mongodb72.ObjectId(id) });
|
|
38625
39451
|
if (!vehicle) {
|
|
38626
|
-
throw new import_node_server_utils106.
|
|
39452
|
+
throw new import_node_server_utils106.NotFoundError("Vehicle not found.");
|
|
38627
39453
|
}
|
|
39454
|
+
const _plateNumber = String(vehicle.plateNumber ?? "");
|
|
38628
39455
|
const owner = vehicle.name;
|
|
38629
|
-
|
|
38630
|
-
|
|
38631
|
-
|
|
38632
|
-
const
|
|
38633
|
-
|
|
38634
|
-
|
|
38635
|
-
|
|
39456
|
+
const isBlocklist = vehicle.type === "blocklist" /* BLOCKLIST */;
|
|
39457
|
+
const mode = listMode(vehicle.type);
|
|
39458
|
+
const ownStart = validDate(vehicle.start);
|
|
39459
|
+
const ownEnd = validDate(vehicle.end);
|
|
39460
|
+
let startDate;
|
|
39461
|
+
let endDate;
|
|
39462
|
+
if (ownEnd) {
|
|
39463
|
+
if (ownEnd.getTime() <= Date.now()) {
|
|
39464
|
+
throw new import_node_server_utils106.BadRequestError(
|
|
39465
|
+
`This vehicle's expiry date (${dahuaWallClock(
|
|
39466
|
+
ownEnd,
|
|
39467
|
+
await siteTimezone(vehicle.site ?? siteId)
|
|
39468
|
+
)}) has already passed, so it cannot be restored as it is. Add it again with new dates.`
|
|
39469
|
+
);
|
|
39470
|
+
}
|
|
39471
|
+
startDate = ownStart ?? /* @__PURE__ */ new Date();
|
|
39472
|
+
endDate = ownEnd;
|
|
39473
|
+
} else {
|
|
39474
|
+
startDate = ownStart ?? /* @__PURE__ */ new Date();
|
|
39475
|
+
endDate = new Date(startDate);
|
|
39476
|
+
endDate.setFullYear(endDate.getFullYear() + 10);
|
|
39477
|
+
}
|
|
39478
|
+
const startDahua = startDate.toISOString();
|
|
39479
|
+
const endDahua = endDate.toISOString();
|
|
39480
|
+
const journal = cameraJournal();
|
|
38636
39481
|
try {
|
|
38637
39482
|
session.startTransaction();
|
|
38638
|
-
|
|
38639
|
-
const siteCameras =
|
|
38640
|
-
let page = 1;
|
|
38641
|
-
let pages = 1;
|
|
38642
|
-
const limit = 20;
|
|
38643
|
-
do {
|
|
38644
|
-
const siteCameraReq = await _getAllSiteCameras({
|
|
38645
|
-
site: siteId,
|
|
38646
|
-
type: "anpr",
|
|
38647
|
-
direction: ["both", "entry"],
|
|
38648
|
-
page,
|
|
38649
|
-
limit
|
|
38650
|
-
});
|
|
38651
|
-
pages = siteCameraReq.pages || 1;
|
|
38652
|
-
siteCameras.push(...siteCameraReq.items);
|
|
38653
|
-
page++;
|
|
38654
|
-
} while (page <= pages);
|
|
39483
|
+
const cameraSite = vehicle.site ?? siteId;
|
|
39484
|
+
const siteCameras = isBlocklist ? await blockCamerasAt(cameraSite) : await allowCamerasAt(cameraSite);
|
|
38655
39485
|
if (!siteCameras.length) {
|
|
38656
39486
|
throw new import_node_server_utils106.BadRequestError(anprNoCameraRefusal("restored"));
|
|
38657
39487
|
}
|
|
38658
|
-
|
|
38659
|
-
|
|
38660
|
-
|
|
38661
|
-
host,
|
|
38662
|
-
username,
|
|
38663
|
-
password,
|
|
39488
|
+
const { failures, recNo } = await writeToCameras(
|
|
39489
|
+
siteCameras,
|
|
39490
|
+
{
|
|
38664
39491
|
plateNumber: _plateNumber,
|
|
38665
|
-
mode
|
|
39492
|
+
mode,
|
|
39493
|
+
owner,
|
|
38666
39494
|
start: startDahua,
|
|
38667
|
-
end: endDahua
|
|
38668
|
-
|
|
38669
|
-
|
|
38670
|
-
|
|
38671
|
-
|
|
38672
|
-
|
|
38673
|
-
|
|
38674
|
-
|
|
38675
|
-
|
|
38676
|
-
|
|
38677
|
-
|
|
38678
|
-
|
|
38679
|
-
|
|
38680
|
-
camera: cameraLabel(camera),
|
|
38681
|
-
reason: anprWriteReason(
|
|
38682
|
-
`HTTP ${dahuaResponse?.statusCode ?? "none"}`
|
|
38683
|
-
)
|
|
38684
|
-
}
|
|
38685
|
-
])
|
|
38686
|
-
);
|
|
38687
|
-
}
|
|
38688
|
-
const responseData = dahuaResponse?.data.toString("utf-8");
|
|
38689
|
-
value.recNo = responseData.split("=")[1]?.trim();
|
|
39495
|
+
end: endDahua
|
|
39496
|
+
},
|
|
39497
|
+
journal
|
|
39498
|
+
);
|
|
39499
|
+
if (failures.length) {
|
|
39500
|
+
throw new import_node_server_utils106.BadRequestError(
|
|
39501
|
+
isBlocklist ? cameraFailureSentence({
|
|
39502
|
+
failures,
|
|
39503
|
+
operation: "added to",
|
|
39504
|
+
outcome: "restored",
|
|
39505
|
+
consequence: "it would still open the barrier"
|
|
39506
|
+
}) : anprRestoreFailureMessage(failures)
|
|
39507
|
+
);
|
|
38690
39508
|
}
|
|
38691
|
-
|
|
38692
|
-
if (vehicle.peopleId && value.recNo) {
|
|
39509
|
+
if (vehicle.peopleId && _plateNumber) {
|
|
38693
39510
|
await _pushVehicleById(
|
|
38694
39511
|
vehicle.peopleId,
|
|
38695
39512
|
{
|
|
38696
39513
|
plateNumber: _plateNumber,
|
|
38697
|
-
recNo
|
|
39514
|
+
recNo
|
|
38698
39515
|
},
|
|
38699
39516
|
session
|
|
38700
39517
|
);
|
|
38701
39518
|
}
|
|
38702
39519
|
const formattedValue = {
|
|
38703
|
-
|
|
38704
|
-
|
|
38705
|
-
|
|
39520
|
+
status: "active" /* ACTIVE */,
|
|
39521
|
+
// Kept for older readers only - one camera's number, never replayed.
|
|
39522
|
+
...recNo ? { recNo } : {},
|
|
39523
|
+
// Dates, not ISO strings: the expiry sweep only matches a BSON Date
|
|
39524
|
+
// (see `expiredVehicleSweepFilter`), so a string here never expired.
|
|
39525
|
+
start: startDate,
|
|
39526
|
+
end: endDate,
|
|
38706
39527
|
deletedAt: ""
|
|
38707
39528
|
};
|
|
38708
39529
|
await _updateVehicleById(id, formattedValue, session);
|
|
@@ -38710,21 +39531,62 @@ function useVehicleService() {
|
|
|
38710
39531
|
return "Vehicle reactivated successfully.";
|
|
38711
39532
|
} catch (error) {
|
|
38712
39533
|
import_node_server_utils106.logger.error("Error in vehicle service reactivation:", error);
|
|
39534
|
+
await rollbackCameras(journal, error);
|
|
38713
39535
|
await session.abortTransaction();
|
|
38714
39536
|
throw error;
|
|
38715
39537
|
} finally {
|
|
38716
39538
|
session.endSession();
|
|
38717
39539
|
}
|
|
38718
39540
|
}
|
|
38719
|
-
async function
|
|
38720
|
-
|
|
38721
|
-
|
|
38722
|
-
|
|
38723
|
-
|
|
38724
|
-
|
|
38725
|
-
|
|
39541
|
+
async function rewriteOnCameras(vehicle, site, next, journal) {
|
|
39542
|
+
const oldPlate = String(vehicle.plateNumber ?? "");
|
|
39543
|
+
const held = listsHeldBy(vehicle);
|
|
39544
|
+
const targets = [];
|
|
39545
|
+
if (held.allow) {
|
|
39546
|
+
targets.push({
|
|
39547
|
+
mode: "TrafficRedList" /* TRAFFIC_REDLIST */,
|
|
39548
|
+
cameras: await allowCamerasAt(site)
|
|
39549
|
+
});
|
|
39550
|
+
}
|
|
39551
|
+
if (held.block) {
|
|
39552
|
+
targets.push({
|
|
39553
|
+
mode: "TrafficBlackList" /* TRAFFIC_BLACKLIST */,
|
|
39554
|
+
cameras: await blockCamerasAt(site)
|
|
39555
|
+
});
|
|
39556
|
+
}
|
|
39557
|
+
if (!targets.some((target) => target.cameras.length)) {
|
|
39558
|
+
throw new import_node_server_utils106.BadRequestError(anprNoCameraRefusal("updated"));
|
|
39559
|
+
}
|
|
39560
|
+
const removeModes = next.plateNumber !== oldPlate ? await revokableModes(site, oldPlate, [vehicle._id]) : targets.map((target) => target.mode);
|
|
39561
|
+
const removeFailures = await removeFromCameras(
|
|
39562
|
+
await camerasAt(site, { activeOnly: true }),
|
|
39563
|
+
removeModes,
|
|
39564
|
+
oldPlate,
|
|
39565
|
+
{
|
|
39566
|
+
owner: String(vehicle.name ?? ""),
|
|
39567
|
+
...cameraWindow(vehicle.start, vehicle.end)
|
|
39568
|
+
},
|
|
39569
|
+
journal,
|
|
39570
|
+
held.block
|
|
39571
|
+
);
|
|
39572
|
+
if (removeFailures.length) {
|
|
39573
|
+
throw new import_node_server_utils106.BadRequestError(
|
|
39574
|
+
anprRevokeFailureMessage(removeFailures, "updated")
|
|
39575
|
+
);
|
|
39576
|
+
}
|
|
39577
|
+
const writeFailures = [];
|
|
39578
|
+
for (const target of targets) {
|
|
39579
|
+
const { failures } = await writeToCameras(
|
|
39580
|
+
target.cameras,
|
|
39581
|
+
{ ...next, mode: target.mode },
|
|
39582
|
+
journal
|
|
39583
|
+
);
|
|
39584
|
+
writeFailures.push(...failures);
|
|
39585
|
+
}
|
|
39586
|
+
if (writeFailures.length) {
|
|
39587
|
+
throw new import_node_server_utils106.BadRequestError(
|
|
39588
|
+
anprWriteFailureMessage(writeFailures, "updated")
|
|
38726
39589
|
);
|
|
38727
|
-
return false;
|
|
38728
39590
|
}
|
|
38729
39591
|
}
|
|
38730
39592
|
async function updateVehicleById(_id, value) {
|
|
@@ -38732,241 +39594,75 @@ function useVehicleService() {
|
|
|
38732
39594
|
if (!session) {
|
|
38733
39595
|
throw new Error("Unable to start session for vehicle service.");
|
|
38734
39596
|
}
|
|
39597
|
+
const journal = cameraJournal();
|
|
38735
39598
|
try {
|
|
38736
39599
|
session.startTransaction();
|
|
38737
|
-
const vehicle = await
|
|
38738
|
-
|
|
38739
|
-
|
|
38740
|
-
|
|
38741
|
-
const _start = vehicle.start;
|
|
38742
|
-
const _end = vehicle.end;
|
|
38743
|
-
const _recNo = plate.recNo;
|
|
38744
|
-
const _type = value.type ? value.type : plate.type;
|
|
39600
|
+
const vehicle = await findOne({ _id: new import_mongodb72.ObjectId(_id) });
|
|
39601
|
+
if (!vehicle) {
|
|
39602
|
+
throw new import_node_server_utils106.NotFoundError("Vehicle not found.");
|
|
39603
|
+
}
|
|
38745
39604
|
if (value.peopleId) {
|
|
38746
39605
|
value.peopleId = new import_mongodb72.ObjectId(value.peopleId);
|
|
38747
39606
|
}
|
|
38748
39607
|
const { name, plateNumber, start, end, recNo, type, unit, site, block, level, ...rest } = value;
|
|
38749
|
-
|
|
38750
|
-
|
|
38751
|
-
|
|
38752
|
-
|
|
38753
|
-
|
|
38754
|
-
|
|
38755
|
-
|
|
38756
|
-
|
|
38757
|
-
|
|
38758
|
-
|
|
38759
|
-
|
|
38760
|
-
|
|
38761
|
-
|
|
38762
|
-
|
|
38763
|
-
|
|
38764
|
-
|
|
38765
|
-
|
|
38766
|
-
|
|
38767
|
-
|
|
38768
|
-
|
|
38769
|
-
|
|
38770
|
-
throw new import_node_server_utils106.BadRequestError(anprNoCameraRefusal("updated"));
|
|
39608
|
+
const oldPlate = String(vehicle.plateNumber ?? "");
|
|
39609
|
+
const oldName = String(vehicle.name ?? "");
|
|
39610
|
+
const oldType = String(vehicle.type || "whitelist" /* WHITELIST */);
|
|
39611
|
+
const newPlate = plateNumber ? String(plateNumber).trim() : oldPlate;
|
|
39612
|
+
const newName = name ? String(name) : oldName;
|
|
39613
|
+
const newType = type ? String(type) : oldType;
|
|
39614
|
+
const cameraSite = vehicle.site ?? site;
|
|
39615
|
+
const startDate = validDate(start);
|
|
39616
|
+
const endDate = validDate(end);
|
|
39617
|
+
const datesChanged = !!startDate && startDate.getTime() !== validDate(vehicle.start)?.getTime() || !!endDate && endDate.getTime() !== validDate(vehicle.end)?.getTime();
|
|
39618
|
+
const isLive = vehicle.status === "active" /* ACTIVE */;
|
|
39619
|
+
let typeFields = {};
|
|
39620
|
+
if (newType !== oldType) {
|
|
39621
|
+
if (newPlate !== oldPlate) {
|
|
39622
|
+
throw new import_node_server_utils106.BadRequestError(
|
|
39623
|
+
"Change the plate number and the vehicle type in two separate steps."
|
|
39624
|
+
);
|
|
39625
|
+
}
|
|
39626
|
+
if (newType === "blocklist" /* BLOCKLIST */) {
|
|
39627
|
+
if (isLive) {
|
|
39628
|
+
await blockOnCameras(vehicle, cameraSite, newName, journal);
|
|
38771
39629
|
}
|
|
38772
|
-
|
|
38773
|
-
|
|
38774
|
-
|
|
38775
|
-
|
|
38776
|
-
|
|
38777
|
-
|
|
38778
|
-
|
|
38779
|
-
|
|
38780
|
-
|
|
38781
|
-
|
|
38782
|
-
|
|
38783
|
-
|
|
38784
|
-
removePlateNumber
|
|
38785
|
-
);
|
|
38786
|
-
if (!responseForDeletion?.ok) {
|
|
38787
|
-
logCameraFailure(
|
|
38788
|
-
"updateVehicleById revoke",
|
|
38789
|
-
camera,
|
|
38790
|
-
responseForDeletion?.detail ?? "no outcome returned"
|
|
38791
|
-
);
|
|
38792
|
-
throw new import_node_server_utils106.BadRequestError(
|
|
38793
|
-
anprRevokeFailureMessage(
|
|
38794
|
-
[
|
|
38795
|
-
{
|
|
38796
|
-
camera: cameraLabel(camera),
|
|
38797
|
-
reason: responseForDeletion?.reason || "the camera could not be reached"
|
|
38798
|
-
}
|
|
38799
|
-
],
|
|
38800
|
-
"updated"
|
|
38801
|
-
)
|
|
38802
|
-
);
|
|
38803
|
-
}
|
|
38804
|
-
const dahuaPayload = {
|
|
38805
|
-
host,
|
|
38806
|
-
username,
|
|
38807
|
-
password,
|
|
38808
|
-
plateNumber,
|
|
38809
|
-
mode: value.type === "blocklist" /* BLOCKLIST */ ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */,
|
|
38810
|
-
//whitelist or blocklist
|
|
38811
|
-
owner: name,
|
|
38812
|
-
...startDahua ? { start: startDahua } : {},
|
|
38813
|
-
...endDahua ? { end: endDahua } : {}
|
|
38814
|
-
};
|
|
38815
|
-
let dahuaResponse;
|
|
38816
|
-
try {
|
|
38817
|
-
dahuaResponse = await _addPlateNumber(dahuaPayload);
|
|
38818
|
-
} catch (error) {
|
|
38819
|
-
logCameraFailure("updateVehicleById write", camera, error);
|
|
38820
|
-
const restored = await restorePlateOnCamera({
|
|
38821
|
-
...removePlateNumber,
|
|
38822
|
-
plateNumber: _plateNumber,
|
|
38823
|
-
owner: _name,
|
|
38824
|
-
...startDahua ? { start: startDahua } : {},
|
|
38825
|
-
...endDahua ? { end: endDahua } : {}
|
|
38826
|
-
});
|
|
38827
|
-
throw new import_node_server_utils106.BadRequestError(
|
|
38828
|
-
anprWriteFailureMessage(
|
|
38829
|
-
[{ camera: cameraLabel(camera), reason: anprWriteReason(error) }],
|
|
38830
|
-
"updated"
|
|
38831
|
-
) + (restored ? ` The previous plate number ${_plateNumber} is still registered, so nothing has changed.` : ` WARNING: ${_plateNumber} was taken off this camera and could not be put back - it will NOT open the barrier until someone restores it.`)
|
|
38832
|
-
);
|
|
38833
|
-
}
|
|
38834
|
-
if (dahuaResponse?.statusCode !== 200) {
|
|
38835
|
-
logCameraFailure(
|
|
38836
|
-
"updateVehicleById write",
|
|
38837
|
-
camera,
|
|
38838
|
-
`HTTP ${dahuaResponse?.statusCode ?? "none"}`
|
|
38839
|
-
);
|
|
38840
|
-
throw new import_node_server_utils106.BadRequestError(
|
|
38841
|
-
anprWriteFailureMessage(
|
|
38842
|
-
[
|
|
38843
|
-
{
|
|
38844
|
-
camera: cameraLabel(camera),
|
|
38845
|
-
reason: anprWriteReason(
|
|
38846
|
-
`HTTP ${dahuaResponse?.statusCode ?? "none"}`
|
|
38847
|
-
)
|
|
38848
|
-
}
|
|
38849
|
-
],
|
|
38850
|
-
"updated"
|
|
38851
|
-
)
|
|
38852
|
-
);
|
|
38853
|
-
}
|
|
38854
|
-
const responseData = dahuaResponse?.data?.toString("utf-8") ?? "";
|
|
38855
|
-
value.recNo = responseData.split("=")[1]?.trim();
|
|
38856
|
-
if (value.peopleId && value.recNo) {
|
|
38857
|
-
await _pushVehicleById(
|
|
38858
|
-
value.peopleId,
|
|
38859
|
-
{
|
|
38860
|
-
plateNumber,
|
|
38861
|
-
recNo: value.recNo
|
|
38862
|
-
},
|
|
38863
|
-
session
|
|
38864
|
-
);
|
|
38865
|
-
}
|
|
38866
|
-
} else {
|
|
38867
|
-
const existing = parseDahuaFind(
|
|
38868
|
-
await _getPlateNumber({
|
|
38869
|
-
host,
|
|
38870
|
-
username,
|
|
38871
|
-
password,
|
|
38872
|
-
mode: _type === "blocklist" /* BLOCKLIST */ ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */,
|
|
38873
|
-
plateNumber,
|
|
38874
|
-
requireOk: true
|
|
38875
|
-
}) ?? ""
|
|
38876
|
-
);
|
|
38877
|
-
if (existing.exists) {
|
|
38878
|
-
const outcome = await _removePlateNumber({
|
|
38879
|
-
host,
|
|
38880
|
-
username,
|
|
38881
|
-
password,
|
|
38882
|
-
mode: _type === "blocklist" /* BLOCKLIST */ ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */,
|
|
38883
|
-
recno: existing.recNo || _recNo
|
|
38884
|
-
});
|
|
38885
|
-
if (!outcome?.ok) {
|
|
38886
|
-
logCameraFailure(
|
|
38887
|
-
"updateVehicleById revoke",
|
|
38888
|
-
camera,
|
|
38889
|
-
outcome?.detail ?? "no outcome returned"
|
|
38890
|
-
);
|
|
38891
|
-
throw new import_node_server_utils106.BadRequestError(
|
|
38892
|
-
anprRevokeFailureMessage([
|
|
38893
|
-
{
|
|
38894
|
-
camera: cameraLabel(camera),
|
|
38895
|
-
reason: outcome?.reason || "the camera could not be reached"
|
|
38896
|
-
}
|
|
38897
|
-
], "updated")
|
|
38898
|
-
);
|
|
38899
|
-
}
|
|
38900
|
-
}
|
|
38901
|
-
const dahuaPayload = {
|
|
38902
|
-
host,
|
|
38903
|
-
username,
|
|
38904
|
-
password,
|
|
38905
|
-
plateNumber,
|
|
38906
|
-
// recno: _recNo,
|
|
38907
|
-
mode: _type === "blocklist" /* BLOCKLIST */ ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */,
|
|
38908
|
-
start: startDahua,
|
|
38909
|
-
end: endDahua,
|
|
38910
|
-
owner: name ? name : _name
|
|
38911
|
-
};
|
|
38912
|
-
let dahuaResponse;
|
|
38913
|
-
try {
|
|
38914
|
-
dahuaResponse = await _addPlateNumber(dahuaPayload);
|
|
38915
|
-
} catch (error) {
|
|
38916
|
-
logCameraFailure("updateVehicleById write", camera, error);
|
|
38917
|
-
const restored = existing.exists ? await restorePlateOnCamera({
|
|
38918
|
-
...dahuaPayload,
|
|
38919
|
-
owner: _name
|
|
38920
|
-
}) : true;
|
|
38921
|
-
throw new import_node_server_utils106.BadRequestError(
|
|
38922
|
-
anprWriteFailureMessage(
|
|
38923
|
-
[{ camera: cameraLabel(camera), reason: anprWriteReason(error) }],
|
|
38924
|
-
"updated"
|
|
38925
|
-
) + (restored ? " The vehicle is unchanged and still registered." : ` WARNING: ${plateNumber} was taken off this camera and could not be put back - it will NOT open the barrier until someone restores it.`)
|
|
38926
|
-
);
|
|
38927
|
-
}
|
|
38928
|
-
if (dahuaResponse?.statusCode !== 200) {
|
|
38929
|
-
logCameraFailure(
|
|
38930
|
-
"updateVehicleById write",
|
|
38931
|
-
camera,
|
|
38932
|
-
`HTTP ${dahuaResponse?.statusCode ?? "none"}`
|
|
38933
|
-
);
|
|
38934
|
-
throw new import_node_server_utils106.BadRequestError(
|
|
38935
|
-
anprWriteFailureMessage(
|
|
38936
|
-
[
|
|
38937
|
-
{
|
|
38938
|
-
camera: cameraLabel(camera),
|
|
38939
|
-
reason: anprWriteReason(
|
|
38940
|
-
`HTTP ${dahuaResponse?.statusCode ?? "none"}`
|
|
38941
|
-
)
|
|
38942
|
-
}
|
|
38943
|
-
],
|
|
38944
|
-
"updated"
|
|
38945
|
-
)
|
|
38946
|
-
);
|
|
38947
|
-
}
|
|
38948
|
-
const responseData = dahuaResponse?.data?.toString("utf-8") ?? "";
|
|
38949
|
-
value.recNo = responseData.split("=")[1]?.trim();
|
|
38950
|
-
if (value.peopleId && value.recNo) {
|
|
38951
|
-
await _pushVehicleById(
|
|
38952
|
-
value.peopleId,
|
|
38953
|
-
{
|
|
38954
|
-
plateNumber,
|
|
38955
|
-
recNo: value.recNo
|
|
38956
|
-
},
|
|
38957
|
-
session
|
|
38958
|
-
);
|
|
38959
|
-
}
|
|
38960
|
-
}
|
|
39630
|
+
typeFields = {
|
|
39631
|
+
type: "blocklist" /* BLOCKLIST */,
|
|
39632
|
+
blockedFrom: oldType === "whitelist" /* WHITELIST */ ? "whitelist" /* WHITELIST */ : String(vehicle.blockedFrom ?? "")
|
|
39633
|
+
};
|
|
39634
|
+
} else {
|
|
39635
|
+
if (isLive) {
|
|
39636
|
+
await unblockOnCameras(
|
|
39637
|
+
vehicle,
|
|
39638
|
+
cameraSite,
|
|
39639
|
+
{ restore: true, owner: newName },
|
|
39640
|
+
journal
|
|
39641
|
+
);
|
|
38961
39642
|
}
|
|
39643
|
+
typeFields = { type: "whitelist" /* WHITELIST */, blockedFrom: "" };
|
|
38962
39644
|
}
|
|
39645
|
+
} else if (isLive && (newPlate !== oldPlate || newName !== oldName || datesChanged)) {
|
|
39646
|
+
await rewriteOnCameras(
|
|
39647
|
+
vehicle,
|
|
39648
|
+
cameraSite,
|
|
39649
|
+
{
|
|
39650
|
+
plateNumber: newPlate,
|
|
39651
|
+
owner: newName,
|
|
39652
|
+
...cameraWindow(startDate ?? vehicle.start, endDate ?? vehicle.end)
|
|
39653
|
+
},
|
|
39654
|
+
journal
|
|
39655
|
+
);
|
|
38963
39656
|
}
|
|
38964
39657
|
const formattedValue = {
|
|
38965
39658
|
...rest,
|
|
38966
39659
|
...name && { name },
|
|
38967
|
-
...plateNumber && { plateNumber },
|
|
38968
|
-
|
|
38969
|
-
|
|
39660
|
+
...plateNumber && { plateNumber: newPlate },
|
|
39661
|
+
// Dates, not ISO strings: the expiry sweep only matches a BSON Date
|
|
39662
|
+
// (see `expiredVehicleSweepFilter`), so an end date edited to a string
|
|
39663
|
+
// never expired.
|
|
39664
|
+
...startDate && { start: startDate },
|
|
39665
|
+
...endDate && { end: endDate },
|
|
38970
39666
|
...block && {
|
|
38971
39667
|
block: typeof block === "string" ? new import_mongodb72.ObjectId(block) : block
|
|
38972
39668
|
},
|
|
@@ -38976,13 +39672,27 @@ function useVehicleService() {
|
|
|
38976
39672
|
...unit && {
|
|
38977
39673
|
unit: typeof unit === "string" ? new import_mongodb72.ObjectId(unit) : unit
|
|
38978
39674
|
},
|
|
38979
|
-
...
|
|
38980
|
-
...
|
|
39675
|
+
...type && { type },
|
|
39676
|
+
...typeFields
|
|
38981
39677
|
};
|
|
38982
39678
|
await _updateVehicleById(_id, formattedValue, session);
|
|
39679
|
+
const peopleId = value.peopleId || vehicle.peopleId;
|
|
39680
|
+
if (isLive && peopleId && newPlate !== oldPlate) {
|
|
39681
|
+
await _pushVehicleById(
|
|
39682
|
+
peopleId,
|
|
39683
|
+
{ plateNumber: newPlate, recNo: "" },
|
|
39684
|
+
session
|
|
39685
|
+
);
|
|
39686
|
+
await _pullVehicleByRecNo(
|
|
39687
|
+
"",
|
|
39688
|
+
{ peopleId, site: cameraSite, plateNumber: oldPlate },
|
|
39689
|
+
session
|
|
39690
|
+
);
|
|
39691
|
+
}
|
|
38983
39692
|
await session.commitTransaction();
|
|
38984
39693
|
} catch (error) {
|
|
38985
39694
|
import_node_server_utils106.logger.error("Error in vehicle service update:", error);
|
|
39695
|
+
await rollbackCameras(journal, error);
|
|
38986
39696
|
await session.abortTransaction();
|
|
38987
39697
|
throw error;
|
|
38988
39698
|
} finally {
|
|
@@ -39018,25 +39728,12 @@ function useVehicleService() {
|
|
|
39018
39728
|
if (sanitizedValues.length === 0) {
|
|
39019
39729
|
throw new Error("No valid vehicle plate numbers found.");
|
|
39020
39730
|
}
|
|
39021
|
-
|
|
39022
|
-
|
|
39023
|
-
|
|
39024
|
-
|
|
39025
|
-
|
|
39026
|
-
|
|
39027
|
-
const siteCameraReq = await _getAllSiteCameras({
|
|
39028
|
-
site,
|
|
39029
|
-
type: "anpr",
|
|
39030
|
-
direction: ["both", "entry"],
|
|
39031
|
-
page,
|
|
39032
|
-
limit
|
|
39033
|
-
});
|
|
39034
|
-
pages = siteCameraReq.pages || 1;
|
|
39035
|
-
siteCameras.push(...siteCameraReq.items);
|
|
39036
|
-
page++;
|
|
39037
|
-
} while (page <= pages);
|
|
39038
|
-
}
|
|
39039
|
-
if (!siteCameras.length) {
|
|
39731
|
+
const isBlockRow = (row) => row.type !== "whitelist" /* WHITELIST */;
|
|
39732
|
+
const allowCameras = site ? await allowCamerasAt(site) : [];
|
|
39733
|
+
const blockCameras = site ? await blockCamerasAt(site) : [];
|
|
39734
|
+
const hasAllowRows = sanitizedValues.some((row) => !isBlockRow(row));
|
|
39735
|
+
const hasBlockRows = sanitizedValues.some((row) => isBlockRow(row));
|
|
39736
|
+
if (hasAllowRows && !allowCameras.length || hasBlockRows && !blockCameras.length) {
|
|
39040
39737
|
throw new Error("No site cameras found.");
|
|
39041
39738
|
}
|
|
39042
39739
|
const written = /* @__PURE__ */ new Map();
|
|
@@ -39045,58 +39742,63 @@ function useVehicleService() {
|
|
|
39045
39742
|
site,
|
|
39046
39743
|
plateNumber: value.plateNumber
|
|
39047
39744
|
}));
|
|
39048
|
-
const
|
|
39049
|
-
|
|
39050
|
-
|
|
39051
|
-
|
|
39052
|
-
|
|
39053
|
-
|
|
39054
|
-
|
|
39055
|
-
|
|
39056
|
-
|
|
39057
|
-
|
|
39058
|
-
|
|
39059
|
-
|
|
39060
|
-
|
|
39061
|
-
|
|
39062
|
-
|
|
39063
|
-
|
|
39064
|
-
|
|
39065
|
-
|
|
39066
|
-
|
|
39067
|
-
|
|
39068
|
-
|
|
39745
|
+
const writeRow = async (camera, row) => {
|
|
39746
|
+
const rowId = String(row._id);
|
|
39747
|
+
const vehicleValue = sanitizedValues[Number(rowId)];
|
|
39748
|
+
const plateNumber = vehicleValue.plateNumber;
|
|
39749
|
+
const { host, username, password } = camera;
|
|
39750
|
+
const dahuaPayload = {
|
|
39751
|
+
host,
|
|
39752
|
+
username,
|
|
39753
|
+
password,
|
|
39754
|
+
plateNumber,
|
|
39755
|
+
mode: vehicleValue.type === "whitelist" /* WHITELIST */ ? "TrafficRedList" /* TRAFFIC_REDLIST */ : "TrafficBlackList" /* TRAFFIC_BLACKLIST */,
|
|
39756
|
+
...vehicleValue.start ? { start: String(vehicleValue.start) } : {},
|
|
39757
|
+
...vehicleValue.end ? { end: String(vehicleValue.end) } : {},
|
|
39758
|
+
...vehicleValue.name ? { owner: vehicleValue.name } : {},
|
|
39759
|
+
...vehicleValue.vehicleModel ? { vehicleType: vehicleValue.vehicleModel } : {},
|
|
39760
|
+
...vehicleValue.vehicleColor ? { vehicleColor: vehicleValue.vehicleColor } : {},
|
|
39761
|
+
// Dates on the camera's clock, in the site's zone.
|
|
39762
|
+
timezone: await siteTimezone(camera.site ?? site)
|
|
39763
|
+
};
|
|
39764
|
+
try {
|
|
39765
|
+
const dahuaResponse = await _bulkInsertPlateNumber(dahuaPayload);
|
|
39766
|
+
const responseStatus = dahuaResponse?.statusCode || dahuaResponse?.status || dahuaResponse?.res?.status || "unknown";
|
|
39767
|
+
const responseData = dahuaResponse?.data?.toString("utf-8") ?? "";
|
|
39768
|
+
if (responseStatus !== 200) {
|
|
39769
|
+
logCameraFailure(
|
|
39770
|
+
"bulk add",
|
|
39771
|
+
camera,
|
|
39772
|
+
`HTTP ${responseStatus}: ${responseData.slice(0, 500)}`
|
|
39773
|
+
);
|
|
39774
|
+
return {
|
|
39775
|
+
ok: false,
|
|
39776
|
+
reason: anprWriteReason(`HTTP ${responseStatus}`)
|
|
39069
39777
|
};
|
|
39070
|
-
try {
|
|
39071
|
-
const dahuaResponse = await _bulkInsertPlateNumber(dahuaPayload);
|
|
39072
|
-
const responseStatus = dahuaResponse?.statusCode || dahuaResponse?.status || dahuaResponse?.res?.status || "unknown";
|
|
39073
|
-
const responseData = dahuaResponse?.data?.toString("utf-8") ?? "";
|
|
39074
|
-
if (responseStatus !== 200) {
|
|
39075
|
-
logCameraFailure(
|
|
39076
|
-
"bulk add",
|
|
39077
|
-
camera,
|
|
39078
|
-
`HTTP ${responseStatus}: ${responseData.slice(0, 500)}`
|
|
39079
|
-
);
|
|
39080
|
-
return {
|
|
39081
|
-
ok: false,
|
|
39082
|
-
reason: anprWriteReason(`HTTP ${responseStatus}`)
|
|
39083
|
-
};
|
|
39084
|
-
}
|
|
39085
|
-
const recNo = String(responseData.split("=")[1]?.trim() ?? "");
|
|
39086
|
-
if (recNo) {
|
|
39087
|
-
vehicleValue.recNo = vehicleValue.recNo || recNo;
|
|
39088
|
-
const entries = written.get(rowId) ?? [];
|
|
39089
|
-
entries.push({ camera, recno: recNo });
|
|
39090
|
-
written.set(rowId, entries);
|
|
39091
|
-
}
|
|
39092
|
-
return { ok: true, reason: "" };
|
|
39093
|
-
} catch (error) {
|
|
39094
|
-
logCameraFailure("bulk add", camera, error);
|
|
39095
|
-
return { ok: false, reason: anprWriteReason(error) };
|
|
39096
|
-
}
|
|
39097
39778
|
}
|
|
39779
|
+
const recNo = String(responseData.split("=")[1]?.trim() ?? "");
|
|
39780
|
+
if (recNo) {
|
|
39781
|
+
vehicleValue.recNo = vehicleValue.recNo || recNo;
|
|
39782
|
+
const entries = written.get(rowId) ?? [];
|
|
39783
|
+
entries.push({ camera, recno: recNo });
|
|
39784
|
+
written.set(rowId, entries);
|
|
39785
|
+
}
|
|
39786
|
+
return { ok: true, reason: "" };
|
|
39787
|
+
} catch (error) {
|
|
39788
|
+
logCameraFailure("bulk add", camera, error);
|
|
39789
|
+
return { ok: false, reason: anprWriteReason(error) };
|
|
39098
39790
|
}
|
|
39791
|
+
};
|
|
39792
|
+
const allowPass = await sweepExpiredVehicles(
|
|
39793
|
+
sweepRows.filter((row) => !isBlockRow(sanitizedValues[Number(row._id)])),
|
|
39794
|
+
{ camerasForSite: async () => allowCameras, cameraLabel, revoke: writeRow }
|
|
39795
|
+
);
|
|
39796
|
+
const blockPass = await sweepExpiredVehicles(
|
|
39797
|
+
sweepRows.filter((row) => isBlockRow(sanitizedValues[Number(row._id)])),
|
|
39798
|
+
{ camerasForSite: async () => blockCameras, cameraLabel, revoke: writeRow }
|
|
39099
39799
|
);
|
|
39800
|
+
const savableIds = [...allowPass.deletableIds, ...blockPass.deletableIds];
|
|
39801
|
+
const skipped = [...allowPass.skipped, ...blockPass.skipped];
|
|
39100
39802
|
const failedRows = [];
|
|
39101
39803
|
for (const entry of skipped) {
|
|
39102
39804
|
const stranded = [];
|
|
@@ -39141,123 +39843,26 @@ function useVehicleService() {
|
|
|
39141
39843
|
if (!session) {
|
|
39142
39844
|
throw new Error("Unable to start session for vehicle service.");
|
|
39143
39845
|
}
|
|
39846
|
+
const journal = cameraJournal();
|
|
39144
39847
|
try {
|
|
39145
39848
|
session.startTransaction();
|
|
39146
|
-
const vehicle = await
|
|
39147
|
-
|
|
39148
|
-
|
|
39149
|
-
const _plateNumber = plate?.plateNumber;
|
|
39150
|
-
const _start = vehicle.start;
|
|
39151
|
-
const _end = vehicle.end;
|
|
39152
|
-
const _recNo = plate.recNo;
|
|
39153
|
-
const _type = plate.type;
|
|
39154
|
-
if (value.peopleId) {
|
|
39155
|
-
value.peopleId = new import_mongodb72.ObjectId(value.peopleId);
|
|
39849
|
+
const vehicle = await findOne({ _id: new import_mongodb72.ObjectId(_id) });
|
|
39850
|
+
if (!vehicle) {
|
|
39851
|
+
throw new import_node_server_utils106.NotFoundError("Vehicle not found.");
|
|
39156
39852
|
}
|
|
39157
|
-
const
|
|
39158
|
-
const
|
|
39159
|
-
|
|
39160
|
-
|
|
39161
|
-
if (_plateNumber) {
|
|
39162
|
-
const siteCameras = [];
|
|
39163
|
-
let page = 1;
|
|
39164
|
-
let pages = 1;
|
|
39165
|
-
const limit = 20;
|
|
39166
|
-
do {
|
|
39167
|
-
const siteCameraReq = await _getAllSiteCameras({
|
|
39168
|
-
site,
|
|
39169
|
-
type: "anpr",
|
|
39170
|
-
direction: ["both", "entry"],
|
|
39171
|
-
page,
|
|
39172
|
-
limit
|
|
39173
|
-
});
|
|
39174
|
-
pages = siteCameraReq.pages || 1;
|
|
39175
|
-
siteCameras.push(...siteCameraReq.items);
|
|
39176
|
-
page++;
|
|
39177
|
-
} while (page <= pages);
|
|
39178
|
-
if (!siteCameras.length) {
|
|
39179
|
-
throw new import_node_server_utils106.BadRequestError(anprNoCameraRefusal("blocked"));
|
|
39180
|
-
}
|
|
39181
|
-
for (const camera of siteCameras) {
|
|
39182
|
-
const { host, username, password } = camera;
|
|
39183
|
-
const removePlateNumber = {
|
|
39184
|
-
host,
|
|
39185
|
-
username,
|
|
39186
|
-
password,
|
|
39187
|
-
mode: _type === "blocklist" /* BLOCKLIST */ ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */,
|
|
39188
|
-
recno: _recNo
|
|
39189
|
-
};
|
|
39190
|
-
const responseForDeletion = await _removePlateNumber(
|
|
39191
|
-
removePlateNumber
|
|
39192
|
-
);
|
|
39193
|
-
if (!responseForDeletion?.ok) {
|
|
39194
|
-
logCameraFailure(
|
|
39195
|
-
"blocklistVehicles revoke",
|
|
39196
|
-
camera,
|
|
39197
|
-
responseForDeletion?.detail ?? "no outcome returned"
|
|
39198
|
-
);
|
|
39199
|
-
throw new import_node_server_utils106.BadRequestError(
|
|
39200
|
-
anprRevokeFailureMessage(
|
|
39201
|
-
[
|
|
39202
|
-
{
|
|
39203
|
-
camera: cameraLabel(camera),
|
|
39204
|
-
reason: responseForDeletion?.reason || "the camera could not be reached"
|
|
39205
|
-
}
|
|
39206
|
-
],
|
|
39207
|
-
"updated"
|
|
39208
|
-
)
|
|
39209
|
-
);
|
|
39210
|
-
}
|
|
39211
|
-
const dahuaPayload = {
|
|
39212
|
-
host,
|
|
39213
|
-
username,
|
|
39214
|
-
password,
|
|
39215
|
-
plateNumber: _plateNumber,
|
|
39216
|
-
mode: "TrafficBlackList" /* TRAFFIC_BLACKLIST */,
|
|
39217
|
-
owner: _name,
|
|
39218
|
-
...startDahua ? { start: startDahua } : {},
|
|
39219
|
-
...endDahua ? { end: endDahua } : {}
|
|
39220
|
-
};
|
|
39221
|
-
const dahuaResponse = await _addPlateNumber(dahuaPayload);
|
|
39222
|
-
if (dahuaResponse?.statusCode !== 200) {
|
|
39223
|
-
logCameraFailure(
|
|
39224
|
-
"blocklistVehicles write",
|
|
39225
|
-
camera,
|
|
39226
|
-
`HTTP ${dahuaResponse?.statusCode ?? "none"}`
|
|
39227
|
-
);
|
|
39228
|
-
throw new import_node_server_utils106.BadRequestError(
|
|
39229
|
-
anprBlockFailureMessage([
|
|
39230
|
-
{
|
|
39231
|
-
camera: cameraLabel(camera),
|
|
39232
|
-
reason: anprWriteReason(
|
|
39233
|
-
`HTTP ${dahuaResponse?.statusCode ?? "none"}`
|
|
39234
|
-
)
|
|
39235
|
-
}
|
|
39236
|
-
])
|
|
39237
|
-
);
|
|
39238
|
-
}
|
|
39239
|
-
const responseData = dahuaResponse?.data?.toString("utf-8") ?? "";
|
|
39240
|
-
value.recNo = responseData.split("=")[1]?.trim();
|
|
39241
|
-
const normalizedPlateNumber = _plateNumber;
|
|
39242
|
-
if (value.peopleId && value.recNo) {
|
|
39243
|
-
await _pushVehicleById(
|
|
39244
|
-
value.peopleId,
|
|
39245
|
-
{
|
|
39246
|
-
plateNumber: normalizedPlateNumber,
|
|
39247
|
-
recNo: value.recNo
|
|
39248
|
-
},
|
|
39249
|
-
session
|
|
39250
|
-
);
|
|
39251
|
-
}
|
|
39252
|
-
}
|
|
39853
|
+
const owner = String(value.name || vehicle.name || "");
|
|
39854
|
+
const cameraSite = vehicle.site ?? value.site;
|
|
39855
|
+
if (vehicle.status === "active" /* ACTIVE */) {
|
|
39856
|
+
await blockOnCameras(vehicle, cameraSite, owner, journal);
|
|
39253
39857
|
}
|
|
39254
39858
|
const formattedValue = {
|
|
39255
39859
|
type: "blocklist" /* BLOCKLIST */,
|
|
39256
|
-
|
|
39860
|
+
blockedFrom: vehicle.type === "whitelist" /* WHITELIST */ ? "whitelist" /* WHITELIST */ : String(vehicle.blockedFrom ?? "")
|
|
39257
39861
|
};
|
|
39258
39862
|
await _updateVehicleById(_id, formattedValue, session);
|
|
39259
39863
|
await session.commitTransaction();
|
|
39260
39864
|
} catch (error) {
|
|
39865
|
+
await rollbackCameras(journal, error);
|
|
39261
39866
|
await session.abortTransaction();
|
|
39262
39867
|
throw error;
|
|
39263
39868
|
} finally {
|
|
@@ -39269,85 +39874,222 @@ function useVehicleService() {
|
|
|
39269
39874
|
if (!session) {
|
|
39270
39875
|
throw new Error("Unable to start session for vehicle service.");
|
|
39271
39876
|
}
|
|
39877
|
+
const journal = cameraJournal();
|
|
39272
39878
|
try {
|
|
39273
39879
|
session.startTransaction();
|
|
39274
|
-
const vehicle = await
|
|
39275
|
-
|
|
39276
|
-
|
|
39277
|
-
|
|
39880
|
+
const vehicle = await findOne({ _id: new import_mongodb72.ObjectId(_id) });
|
|
39881
|
+
if (!vehicle) {
|
|
39882
|
+
throw new import_node_server_utils106.NotFoundError("Vehicle not found.");
|
|
39883
|
+
}
|
|
39884
|
+
if (vehicle.type !== "blocklist" /* BLOCKLIST */) {
|
|
39885
|
+
throw new import_node_server_utils106.BadRequestError("This vehicle is not on the blocklist.");
|
|
39886
|
+
}
|
|
39887
|
+
const _plateNumber = String(vehicle.plateNumber ?? "");
|
|
39888
|
+
const cameraSite = vehicle.site ?? value.site;
|
|
39889
|
+
const restore = vehicle.blockedFrom === "whitelist" /* WHITELIST */;
|
|
39278
39890
|
if (value.peopleId) {
|
|
39279
39891
|
value.peopleId = new import_mongodb72.ObjectId(value.peopleId);
|
|
39280
39892
|
}
|
|
39281
|
-
|
|
39282
|
-
|
|
39283
|
-
|
|
39284
|
-
|
|
39285
|
-
|
|
39286
|
-
|
|
39287
|
-
do {
|
|
39288
|
-
const siteCameraReq = await _getAllSiteCameras({
|
|
39289
|
-
site,
|
|
39290
|
-
type: "anpr",
|
|
39291
|
-
direction: ["both", "entry"],
|
|
39292
|
-
page,
|
|
39293
|
-
limit
|
|
39294
|
-
});
|
|
39295
|
-
pages = siteCameraReq.pages || 1;
|
|
39296
|
-
siteCameras.push(...siteCameraReq.items);
|
|
39297
|
-
page++;
|
|
39298
|
-
} while (page <= pages);
|
|
39299
|
-
import_node_server_utils106.logger.info(
|
|
39300
|
-
`removeFromBlocklist resolved ${siteCameras.length} ANPR camera(s)`
|
|
39893
|
+
if (vehicle.status === "active" /* ACTIVE */) {
|
|
39894
|
+
await unblockOnCameras(
|
|
39895
|
+
vehicle,
|
|
39896
|
+
cameraSite,
|
|
39897
|
+
{ restore, owner: String(vehicle.name ?? "") },
|
|
39898
|
+
journal
|
|
39301
39899
|
);
|
|
39302
|
-
|
|
39303
|
-
|
|
39304
|
-
|
|
39305
|
-
|
|
39306
|
-
|
|
39307
|
-
|
|
39308
|
-
|
|
39309
|
-
|
|
39310
|
-
|
|
39311
|
-
const responseForDeletion = await _removePlateNumber(
|
|
39312
|
-
removePlateNumber
|
|
39313
|
-
);
|
|
39314
|
-
if (!responseForDeletion?.ok) {
|
|
39315
|
-
logCameraFailure(
|
|
39316
|
-
"removeFromBlocklist revoke",
|
|
39317
|
-
camera,
|
|
39318
|
-
responseForDeletion?.detail ?? "no outcome returned"
|
|
39319
|
-
);
|
|
39320
|
-
throw new import_node_server_utils106.BadRequestError(
|
|
39321
|
-
anprUnblockFailureMessage([
|
|
39322
|
-
{
|
|
39323
|
-
camera: cameraLabel(camera),
|
|
39324
|
-
reason: responseForDeletion?.reason || "the camera could not be reached"
|
|
39325
|
-
}
|
|
39326
|
-
])
|
|
39327
|
-
);
|
|
39328
|
-
}
|
|
39329
|
-
}
|
|
39900
|
+
}
|
|
39901
|
+
if (restore) {
|
|
39902
|
+
await _updateVehicleById(
|
|
39903
|
+
_id,
|
|
39904
|
+
{ type: "whitelist" /* WHITELIST */, blockedFrom: "" },
|
|
39905
|
+
session
|
|
39906
|
+
);
|
|
39907
|
+
await session.commitTransaction();
|
|
39908
|
+
return "Vehicle unblocked and restored to the whitelist.";
|
|
39330
39909
|
}
|
|
39331
39910
|
await _deleteVehicle(_id, session);
|
|
39332
|
-
|
|
39911
|
+
const peopleId = value.peopleId || vehicle.peopleId;
|
|
39912
|
+
if ((peopleId || cameraSite) && _plateNumber) {
|
|
39333
39913
|
await _pullVehicleByRecNo(
|
|
39334
|
-
|
|
39914
|
+
"",
|
|
39335
39915
|
{
|
|
39336
|
-
peopleId
|
|
39337
|
-
site,
|
|
39916
|
+
peopleId,
|
|
39917
|
+
site: cameraSite,
|
|
39338
39918
|
plateNumber: _plateNumber
|
|
39339
39919
|
},
|
|
39340
39920
|
session
|
|
39341
39921
|
);
|
|
39342
39922
|
}
|
|
39343
39923
|
await session.commitTransaction();
|
|
39924
|
+
return "Vehicle removed from the blocklist.";
|
|
39344
39925
|
} catch (error) {
|
|
39926
|
+
await rollbackCameras(journal, error);
|
|
39345
39927
|
await session.abortTransaction();
|
|
39346
39928
|
throw error;
|
|
39347
39929
|
} finally {
|
|
39348
39930
|
session.endSession();
|
|
39349
39931
|
}
|
|
39350
39932
|
}
|
|
39933
|
+
async function fillCameraLists(site, camera, lists) {
|
|
39934
|
+
let target;
|
|
39935
|
+
if (camera.id) {
|
|
39936
|
+
const stored = (await camerasAt(site)).find(
|
|
39937
|
+
(candidate) => String(candidate._id) === String(camera.id)
|
|
39938
|
+
);
|
|
39939
|
+
if (!stored)
|
|
39940
|
+
throw new import_node_server_utils106.NotFoundError("Camera not found.");
|
|
39941
|
+
target = {
|
|
39942
|
+
...stored,
|
|
39943
|
+
...camera.host ? { host: camera.host } : {},
|
|
39944
|
+
...camera.username ? { username: camera.username } : {},
|
|
39945
|
+
...camera.password ? { password: camera.password } : {},
|
|
39946
|
+
...camera.direction ? { direction: camera.direction } : {}
|
|
39947
|
+
};
|
|
39948
|
+
} else {
|
|
39949
|
+
target = {
|
|
39950
|
+
host: String(camera.host ?? ""),
|
|
39951
|
+
username: String(camera.username ?? ""),
|
|
39952
|
+
password: String(camera.password ?? ""),
|
|
39953
|
+
name: camera.name,
|
|
39954
|
+
direction: camera.direction,
|
|
39955
|
+
// Whose clock the season pass windows are written on.
|
|
39956
|
+
site
|
|
39957
|
+
};
|
|
39958
|
+
}
|
|
39959
|
+
const vehicles = await _getLivePlatesBySite(String(site));
|
|
39960
|
+
const unique = (rows) => {
|
|
39961
|
+
const seen = /* @__PURE__ */ new Set();
|
|
39962
|
+
return rows.filter((row) => {
|
|
39963
|
+
const key = normalisePlate(row.plateNumber);
|
|
39964
|
+
if (!key || seen.has(key))
|
|
39965
|
+
return false;
|
|
39966
|
+
seen.add(key);
|
|
39967
|
+
return true;
|
|
39968
|
+
});
|
|
39969
|
+
};
|
|
39970
|
+
const blocked = lists.block ? unique(vehicles.filter((vehicle) => listsHeldBy(vehicle).block)) : [];
|
|
39971
|
+
const allowed = lists.allow ? unique(vehicles.filter((vehicle) => listsHeldBy(vehicle).allow)) : [];
|
|
39972
|
+
const writes = [
|
|
39973
|
+
...blocked.map((vehicle) => ({
|
|
39974
|
+
plateNumber: String(vehicle.plateNumber),
|
|
39975
|
+
mode: "TrafficBlackList" /* TRAFFIC_BLACKLIST */,
|
|
39976
|
+
owner: String(vehicle.name ?? ""),
|
|
39977
|
+
...blockWindow(vehicle.end)
|
|
39978
|
+
})),
|
|
39979
|
+
...allowed.map((vehicle) => ({
|
|
39980
|
+
plateNumber: String(vehicle.plateNumber),
|
|
39981
|
+
mode: "TrafficRedList" /* TRAFFIC_REDLIST */,
|
|
39982
|
+
owner: String(vehicle.name ?? ""),
|
|
39983
|
+
...cameraWindow(vehicle.start, vehicle.end)
|
|
39984
|
+
}))
|
|
39985
|
+
];
|
|
39986
|
+
const written = { allowed: 0, blocked: 0 };
|
|
39987
|
+
for (const write of writes) {
|
|
39988
|
+
try {
|
|
39989
|
+
await insertPlateOnCamera(target, write);
|
|
39990
|
+
} catch (error) {
|
|
39991
|
+
logCameraFailure("fill camera", target, error);
|
|
39992
|
+
throw new import_node_server_utils106.BadRequestError(
|
|
39993
|
+
`Only ${written.allowed + written.blocked} of ${writes.length} plates could be added to ${cameraLabel(target)} before it stopped: ${anprWriteReason(error)}.`
|
|
39994
|
+
);
|
|
39995
|
+
}
|
|
39996
|
+
if (write.mode === "TrafficBlackList" /* TRAFFIC_BLACKLIST */)
|
|
39997
|
+
written.blocked++;
|
|
39998
|
+
else
|
|
39999
|
+
written.allowed++;
|
|
40000
|
+
}
|
|
40001
|
+
import_node_server_utils106.logger.info(
|
|
40002
|
+
`fillCameraLists wrote ${written.allowed} allowed and ${written.blocked} blocked plate(s) for site ${String(site)}`
|
|
40003
|
+
);
|
|
40004
|
+
return written;
|
|
40005
|
+
}
|
|
40006
|
+
async function newCameraPlateRows(site, direction) {
|
|
40007
|
+
const lists = listsFilledByDirectionChange("", direction);
|
|
40008
|
+
if (!lists.allow && !lists.block)
|
|
40009
|
+
return [];
|
|
40010
|
+
const vehicles = await _getLivePlatesBySite(String(site));
|
|
40011
|
+
const rowsFor = (list) => {
|
|
40012
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40013
|
+
const rows = [];
|
|
40014
|
+
for (const vehicle of vehicles) {
|
|
40015
|
+
const held = listsHeldBy(vehicle);
|
|
40016
|
+
if (list === "blocked" ? !held.block : !held.allow)
|
|
40017
|
+
continue;
|
|
40018
|
+
const key = normalisePlate(vehicle.plateNumber);
|
|
40019
|
+
if (!key || seen.has(key))
|
|
40020
|
+
continue;
|
|
40021
|
+
seen.add(key);
|
|
40022
|
+
rows.push({
|
|
40023
|
+
id: `${list}:${String(vehicle._id)}`,
|
|
40024
|
+
vehicle: String(vehicle._id),
|
|
40025
|
+
list,
|
|
40026
|
+
plateNumber: String(vehicle.plateNumber ?? ""),
|
|
40027
|
+
name: String(vehicle.name ?? ""),
|
|
40028
|
+
block: String(vehicle.blockName ?? ""),
|
|
40029
|
+
level: String(vehicle.levelName ?? ""),
|
|
40030
|
+
unit: String(vehicle.unitName ?? ""),
|
|
40031
|
+
kind: list === "blocked" ? "Blocked" : vehicle.seasonPassType ? "Season pass" : "Whitelist"
|
|
40032
|
+
});
|
|
40033
|
+
}
|
|
40034
|
+
return rows;
|
|
40035
|
+
};
|
|
40036
|
+
return [
|
|
40037
|
+
...lists.block ? rowsFor("blocked") : [],
|
|
40038
|
+
...lists.allow ? rowsFor("allowed") : []
|
|
40039
|
+
];
|
|
40040
|
+
}
|
|
40041
|
+
async function writeVehicleToNewCamera(site, device, vehicleId, list, options = {}) {
|
|
40042
|
+
let siteId;
|
|
40043
|
+
let id;
|
|
40044
|
+
try {
|
|
40045
|
+
siteId = new import_mongodb72.ObjectId(String(site));
|
|
40046
|
+
id = new import_mongodb72.ObjectId(String(vehicleId));
|
|
40047
|
+
} catch {
|
|
40048
|
+
throw new import_node_server_utils106.BadRequestError("Invalid vehicle or site ID.");
|
|
40049
|
+
}
|
|
40050
|
+
const vehicle = await findOne({
|
|
40051
|
+
_id: id,
|
|
40052
|
+
site: siteId,
|
|
40053
|
+
status: "active" /* ACTIVE */
|
|
40054
|
+
});
|
|
40055
|
+
if (!vehicle)
|
|
40056
|
+
throw new import_node_server_utils106.NotFoundError("Vehicle not found.");
|
|
40057
|
+
const held = listsHeldBy(vehicle);
|
|
40058
|
+
if (list === "blocked" ? !held.block : !held.allow) {
|
|
40059
|
+
throw new import_node_server_utils106.BadRequestError(
|
|
40060
|
+
"This vehicle no longer belongs on that list. Refresh and try again."
|
|
40061
|
+
);
|
|
40062
|
+
}
|
|
40063
|
+
const write = {
|
|
40064
|
+
plateNumber: String(vehicle.plateNumber ?? ""),
|
|
40065
|
+
mode: list === "blocked" ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */,
|
|
40066
|
+
owner: String(vehicle.name ?? ""),
|
|
40067
|
+
...list === "blocked" ? blockWindow(vehicle.end) : cameraWindow(vehicle.start, vehicle.end)
|
|
40068
|
+
};
|
|
40069
|
+
const target = {
|
|
40070
|
+
host: device.host,
|
|
40071
|
+
username: device.username,
|
|
40072
|
+
password: device.password,
|
|
40073
|
+
name: device.name,
|
|
40074
|
+
direction: device.direction,
|
|
40075
|
+
// Whose clock the season pass window is written on.
|
|
40076
|
+
site: siteId
|
|
40077
|
+
};
|
|
40078
|
+
try {
|
|
40079
|
+
if (options.onlyIfMissing)
|
|
40080
|
+
await ensurePlateOnCamera(target, write);
|
|
40081
|
+
else
|
|
40082
|
+
await insertPlateOnCamera(target, write);
|
|
40083
|
+
return { ok: true };
|
|
40084
|
+
} catch (error) {
|
|
40085
|
+
logCameraFailure("new camera plate", target, error);
|
|
40086
|
+
return {
|
|
40087
|
+
ok: false,
|
|
40088
|
+
reason: anprWriteReason(error),
|
|
40089
|
+
kind: classifyDahuaFailure(error?.message ?? error)
|
|
40090
|
+
};
|
|
40091
|
+
}
|
|
40092
|
+
}
|
|
39351
40093
|
return {
|
|
39352
40094
|
add,
|
|
39353
40095
|
deleteVehicle,
|
|
@@ -39357,7 +40099,10 @@ function useVehicleService() {
|
|
|
39357
40099
|
updateVehicleById,
|
|
39358
40100
|
bulkUpsertVehicles,
|
|
39359
40101
|
blocklistVehicles,
|
|
39360
|
-
removeFromBlocklist
|
|
40102
|
+
removeFromBlocklist,
|
|
40103
|
+
fillCameraLists,
|
|
40104
|
+
newCameraPlateRows,
|
|
40105
|
+
writeVehicleToNewCamera
|
|
39361
40106
|
};
|
|
39362
40107
|
}
|
|
39363
40108
|
|
|
@@ -43571,8 +44316,10 @@ function useVehicleController() {
|
|
|
43571
44316
|
}
|
|
43572
44317
|
const { _id, ...rest } = value;
|
|
43573
44318
|
await requireVehicleReach(req, _id);
|
|
43574
|
-
await _removeFromBlocklist(_id, rest);
|
|
43575
|
-
res.json({
|
|
44319
|
+
const message = await _removeFromBlocklist(_id, rest);
|
|
44320
|
+
res.json({
|
|
44321
|
+
message: message || "Successfully removed vehicle from blocklist."
|
|
44322
|
+
});
|
|
43576
44323
|
return;
|
|
43577
44324
|
} catch (error) {
|
|
43578
44325
|
import_node_server_utils120.logger.log({ level: "error", message: error.message });
|
|
@@ -43672,10 +44419,69 @@ function useSiteCameraService() {
|
|
|
43672
44419
|
session.endSession();
|
|
43673
44420
|
}
|
|
43674
44421
|
}
|
|
44422
|
+
async function clearPlateLists(site, camera, lists, action = "type-change") {
|
|
44423
|
+
let target;
|
|
44424
|
+
const id = typeof camera === "string" ? camera : "";
|
|
44425
|
+
if (typeof camera === "string") {
|
|
44426
|
+
const cameras = [];
|
|
44427
|
+
let page = 1;
|
|
44428
|
+
let pages = 1;
|
|
44429
|
+
const limit = 20;
|
|
44430
|
+
do {
|
|
44431
|
+
const siteCameraReq = await _getAllCameraWithPassword({
|
|
44432
|
+
site: String(site ?? ""),
|
|
44433
|
+
type: "anpr",
|
|
44434
|
+
page,
|
|
44435
|
+
limit
|
|
44436
|
+
});
|
|
44437
|
+
pages = siteCameraReq.pages || 1;
|
|
44438
|
+
cameras.push(...siteCameraReq.items);
|
|
44439
|
+
page++;
|
|
44440
|
+
} while (page <= pages);
|
|
44441
|
+
target = cameras.find((candidate) => String(candidate._id) === camera);
|
|
44442
|
+
if (!target)
|
|
44443
|
+
throw new import_node_server_utils121.NotFoundError("Camera not found.");
|
|
44444
|
+
} else {
|
|
44445
|
+
target = camera;
|
|
44446
|
+
}
|
|
44447
|
+
const label = cameraLabel(target);
|
|
44448
|
+
const notDone = action === "add" ? "The camera has NOT been added." : action === "sync" ? "No vehicles have been added to it." : "The camera type has NOT been changed.";
|
|
44449
|
+
const cleared = [];
|
|
44450
|
+
const steps = [
|
|
44451
|
+
...lists.allow ? [{ list: "allowed", mode: "TrafficRedList" /* TRAFFIC_REDLIST */ }] : [],
|
|
44452
|
+
...lists.block ? [{ list: "blocked", mode: "TrafficBlackList" /* TRAFFIC_BLACKLIST */ }] : []
|
|
44453
|
+
];
|
|
44454
|
+
for (const [index, step] of steps.entries()) {
|
|
44455
|
+
if (index > 0)
|
|
44456
|
+
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
44457
|
+
const outcome = await dahuaService.clearPlateList({
|
|
44458
|
+
host: target.host,
|
|
44459
|
+
username: target.username,
|
|
44460
|
+
password: target.password,
|
|
44461
|
+
mode: step.mode
|
|
44462
|
+
});
|
|
44463
|
+
if (!outcome?.ok) {
|
|
44464
|
+
import_node_server_utils121.logger.error(
|
|
44465
|
+
`clearPlateLists could not clear ${step.mode} on camera ${id || target.host}: ${outcome?.detail ?? "no outcome"}`
|
|
44466
|
+
);
|
|
44467
|
+
const listName = step.list === "allowed" ? "allowed list" : "blocklist";
|
|
44468
|
+
const reason = outcome?.reason || "the camera could not be reached";
|
|
44469
|
+
throw new import_node_server_utils121.BadRequestError(
|
|
44470
|
+
cleared.length ? `The allowed list on ${label} was cleared, but its blocklist could not be: ${reason}. ${notDone} Until it is sorted out, whitelist and season pass vehicles will not be let in through this camera. Please check the camera and try again.` : `The ${listName} on ${label} could not be cleared: ${reason}. Nothing on the camera has changed. ${notDone} Please check the camera and try again.`
|
|
44471
|
+
);
|
|
44472
|
+
}
|
|
44473
|
+
cleared.push(step.list);
|
|
44474
|
+
}
|
|
44475
|
+
import_node_server_utils121.logger.info(
|
|
44476
|
+
`clearPlateLists cleared ${cleared.join(" and ") || "nothing"} on camera ${id || target.host}`
|
|
44477
|
+
);
|
|
44478
|
+
return cleared;
|
|
44479
|
+
}
|
|
43675
44480
|
return {
|
|
43676
44481
|
add,
|
|
43677
44482
|
listenToCapturedPlateNumber,
|
|
43678
|
-
updateById
|
|
44483
|
+
updateById,
|
|
44484
|
+
clearPlateLists
|
|
43679
44485
|
};
|
|
43680
44486
|
}
|
|
43681
44487
|
|
|
@@ -43811,8 +44617,29 @@ function summariseBulkCameraPlan(results) {
|
|
|
43811
44617
|
}
|
|
43812
44618
|
|
|
43813
44619
|
// src/controllers/site-camera.controller.ts
|
|
44620
|
+
var CLEARED_ONLY_NOTE = " The camera's plate lists were cleared.";
|
|
44621
|
+
function filledNote(filled, cleared = true) {
|
|
44622
|
+
const parts = [
|
|
44623
|
+
...filled.allowed ? [`${filled.allowed} allowed`] : [],
|
|
44624
|
+
...filled.blocked ? [`${filled.blocked} blocked`] : []
|
|
44625
|
+
];
|
|
44626
|
+
const total = filled.allowed + filled.blocked;
|
|
44627
|
+
if (!total) {
|
|
44628
|
+
return cleared ? " The camera's plate lists were cleared; there were no vehicle plates to add." : " There were no vehicle plates to add to the camera.";
|
|
44629
|
+
}
|
|
44630
|
+
const added = `${parts.join(" and ")} ${total === 1 ? "plate was" : "plates were"} added`;
|
|
44631
|
+
return cleared ? ` The camera's plate lists were cleared and ${added} to it.` : ` ${added} to the camera.`;
|
|
44632
|
+
}
|
|
44633
|
+
function cameraListClearConfirmation(lists) {
|
|
44634
|
+
const what = lists.allow && lists.block ? "its allowed list and its blocklist" : lists.block ? "its blocklist" : "its allowed list";
|
|
44635
|
+
return `This change clears every plate on ${what}, on the camera itself - including plates added directly on the camera. Confirm the change to continue.`;
|
|
44636
|
+
}
|
|
43814
44637
|
function useSiteCameraController() {
|
|
43815
|
-
const {
|
|
44638
|
+
const {
|
|
44639
|
+
add: _add,
|
|
44640
|
+
updateById: _updateById,
|
|
44641
|
+
clearPlateLists: _clearPlateLists
|
|
44642
|
+
} = useSiteCameraService();
|
|
43816
44643
|
const {
|
|
43817
44644
|
add: _addOne,
|
|
43818
44645
|
activeHostsBySite: _activeHostsBySite,
|
|
@@ -43896,6 +44723,12 @@ function useSiteCameraController() {
|
|
|
43896
44723
|
return null;
|
|
43897
44724
|
}
|
|
43898
44725
|
async function add(req, res, next) {
|
|
44726
|
+
const clearCameraLists = req.body?.clearCameraLists === true;
|
|
44727
|
+
const skipPlateSync = req.body?.skipPlateSync === true;
|
|
44728
|
+
if (req.body && typeof req.body === "object") {
|
|
44729
|
+
delete req.body.clearCameraLists;
|
|
44730
|
+
delete req.body.skipPlateSync;
|
|
44731
|
+
}
|
|
43899
44732
|
const data = req.body;
|
|
43900
44733
|
const refusal = applyDerivedHost(data);
|
|
43901
44734
|
if (refusal) {
|
|
@@ -43914,6 +44747,52 @@ function useSiteCameraController() {
|
|
|
43914
44747
|
type: value.type,
|
|
43915
44748
|
notFound: "Site not found."
|
|
43916
44749
|
});
|
|
44750
|
+
const isActiveAnpr = !skipPlateSync && String(value.type ?? "").toLowerCase() === "anpr" && String(value.status ?? "active") === "active";
|
|
44751
|
+
const lists = isActiveAnpr ? listsFilledByDirectionChange("", value.direction) : { allow: false, block: false };
|
|
44752
|
+
const willClear = isActiveAnpr && clearCameraLists;
|
|
44753
|
+
let cleared = false;
|
|
44754
|
+
let filled = null;
|
|
44755
|
+
if (willClear || lists.allow || lists.block) {
|
|
44756
|
+
const taken = await _activeHostsBySite(value.site);
|
|
44757
|
+
if (taken.includes(value.host)) {
|
|
44758
|
+
throw new import_node_server_utils122.BadRequestError("ANPR already exist.");
|
|
44759
|
+
}
|
|
44760
|
+
const device = {
|
|
44761
|
+
host: value.host,
|
|
44762
|
+
username: value.username,
|
|
44763
|
+
password: value.password,
|
|
44764
|
+
name: value.name,
|
|
44765
|
+
direction: value.direction
|
|
44766
|
+
};
|
|
44767
|
+
const everything = { allow: true, block: true };
|
|
44768
|
+
if (willClear) {
|
|
44769
|
+
await _clearPlateLists(value.site, device, everything, "add");
|
|
44770
|
+
cleared = true;
|
|
44771
|
+
}
|
|
44772
|
+
if (lists.allow || lists.block) {
|
|
44773
|
+
try {
|
|
44774
|
+
filled = await useVehicleService().fillCameraLists(
|
|
44775
|
+
value.site,
|
|
44776
|
+
device,
|
|
44777
|
+
lists
|
|
44778
|
+
);
|
|
44779
|
+
} catch (error2) {
|
|
44780
|
+
let undone = null;
|
|
44781
|
+
if (willClear) {
|
|
44782
|
+
try {
|
|
44783
|
+
await _clearPlateLists(value.site, device, everything, "add");
|
|
44784
|
+
undone = true;
|
|
44785
|
+
} catch {
|
|
44786
|
+
undone = false;
|
|
44787
|
+
}
|
|
44788
|
+
}
|
|
44789
|
+
if (error2 && typeof error2 === "object") {
|
|
44790
|
+
error2.message = `${error2.message ?? ""} The camera has NOT been added` + (undone === true ? ` and the plates written to it were cleared again.` : undone === false ? `, and the plates written to it could NOT be cleared again - clear them on the camera by hand.` : `. The plates already written to it stay on the camera.`) + ` Please check the camera and try again.`;
|
|
44791
|
+
}
|
|
44792
|
+
throw error2;
|
|
44793
|
+
}
|
|
44794
|
+
}
|
|
44795
|
+
}
|
|
43917
44796
|
const vehicle = await _add(value);
|
|
43918
44797
|
await recordConsoleAction({
|
|
43919
44798
|
action: "camera.added" /* CAMERA_ADDED */,
|
|
@@ -43923,8 +44802,10 @@ function useSiteCameraController() {
|
|
|
43923
44802
|
after: pickAuditFields("camera.added" /* CAMERA_ADDED */, value)
|
|
43924
44803
|
});
|
|
43925
44804
|
res.status(201).json({
|
|
43926
|
-
message:
|
|
43927
|
-
data: { vehicle }
|
|
44805
|
+
message: `ANPR(s) added successfully.` + (filled ? filledNote(filled, cleared) : cleared ? CLEARED_ONLY_NOTE : ""),
|
|
44806
|
+
data: { vehicle },
|
|
44807
|
+
...filled ? { addedPlates: filled } : {},
|
|
44808
|
+
...cleared ? { clearedLists: ["allowed", "blocked"] } : {}
|
|
43928
44809
|
});
|
|
43929
44810
|
} catch (error2) {
|
|
43930
44811
|
next(error2);
|
|
@@ -44033,6 +44914,12 @@ function useSiteCameraController() {
|
|
|
44033
44914
|
}
|
|
44034
44915
|
}
|
|
44035
44916
|
async function updateById(req, res, next) {
|
|
44917
|
+
const confirmClearCameraLists = req.body?.confirmClearCameraLists === true;
|
|
44918
|
+
const skipPlateSync = req.body?.skipPlateSync === true;
|
|
44919
|
+
if (req.body && typeof req.body === "object") {
|
|
44920
|
+
delete req.body.confirmClearCameraLists;
|
|
44921
|
+
delete req.body.skipPlateSync;
|
|
44922
|
+
}
|
|
44036
44923
|
const value = req.body;
|
|
44037
44924
|
const id = req.params.id ?? "";
|
|
44038
44925
|
const refusal = applyDerivedHost(value);
|
|
@@ -44067,6 +44954,43 @@ function useSiteCameraController() {
|
|
|
44067
44954
|
throw error2;
|
|
44068
44955
|
}
|
|
44069
44956
|
const update = staffId ? staffUpdate(camera, value.status) : value;
|
|
44957
|
+
const lists = String(camera.type ?? "").toLowerCase() === "anpr" ? listsClearedByDirectionChange(camera.direction, update?.direction) : { allow: false, block: false };
|
|
44958
|
+
let clearedLists = null;
|
|
44959
|
+
if (lists.allow || lists.block) {
|
|
44960
|
+
if (!confirmClearCameraLists) {
|
|
44961
|
+
throw new import_node_server_utils122.BadRequestError(cameraListClearConfirmation(lists));
|
|
44962
|
+
}
|
|
44963
|
+
clearedLists = await _clearPlateLists(camera.site, id, lists);
|
|
44964
|
+
}
|
|
44965
|
+
const fill = !skipPlateSync && String(camera.type ?? "").toLowerCase() === "anpr" && String(update?.status ?? camera.status ?? "active") === "active" ? listsFilledByDirectionChange(camera.direction, update?.direction) : { allow: false, block: false };
|
|
44966
|
+
let filled = null;
|
|
44967
|
+
if (fill.allow || fill.block) {
|
|
44968
|
+
await _clearPlateLists(camera.site, id, fill);
|
|
44969
|
+
try {
|
|
44970
|
+
filled = await useVehicleService().fillCameraLists(
|
|
44971
|
+
camera.site,
|
|
44972
|
+
{
|
|
44973
|
+
id,
|
|
44974
|
+
host: update?.host,
|
|
44975
|
+
username: update?.username,
|
|
44976
|
+
password: update?.password
|
|
44977
|
+
},
|
|
44978
|
+
fill
|
|
44979
|
+
);
|
|
44980
|
+
} catch (error2) {
|
|
44981
|
+
let undone = false;
|
|
44982
|
+
try {
|
|
44983
|
+
await _clearPlateLists(camera.site, id, fill);
|
|
44984
|
+
undone = true;
|
|
44985
|
+
} catch {
|
|
44986
|
+
undone = false;
|
|
44987
|
+
}
|
|
44988
|
+
if (error2 && typeof error2 === "object") {
|
|
44989
|
+
error2.message = `${error2.message ?? ""} The camera type has NOT been changed` + (undone ? ` and the plates written to it were cleared again.` : `, and the plates written to it could NOT be cleared again - clear them on the camera by hand.`) + ` Please check the camera and try again.`;
|
|
44990
|
+
}
|
|
44991
|
+
throw error2;
|
|
44992
|
+
}
|
|
44993
|
+
}
|
|
44070
44994
|
const message = await _updateById(id, update);
|
|
44071
44995
|
if (staffId) {
|
|
44072
44996
|
await recordConsoleAction({
|
|
@@ -44088,8 +45012,11 @@ function useSiteCameraController() {
|
|
|
44088
45012
|
after: pickAuditFields("camera.updated" /* CAMERA_UPDATED */, update)
|
|
44089
45013
|
});
|
|
44090
45014
|
}
|
|
45015
|
+
const clearedNote = clearedLists === null ? "" : clearedLists.length === 2 ? " The camera's allowed list and blocklist were cleared." : clearedLists[0] === "blocked" ? " The camera's blocklist was cleared." : " The camera's allowed list was cleared.";
|
|
44091
45016
|
res.json({
|
|
44092
|
-
message
|
|
45017
|
+
message: `${message}${clearedNote}${filled ? filledNote(filled) : ""}`,
|
|
45018
|
+
...clearedLists === null ? {} : { clearedLists },
|
|
45019
|
+
...filled ? { addedPlates: filled } : {}
|
|
44093
45020
|
});
|
|
44094
45021
|
} catch (error2) {
|
|
44095
45022
|
next(error2);
|
|
@@ -44130,12 +45057,216 @@ function useSiteCameraController() {
|
|
|
44130
45057
|
next(error2);
|
|
44131
45058
|
}
|
|
44132
45059
|
}
|
|
45060
|
+
const schemaNewCameraDevice = {
|
|
45061
|
+
host: import_joi58.default.string().trim().required(),
|
|
45062
|
+
username: import_joi58.default.string().required(),
|
|
45063
|
+
password: import_joi58.default.string().required(),
|
|
45064
|
+
name: import_joi58.default.string().optional().allow("", null),
|
|
45065
|
+
direction: import_joi58.default.string().required().valid("entry", "exit", "both", "visitors", "residents")
|
|
45066
|
+
};
|
|
45067
|
+
async function authorizeNewCamera(req, siteId, host) {
|
|
45068
|
+
await authorizeWrite({
|
|
45069
|
+
req,
|
|
45070
|
+
site: siteId,
|
|
45071
|
+
type: "anpr",
|
|
45072
|
+
notFound: "Site not found."
|
|
45073
|
+
});
|
|
45074
|
+
const taken = await _activeHostsBySite(siteId);
|
|
45075
|
+
if (taken.includes(host)) {
|
|
45076
|
+
throw new import_node_server_utils122.BadRequestError("ANPR already exist.");
|
|
45077
|
+
}
|
|
45078
|
+
}
|
|
45079
|
+
async function newCameraPlates(req, res, next) {
|
|
45080
|
+
const { error, value } = import_joi58.default.object({
|
|
45081
|
+
siteId: import_joi58.default.string().hex().length(24).required(),
|
|
45082
|
+
host: import_joi58.default.string().trim().required(),
|
|
45083
|
+
direction: schemaNewCameraDevice.direction
|
|
45084
|
+
}).validate({
|
|
45085
|
+
siteId: req.params.siteId,
|
|
45086
|
+
host: req.query?.host,
|
|
45087
|
+
direction: req.query?.direction
|
|
45088
|
+
});
|
|
45089
|
+
if (error) {
|
|
45090
|
+
next(new import_node_server_utils122.BadRequestError(error.message));
|
|
45091
|
+
return;
|
|
45092
|
+
}
|
|
45093
|
+
try {
|
|
45094
|
+
await authorizeNewCamera(req, value.siteId, value.host);
|
|
45095
|
+
const items = await useVehicleService().newCameraPlateRows(
|
|
45096
|
+
value.siteId,
|
|
45097
|
+
value.direction
|
|
45098
|
+
);
|
|
45099
|
+
res.json({ items });
|
|
45100
|
+
} catch (error2) {
|
|
45101
|
+
next(error2);
|
|
45102
|
+
}
|
|
45103
|
+
}
|
|
45104
|
+
async function clearNewCamera(req, res, next) {
|
|
45105
|
+
const { error, value } = import_joi58.default.object({
|
|
45106
|
+
siteId: import_joi58.default.string().hex().length(24).required(),
|
|
45107
|
+
...schemaNewCameraDevice
|
|
45108
|
+
}).validate({ siteId: req.params.siteId, ...req.body ?? {} });
|
|
45109
|
+
if (error) {
|
|
45110
|
+
next(new import_node_server_utils122.BadRequestError(error.message));
|
|
45111
|
+
return;
|
|
45112
|
+
}
|
|
45113
|
+
try {
|
|
45114
|
+
await authorizeNewCamera(req, value.siteId, value.host);
|
|
45115
|
+
const { siteId, ...device } = value;
|
|
45116
|
+
const cleared = await _clearPlateLists(
|
|
45117
|
+
siteId,
|
|
45118
|
+
device,
|
|
45119
|
+
{ allow: true, block: true },
|
|
45120
|
+
"add"
|
|
45121
|
+
);
|
|
45122
|
+
res.json({
|
|
45123
|
+
message: "The camera's allowed list and blocklist were cleared.",
|
|
45124
|
+
clearedLists: cleared
|
|
45125
|
+
});
|
|
45126
|
+
} catch (error2) {
|
|
45127
|
+
next(error2);
|
|
45128
|
+
}
|
|
45129
|
+
}
|
|
45130
|
+
async function addNewCameraPlate(req, res, next) {
|
|
45131
|
+
const { error, value } = import_joi58.default.object({
|
|
45132
|
+
siteId: import_joi58.default.string().hex().length(24).required(),
|
|
45133
|
+
...schemaNewCameraDevice,
|
|
45134
|
+
vehicle: import_joi58.default.string().hex().length(24).required(),
|
|
45135
|
+
list: import_joi58.default.string().valid("allowed", "blocked").required(),
|
|
45136
|
+
onlyIfMissing: import_joi58.default.boolean().optional().default(false)
|
|
45137
|
+
}).validate({ siteId: req.params.siteId, ...req.body ?? {} });
|
|
45138
|
+
if (error) {
|
|
45139
|
+
next(new import_node_server_utils122.BadRequestError(error.message));
|
|
45140
|
+
return;
|
|
45141
|
+
}
|
|
45142
|
+
try {
|
|
45143
|
+
await authorizeNewCamera(req, value.siteId, value.host);
|
|
45144
|
+
const { siteId, vehicle, list, onlyIfMissing, ...device } = value;
|
|
45145
|
+
const outcome = await useVehicleService().writeVehicleToNewCamera(
|
|
45146
|
+
siteId,
|
|
45147
|
+
device,
|
|
45148
|
+
vehicle,
|
|
45149
|
+
list,
|
|
45150
|
+
{ onlyIfMissing }
|
|
45151
|
+
);
|
|
45152
|
+
res.json(outcome);
|
|
45153
|
+
} catch (error2) {
|
|
45154
|
+
next(error2);
|
|
45155
|
+
}
|
|
45156
|
+
}
|
|
45157
|
+
async function loadForSync(req, id) {
|
|
45158
|
+
if (!import_mongodb78.ObjectId.isValid(id))
|
|
45159
|
+
throw new import_node_server_utils122.NotFoundError("Camera not found.");
|
|
45160
|
+
const camera = await _findOne(
|
|
45161
|
+
{ _id: new import_mongodb78.ObjectId(id) },
|
|
45162
|
+
{
|
|
45163
|
+
site: 1,
|
|
45164
|
+
type: 1,
|
|
45165
|
+
host: 1,
|
|
45166
|
+
username: 1,
|
|
45167
|
+
password: 1,
|
|
45168
|
+
name: 1,
|
|
45169
|
+
direction: 1,
|
|
45170
|
+
status: 1
|
|
45171
|
+
}
|
|
45172
|
+
);
|
|
45173
|
+
if (!camera)
|
|
45174
|
+
throw new import_node_server_utils122.NotFoundError("Camera not found.");
|
|
45175
|
+
await authorizeWrite({
|
|
45176
|
+
req,
|
|
45177
|
+
site: camera.site,
|
|
45178
|
+
type: camera.type,
|
|
45179
|
+
notFound: "Camera not found."
|
|
45180
|
+
});
|
|
45181
|
+
if (String(camera.type ?? "").toLowerCase() !== "anpr") {
|
|
45182
|
+
throw new import_node_server_utils122.BadRequestError("Only ANPR cameras hold vehicle plates.");
|
|
45183
|
+
}
|
|
45184
|
+
if (String(camera.status ?? "active") !== "active") {
|
|
45185
|
+
throw new import_node_server_utils122.BadRequestError(
|
|
45186
|
+
"This camera is inactive. Switch it on before adding vehicles to it."
|
|
45187
|
+
);
|
|
45188
|
+
}
|
|
45189
|
+
const lists = listsFilledByDirectionChange("", camera.direction);
|
|
45190
|
+
if (!lists.allow && !lists.block) {
|
|
45191
|
+
throw new import_node_server_utils122.BadRequestError("Exit cameras receive no vehicle plates.");
|
|
45192
|
+
}
|
|
45193
|
+
return {
|
|
45194
|
+
site: String(camera.site ?? ""),
|
|
45195
|
+
device: {
|
|
45196
|
+
host: String(camera.host ?? ""),
|
|
45197
|
+
username: String(camera.username ?? ""),
|
|
45198
|
+
password: String(camera.password ?? ""),
|
|
45199
|
+
name: camera.name ?? "",
|
|
45200
|
+
direction: String(camera.direction ?? "")
|
|
45201
|
+
}
|
|
45202
|
+
};
|
|
45203
|
+
}
|
|
45204
|
+
async function cameraPlates(req, res, next) {
|
|
45205
|
+
try {
|
|
45206
|
+
const { site, device } = await loadForSync(req, String(req.params.id));
|
|
45207
|
+
const items = await useVehicleService().newCameraPlateRows(
|
|
45208
|
+
site,
|
|
45209
|
+
device.direction
|
|
45210
|
+
);
|
|
45211
|
+
res.json({ items });
|
|
45212
|
+
} catch (error) {
|
|
45213
|
+
next(error);
|
|
45214
|
+
}
|
|
45215
|
+
}
|
|
45216
|
+
async function clearCamera(req, res, next) {
|
|
45217
|
+
try {
|
|
45218
|
+
const { site, device } = await loadForSync(req, String(req.params.id));
|
|
45219
|
+
const cleared = await _clearPlateLists(
|
|
45220
|
+
site,
|
|
45221
|
+
device,
|
|
45222
|
+
{ allow: true, block: true },
|
|
45223
|
+
"sync"
|
|
45224
|
+
);
|
|
45225
|
+
res.json({
|
|
45226
|
+
message: "The camera's allowed list and blocklist were cleared.",
|
|
45227
|
+
clearedLists: cleared
|
|
45228
|
+
});
|
|
45229
|
+
} catch (error) {
|
|
45230
|
+
next(error);
|
|
45231
|
+
}
|
|
45232
|
+
}
|
|
45233
|
+
async function addCameraPlate(req, res, next) {
|
|
45234
|
+
const { error, value } = import_joi58.default.object({
|
|
45235
|
+
vehicle: import_joi58.default.string().hex().length(24).required(),
|
|
45236
|
+
list: import_joi58.default.string().valid("allowed", "blocked").required(),
|
|
45237
|
+
onlyIfMissing: import_joi58.default.boolean().optional().default(true)
|
|
45238
|
+
}).validate(req.body ?? {});
|
|
45239
|
+
if (error) {
|
|
45240
|
+
next(new import_node_server_utils122.BadRequestError(error.message));
|
|
45241
|
+
return;
|
|
45242
|
+
}
|
|
45243
|
+
try {
|
|
45244
|
+
const { site, device } = await loadForSync(req, String(req.params.id));
|
|
45245
|
+
const outcome = await useVehicleService().writeVehicleToNewCamera(
|
|
45246
|
+
site,
|
|
45247
|
+
device,
|
|
45248
|
+
value.vehicle,
|
|
45249
|
+
value.list,
|
|
45250
|
+
// Absent means look first: a saved camera may already hold the plate.
|
|
45251
|
+
{ onlyIfMissing: value.onlyIfMissing !== false }
|
|
45252
|
+
);
|
|
45253
|
+
res.json(outcome);
|
|
45254
|
+
} catch (error2) {
|
|
45255
|
+
next(error2);
|
|
45256
|
+
}
|
|
45257
|
+
}
|
|
44133
45258
|
return {
|
|
44134
45259
|
add,
|
|
44135
45260
|
addBulk,
|
|
44136
45261
|
getAll,
|
|
44137
45262
|
updateById,
|
|
44138
|
-
deleteById
|
|
45263
|
+
deleteById,
|
|
45264
|
+
newCameraPlates,
|
|
45265
|
+
clearNewCamera,
|
|
45266
|
+
addNewCameraPlate,
|
|
45267
|
+
cameraPlates,
|
|
45268
|
+
clearCamera,
|
|
45269
|
+
addCameraPlate
|
|
44139
45270
|
};
|
|
44140
45271
|
}
|
|
44141
45272
|
|
|
@@ -47732,7 +48863,7 @@ var VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
|
47732
48863
|
]);
|
|
47733
48864
|
|
|
47734
48865
|
// src/services/hid-amico.service.ts
|
|
47735
|
-
var
|
|
48866
|
+
var import_crypto4 = __toESM(require("crypto"));
|
|
47736
48867
|
var import_axios = __toESM(require("axios"));
|
|
47737
48868
|
var import_node_server_utils140 = require("@7365admin1/node-server-utils");
|
|
47738
48869
|
|
|
@@ -49687,7 +50818,7 @@ function stringifyHidJson(value) {
|
|
|
49687
50818
|
}
|
|
49688
50819
|
|
|
49689
50820
|
// src/utils/hid-secret.util.ts
|
|
49690
|
-
var
|
|
50821
|
+
var import_crypto3 = __toESM(require("crypto"));
|
|
49691
50822
|
var HID_SECRET_ENV = "HID_AMICO_SECRET";
|
|
49692
50823
|
var HID_LEGACY_SECRET_ENV = "ACCESS_TOKEN_SECRET";
|
|
49693
50824
|
var HID_SECRET_MISSING_FOR_WRITE = `${HID_SECRET_ENV} is not configured, so a new HID credential cannot be encrypted. Set ${HID_SECRET_ENV} on this service and try again. It is deliberately NOT falling back to ${HID_LEGACY_SECRET_ENV}: that key is rotated independently, and anything encrypted with it becomes permanently unreadable the moment it is.`;
|
|
@@ -49708,7 +50839,7 @@ function resolveHidSecretKey(purpose, env2 = process.env) {
|
|
|
49708
50839
|
return { key: deriveKey(legacy), usedLegacyFallback: true };
|
|
49709
50840
|
}
|
|
49710
50841
|
function deriveKey(secret) {
|
|
49711
|
-
return
|
|
50842
|
+
return import_crypto3.default.createHash("sha256").update(secret).digest();
|
|
49712
50843
|
}
|
|
49713
50844
|
|
|
49714
50845
|
// src/services/hid-amico.service.ts
|
|
@@ -49912,8 +51043,8 @@ function getPublicMonitorConfiguration(monitor, callbackToken) {
|
|
|
49912
51043
|
};
|
|
49913
51044
|
}
|
|
49914
51045
|
function encryptSecret(value) {
|
|
49915
|
-
const iv =
|
|
49916
|
-
const cipher =
|
|
51046
|
+
const iv = import_crypto4.default.randomBytes(12);
|
|
51047
|
+
const cipher = import_crypto4.default.createCipheriv("aes-256-gcm", getSecretKey("encrypt"), iv);
|
|
49917
51048
|
const encrypted = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
49918
51049
|
const tag = cipher.getAuthTag();
|
|
49919
51050
|
return [PASSWORD_PREFIX, iv.toString("hex"), tag.toString("hex"), encrypted.toString("hex")].join(":");
|
|
@@ -49925,7 +51056,7 @@ function decryptSecret(value) {
|
|
|
49925
51056
|
const key = getSecretKey("decrypt");
|
|
49926
51057
|
try {
|
|
49927
51058
|
const [, ivHex, tagHex, encryptedHex] = value.split(":");
|
|
49928
|
-
const decipher =
|
|
51059
|
+
const decipher = import_crypto4.default.createDecipheriv("aes-256-gcm", key, Buffer.from(ivHex, "hex"));
|
|
49929
51060
|
decipher.setAuthTag(Buffer.from(tagHex, "hex"));
|
|
49930
51061
|
return Buffer.concat([
|
|
49931
51062
|
decipher.update(Buffer.from(encryptedHex, "hex")),
|
|
@@ -49965,7 +51096,7 @@ function normalizeHidReaderBaseUrl(value) {
|
|
|
49965
51096
|
return endpoint.origin;
|
|
49966
51097
|
}
|
|
49967
51098
|
function createStableHidId(seed) {
|
|
49968
|
-
const bytes =
|
|
51099
|
+
const bytes = import_crypto4.default.createHash("sha256").update(seed).digest().subarray(0, 6);
|
|
49969
51100
|
return Number.parseInt(bytes.toString("hex"), 16) || 1;
|
|
49970
51101
|
}
|
|
49971
51102
|
function isUnknownRecord2(value) {
|
|
@@ -50040,7 +51171,7 @@ function resolveProfileIdentityType(memberType, userType) {
|
|
|
50040
51171
|
var VISITOR_QR_FORMAT = "0";
|
|
50041
51172
|
function createVisitorQrCredential(visitorId, readerId, qrValue) {
|
|
50042
51173
|
const credentialId = createStableHidId(`visitor-qr:${readerId}:${visitorId}`);
|
|
50043
|
-
const token = qrValue || `VIS${
|
|
51174
|
+
const token = qrValue || `VIS${import_crypto4.default.randomBytes(12).toString("hex").toUpperCase()}`;
|
|
50044
51175
|
return { credentialId, object: "qrcodes", value: token, qrValue: token };
|
|
50045
51176
|
}
|
|
50046
51177
|
async function configureVisitorQrReader(client) {
|
|
@@ -50544,7 +51675,7 @@ function getSipProvisioningConfig() {
|
|
|
50544
51675
|
return { provisionerUrl, provisionerToken, websocketUrl, domain };
|
|
50545
51676
|
}
|
|
50546
51677
|
function createSipPassword() {
|
|
50547
|
-
return
|
|
51678
|
+
return import_crypto4.default.randomBytes(12).toString("base64url");
|
|
50548
51679
|
}
|
|
50549
51680
|
function sipAccountResponse(account) {
|
|
50550
51681
|
const { websocketUrl, domain } = getSipProvisioningConfig();
|
|
@@ -53224,7 +54355,7 @@ function useHidAmicoService() {
|
|
|
53224
54355
|
assertReaderCardProfile(reader, cardType);
|
|
53225
54356
|
const hidUserId = Number(hidUserIdValue);
|
|
53226
54357
|
const client = new HidAmicoClient(reader);
|
|
53227
|
-
const lockOwner =
|
|
54358
|
+
const lockOwner = import_crypto4.default.randomUUID();
|
|
53228
54359
|
const acquired = await repo.acquireEnrollmentLock(readerId, lockOwner, timeoutSeconds + 30);
|
|
53229
54360
|
if (!acquired) {
|
|
53230
54361
|
throw new import_node_server_utils140.BadRequestError("Another physical HID card enrollment is already running on this reader.");
|
|
@@ -55506,17 +56637,17 @@ var import_fs5 = __toESM(require("fs"));
|
|
|
55506
56637
|
var import_path2 = __toESM(require("path"));
|
|
55507
56638
|
var import_axios2 = __toESM(require("axios"));
|
|
55508
56639
|
var import_xml2js = require("xml2js");
|
|
55509
|
-
var
|
|
56640
|
+
var import_crypto5 = __toESM(require("crypto"));
|
|
55510
56641
|
var ALGORITHM = "aes-256-gcm";
|
|
55511
|
-
var SECRET_KEY =
|
|
55512
|
-
new Uint8Array(
|
|
56642
|
+
var SECRET_KEY = import_crypto5.default.createSecretKey(
|
|
56643
|
+
new Uint8Array(import_crypto5.default.createHash("sha256").update("acm-secret-key").digest())
|
|
55513
56644
|
);
|
|
55514
56645
|
function decryptAcmUrl(encryptedText) {
|
|
55515
56646
|
const [ivB64, authTagB64, encryptedB64] = encryptedText.split(":");
|
|
55516
56647
|
const iv = Buffer.from(ivB64, "base64");
|
|
55517
56648
|
const authTag = Buffer.from(authTagB64, "base64");
|
|
55518
56649
|
const encrypted = Buffer.from(encryptedB64, "base64");
|
|
55519
|
-
const decipher =
|
|
56650
|
+
const decipher = import_crypto5.default.createDecipheriv(
|
|
55520
56651
|
ALGORITHM,
|
|
55521
56652
|
SECRET_KEY,
|
|
55522
56653
|
new Uint8Array(iv)
|
|
@@ -55659,10 +56790,10 @@ async function getTransactions(index, url) {
|
|
|
55659
56790
|
}
|
|
55660
56791
|
function encryptPayload(payloadHex) {
|
|
55661
56792
|
const buffer = Buffer.from(payloadHex, "ascii");
|
|
55662
|
-
const encrypted =
|
|
56793
|
+
const encrypted = import_crypto5.default.publicEncrypt(
|
|
55663
56794
|
{
|
|
55664
56795
|
key: "testing",
|
|
55665
|
-
padding:
|
|
56796
|
+
padding: import_crypto5.default.constants.RSA_PKCS1_PADDING
|
|
55666
56797
|
},
|
|
55667
56798
|
buffer
|
|
55668
56799
|
);
|
|
@@ -55672,7 +56803,7 @@ function toHex(num, length) {
|
|
|
55672
56803
|
return num.toString(16).padStart(length * 2, "0");
|
|
55673
56804
|
}
|
|
55674
56805
|
function buildPayload(cardNumber, validityMinutes) {
|
|
55675
|
-
const randomHex =
|
|
56806
|
+
const randomHex = import_crypto5.default.randomBytes(2).toString("hex");
|
|
55676
56807
|
const cardHex = toHex(cardNumber, 4);
|
|
55677
56808
|
const epochHex = toHex(Math.floor(Date.now() / 1e3), 4);
|
|
55678
56809
|
const validityHex = toHex(validityMinutes, 2);
|
|
@@ -60615,6 +61746,21 @@ var import_joi72 = __toESM(require("joi"));
|
|
|
60615
61746
|
var import_node_server_utils148 = require("@7365admin1/node-server-utils");
|
|
60616
61747
|
var import_node_server_utils149 = require("@7365admin1/node-server-utils");
|
|
60617
61748
|
|
|
61749
|
+
// src/utils/people-create-gate.util.ts
|
|
61750
|
+
var PEOPLE_CREATE_REACH_MODE_DEFAULT = "log";
|
|
61751
|
+
function peopleCreateReachMode(env2 = process.env) {
|
|
61752
|
+
return gateMode(
|
|
61753
|
+
env2.PEOPLE_CREATE_REACH_MODE,
|
|
61754
|
+
["log", "enforce", "off"],
|
|
61755
|
+
PEOPLE_CREATE_REACH_MODE_DEFAULT
|
|
61756
|
+
);
|
|
61757
|
+
}
|
|
61758
|
+
function peopleCreateVerdict(params) {
|
|
61759
|
+
if (params.mode === "off" || params.reachable)
|
|
61760
|
+
return { refuse: false, record: false };
|
|
61761
|
+
return { refuse: params.mode === "enforce", record: true };
|
|
61762
|
+
}
|
|
61763
|
+
|
|
60618
61764
|
// src/utils/self-signup-files.util.ts
|
|
60619
61765
|
function dropUnsentFiles(body) {
|
|
60620
61766
|
if (!Array.isArray(body?.files))
|
|
@@ -60723,6 +61869,41 @@ function usePersonController() {
|
|
|
60723
61869
|
next(new import_node_server_utils148.BadRequestError(messages));
|
|
60724
61870
|
return;
|
|
60725
61871
|
}
|
|
61872
|
+
try {
|
|
61873
|
+
const mode = peopleCreateReachMode();
|
|
61874
|
+
if (mode !== "off") {
|
|
61875
|
+
const actor = await resolveInviteActor(callerId(req));
|
|
61876
|
+
let reachable = true;
|
|
61877
|
+
if (!staffMayBypass(actor)) {
|
|
61878
|
+
const site = payload?.site ? String(payload.site) : "";
|
|
61879
|
+
const org = payload?.org ? String(payload.org) : "";
|
|
61880
|
+
if (site) {
|
|
61881
|
+
reachable = await useCameraViewService().entitleSite({ siteId: site, userId: actor.id }).then(
|
|
61882
|
+
() => true,
|
|
61883
|
+
() => false
|
|
61884
|
+
);
|
|
61885
|
+
} else if (org) {
|
|
61886
|
+
reachable = actor.orgIds.includes(org);
|
|
61887
|
+
}
|
|
61888
|
+
}
|
|
61889
|
+
const verdict = peopleCreateVerdict({ mode, reachable });
|
|
61890
|
+
if (verdict.record) {
|
|
61891
|
+
import_node_server_utils148.logger.log({
|
|
61892
|
+
level: "info",
|
|
61893
|
+
message: `[people-create] ${verdict.refuse ? "refused" : "would-refuse"} caller=${actor.id} site=${payload?.site ?? ""} org=${payload?.org ?? ""} mode=${mode}`
|
|
61894
|
+
});
|
|
61895
|
+
}
|
|
61896
|
+
if (verdict.refuse) {
|
|
61897
|
+
next(new import_node_server_utils149.UnauthorizedError("Not authorized."));
|
|
61898
|
+
return;
|
|
61899
|
+
}
|
|
61900
|
+
}
|
|
61901
|
+
} catch (gateError) {
|
|
61902
|
+
import_node_server_utils148.logger.log({
|
|
61903
|
+
level: "warn",
|
|
61904
|
+
message: `[people-create] reach check failed open: ${gateError?.message}`
|
|
61905
|
+
});
|
|
61906
|
+
}
|
|
60726
61907
|
try {
|
|
60727
61908
|
const data = await _add(payload);
|
|
60728
61909
|
res.status(201).json({ message: data });
|
|
@@ -86987,7 +88168,7 @@ function useRedDotPaymentController() {
|
|
|
86987
88168
|
|
|
86988
88169
|
// src/services/verification-v2.service.ts
|
|
86989
88170
|
var import_node_server_utils271 = require("@7365admin1/node-server-utils");
|
|
86990
|
-
var
|
|
88171
|
+
var import_crypto6 = __toESM(require("crypto"));
|
|
86991
88172
|
var import_joi142 = __toESM(require("joi"));
|
|
86992
88173
|
|
|
86993
88174
|
// src/services/auth-v2.service.ts
|
|
@@ -87685,7 +88866,7 @@ function useVerificationServiceV2() {
|
|
|
87685
88866
|
}
|
|
87686
88867
|
function generateVerificationCode(length = 8) {
|
|
87687
88868
|
const chars = "0123456789";
|
|
87688
|
-
const bytes =
|
|
88869
|
+
const bytes = import_crypto6.default.randomBytes(length);
|
|
87689
88870
|
let result = "";
|
|
87690
88871
|
for (let i = 0; i < length; i++) {
|
|
87691
88872
|
result += chars[bytes[i] % chars.length];
|
|
@@ -95518,6 +96699,7 @@ function usePersonalEmergencyChainController() {
|
|
|
95518
96699
|
cameraGrant,
|
|
95519
96700
|
cameraHealthClaim,
|
|
95520
96701
|
cameraHealthSummary,
|
|
96702
|
+
cameraListClearConfirmation,
|
|
95521
96703
|
cameraManagePermissions,
|
|
95522
96704
|
cameraProbeCacheKey,
|
|
95523
96705
|
cameraTransports,
|
|
@@ -95581,6 +96763,7 @@ function usePersonalEmergencyChainController() {
|
|
|
95581
96763
|
incidents_namespace_collection,
|
|
95582
96764
|
invitationOffersRole,
|
|
95583
96765
|
isCameraEntitled,
|
|
96766
|
+
isDuplicateMemberError,
|
|
95584
96767
|
isDuplicateVersionError,
|
|
95585
96768
|
isLikelySelfServiceEmail,
|
|
95586
96769
|
isPatrolCctvCamera,
|