@7365admin1/core 2.50.0 → 2.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4333,6 +4333,9 @@ function useVerificationRepo() {
4333
4333
  return Promise.reject(error);
4334
4334
  }
4335
4335
  }
4336
+ async function findOne(query) {
4337
+ return await collection.findOne(query);
4338
+ }
4336
4339
  return {
4337
4340
  createIndex,
4338
4341
  createTextIndex,
@@ -4341,7 +4344,8 @@ function useVerificationRepo() {
4341
4344
  getVerifications,
4342
4345
  getByIdByType,
4343
4346
  updateStatusById,
4344
- getByStatus
4347
+ getByStatus,
4348
+ findOne
4345
4349
  };
4346
4350
  }
4347
4351
 
@@ -5645,29 +5649,28 @@ function useVerificationService() {
5645
5649
  throw error;
5646
5650
  }
5647
5651
  }
5648
- async function createSimpleUserInvite({
5649
- email,
5650
- metadata
5651
- }) {
5652
+ async function createSimpleUserInvite({ email, metadata }) {
5652
5653
  const type = "user-invite";
5654
+ if (metadata?.org)
5655
+ await getOrgById(metadata.org);
5656
+ if (metadata?.siteId)
5657
+ await getSiteById(metadata.siteId);
5658
+ const existing = await useVerificationRepo().findOne({
5659
+ type,
5660
+ email,
5661
+ "metadata.org": metadata?.org,
5662
+ "metadata.siteId": metadata?.siteId
5663
+ });
5664
+ if (existing)
5665
+ return existing._id;
5653
5666
  const value = {
5654
5667
  type,
5655
5668
  email,
5656
5669
  metadata,
5657
- expireAt: new Date(
5658
- (/* @__PURE__ */ new Date()).getTime() + 72 * 60 * 60 * 1e3
5659
- ).toISOString(),
5670
+ expireAt: new Date(Date.now() + 72 * 60 * 60 * 1e3).toISOString(),
5660
5671
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
5661
5672
  };
5662
- if (value.metadata?.org) {
5663
- await getOrgById(value.metadata?.org);
5664
- }
5665
- if (value.metadata?.siteId) {
5666
- await getSiteById(value.metadata?.siteId);
5667
- }
5668
5673
  const res = await add(value);
5669
- const dir = __dirname;
5670
- const filePath = (0, import_node_server_utils21.getDirectory)(dir, "./public/handlebars/user-invite");
5671
5674
  const link = `${APP_MAIN}/verify/invitation/${res}`;
5672
5675
  const emailContent = (0, import_node_server_utils21.compileHandlebar)({
5673
5676
  context: {
@@ -5675,9 +5678,9 @@ function useVerificationService() {
5675
5678
  validity: VERIFICATION_USER_INVITE_DURATION,
5676
5679
  link
5677
5680
  },
5678
- filePath
5681
+ filePath: (0, import_node_server_utils21.getDirectory)(__dirname, "./public/handlebars/user-invite")
5679
5682
  });
5680
- mailer.sendMail({
5683
+ await mailer.sendMail({
5681
5684
  to: email,
5682
5685
  subject: "User Invite",
5683
5686
  html: emailContent,
@@ -7504,7 +7507,7 @@ function useMemberService() {
7504
7507
  getByRoles
7505
7508
  } = useMemberRepo();
7506
7509
  const { getById: _getVerificationById, updateStatusById } = useVerificationRepo();
7507
- const { getUserByEmail, updateDefaultOrgByEmail } = useUserRepo();
7510
+ const { getUserByEmail, updateDefaultOrgByEmail, getUserById } = useUserRepo();
7508
7511
  const { getById: getOrgById } = useOrgRepo();
7509
7512
  const { getSiteById } = useSiteRepo();
7510
7513
  const { getOwnerRolesByTypeOrg } = useRoleRepo();
@@ -7566,6 +7569,52 @@ function useMemberService() {
7566
7569
  session?.endSession();
7567
7570
  }
7568
7571
  }
7572
+ async function createMemberDirect({
7573
+ userId,
7574
+ orgId,
7575
+ roleId,
7576
+ app,
7577
+ siteId,
7578
+ siteName
7579
+ }) {
7580
+ const session = import_node_server_utils30.useAtlas.getClient()?.startSession();
7581
+ session?.startTransaction();
7582
+ try {
7583
+ const org = await getOrgById(orgId);
7584
+ if (!org)
7585
+ throw new import_node_server_utils30.BadRequestError("Organization not found.");
7586
+ const user = await getUserById(userId);
7587
+ if (!user)
7588
+ throw new import_node_server_utils30.BadRequestError("User not found.");
7589
+ const member = await addMember(
7590
+ {
7591
+ org: org._id?.toString() || "",
7592
+ orgName: org.name || "",
7593
+ user: user._id?.toString() || "",
7594
+ name: user?.email,
7595
+ role: roleId,
7596
+ type: app,
7597
+ siteId: siteId ?? "",
7598
+ siteName: siteName ?? ""
7599
+ },
7600
+ session
7601
+ );
7602
+ if (!user.defaultOrg) {
7603
+ await updateDefaultOrgByEmail(
7604
+ user.email,
7605
+ org._id?.toString() || "",
7606
+ session
7607
+ );
7608
+ }
7609
+ await session?.commitTransaction();
7610
+ return { member };
7611
+ } catch (error) {
7612
+ await session?.abortTransaction();
7613
+ throw error;
7614
+ } finally {
7615
+ session?.endSession();
7616
+ }
7617
+ }
7569
7618
  async function updateRoleById(id, role, type, org) {
7570
7619
  const owner = await getOwnerRolesByTypeOrg(type, org);
7571
7620
  if (!owner.length) {
@@ -7593,6 +7642,7 @@ function useMemberService() {
7593
7642
  }
7594
7643
  return {
7595
7644
  createMember,
7645
+ createMemberDirect,
7596
7646
  updateRoleById
7597
7647
  };
7598
7648
  }
@@ -7607,7 +7657,7 @@ function useMemberController() {
7607
7657
  updateMemberStatus: _updateMemberStatus,
7608
7658
  updateStatusByUserId: _updateStatusByUserId
7609
7659
  } = useMemberRepo();
7610
- const { createMember: _createMember, updateRoleById: _updateRoleById } = useMemberService();
7660
+ const { createMember: _createMember, createMemberDirect: _createMemberDirect, updateRoleById: _updateRoleById } = useMemberService();
7611
7661
  async function createMember(req, res, next) {
7612
7662
  const validation = import_joi15.default.string().hex().required();
7613
7663
  const _id = req.params.id;
@@ -7796,6 +7846,32 @@ function useMemberController() {
7796
7846
  return;
7797
7847
  }
7798
7848
  }
7849
+ async function createMemberDirect(req, res, next) {
7850
+ const validation = import_joi15.default.object({
7851
+ userId: import_joi15.default.string().hex().required(),
7852
+ orgId: import_joi15.default.string().hex().required(),
7853
+ roleId: import_joi15.default.string().hex().required(),
7854
+ app: import_joi15.default.string().required(),
7855
+ siteId: import_joi15.default.string().hex().optional().allow("", null),
7856
+ siteName: import_joi15.default.string().optional().allow("", null)
7857
+ });
7858
+ const { error } = validation.validate(req.body);
7859
+ if (error) {
7860
+ import_node_server_utils31.logger.log({ level: "error", message: error.message });
7861
+ next(new import_node_server_utils31.BadRequestError(error.message));
7862
+ return;
7863
+ }
7864
+ const { userId, orgId, roleId, app, siteId, siteName } = req.body;
7865
+ try {
7866
+ const data = await _createMemberDirect({ userId, orgId, roleId, app, siteId, siteName });
7867
+ res.status(201).json(data);
7868
+ return;
7869
+ } catch (error2) {
7870
+ import_node_server_utils31.logger.log({ level: "error", message: error2.message });
7871
+ next(error2);
7872
+ return;
7873
+ }
7874
+ }
7799
7875
  return {
7800
7876
  createMember,
7801
7877
  getByUserId,
@@ -7803,7 +7879,8 @@ function useMemberController() {
7803
7879
  getAll,
7804
7880
  getOrgsByMembership,
7805
7881
  updateMemberStatus,
7806
- updateRoleById
7882
+ updateRoleById,
7883
+ createMemberDirect
7807
7884
  };
7808
7885
  }
7809
7886
 
@@ -13236,23 +13313,42 @@ var CameraType = /* @__PURE__ */ ((CameraType2) => {
13236
13313
  })(CameraType || {});
13237
13314
  var schemaUpdateSiteCamera = import_joi34.default.object({
13238
13315
  host: import_joi34.default.string().required(),
13239
- username: import_joi34.default.string().required(),
13240
- password: import_joi34.default.string().required(),
13316
+ username: import_joi34.default.string().when("type", {
13317
+ is: "anpr",
13318
+ then: import_joi34.default.required(),
13319
+ otherwise: import_joi34.default.optional().allow(null, "")
13320
+ }),
13321
+ password: import_joi34.default.string().when("type", {
13322
+ is: "anpr",
13323
+ then: import_joi34.default.required(),
13324
+ otherwise: import_joi34.default.optional().allow(null, "")
13325
+ }),
13241
13326
  category: import_joi34.default.string().allow("standard", "resident", "visitor").required(),
13242
13327
  guardPost: import_joi34.default.number().integer().optional(),
13243
- direction: import_joi34.default.string().optional().allow("entry", "exit", "both", "none")
13328
+ direction: import_joi34.default.string().optional().allow("entry", "exit", "both", "none"),
13329
+ status: import_joi34.default.string().optional().allow("active", "inactive", "deleted"),
13330
+ name: import_joi34.default.string().optional().allow(null, "")
13244
13331
  });
13245
13332
  var schemaSiteCamera = import_joi34.default.object({
13246
13333
  _id: import_joi34.default.string().hex().optional(),
13247
13334
  site: import_joi34.default.string().hex().required(),
13248
13335
  host: import_joi34.default.string().required(),
13249
- username: import_joi34.default.string().required(),
13250
- password: import_joi34.default.string().required(),
13336
+ username: import_joi34.default.string().when("type", {
13337
+ is: "anpr",
13338
+ then: import_joi34.default.required(),
13339
+ otherwise: import_joi34.default.optional().allow(null, "")
13340
+ }),
13341
+ password: import_joi34.default.string().when("type", {
13342
+ is: "anpr",
13343
+ then: import_joi34.default.required(),
13344
+ otherwise: import_joi34.default.optional().allow(null, "")
13345
+ }),
13251
13346
  type: import_joi34.default.string().allow("ip", "anpr").required(),
13252
13347
  category: import_joi34.default.string().allow("standard", "resident", "visitor").required(),
13253
13348
  direction: import_joi34.default.string().required().allow("entry", "exit", "both", "none"),
13254
13349
  guardPost: import_joi34.default.number().integer().optional(),
13255
13350
  status: import_joi34.default.string().optional().allow("active", "inactive", "deleted"),
13351
+ name: import_joi34.default.string().optional().allow(null, ""),
13256
13352
  createdAt: import_joi34.default.date().optional(),
13257
13353
  updatedAt: import_joi34.default.date().optional(),
13258
13354
  deletedAt: import_joi34.default.date().optional()
@@ -13281,6 +13377,7 @@ function MSiteCamera(value) {
13281
13377
  guardPost: value.guardPost ?? 0,
13282
13378
  status: value.status ?? "active",
13283
13379
  direction: value.direction ?? "",
13380
+ name: value.name ?? "",
13284
13381
  createdAt: value.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
13285
13382
  updatedAt: value.updatedAt ?? "",
13286
13383
  deletedAt: value.deletedAt ?? ""
@@ -13595,7 +13692,24 @@ var schemaUpdateVisTrans = import_joi36.default.object({
13595
13692
  checkInRemarks: import_joi36.default.string().optional().allow("", null),
13596
13693
  checkOutRemarks: import_joi36.default.string().optional().allow("", null),
13597
13694
  expectedCheckIn: import_joi36.default.string().isoDate().optional(),
13598
- purpose: import_joi36.default.string().optional().allow(null, "")
13695
+ purpose: import_joi36.default.string().optional().allow(null, ""),
13696
+ members: import_joi36.default.array().items(
13697
+ import_joi36.default.object({
13698
+ name: import_joi36.default.string().optional().allow(null, ""),
13699
+ nric: import_joi36.default.string().optional().allow(null, ""),
13700
+ visitorPass: import_joi36.default.array().items(
13701
+ import_joi36.default.object({
13702
+ keyId: import_joi36.default.string().hex().length(24).required()
13703
+ })
13704
+ ).optional().allow(null),
13705
+ passKeys: import_joi36.default.array().items(
13706
+ import_joi36.default.object({
13707
+ keyId: import_joi36.default.string().hex().length(24).required()
13708
+ })
13709
+ ).optional().allow(null),
13710
+ contact: import_joi36.default.string().optional().allow(null, "")
13711
+ })
13712
+ ).optional().allow(null)
13599
13713
  });
13600
13714
  function MVisitorTransaction(value) {
13601
13715
  const { error } = schemaVisitorTransaction.validate(value, {
@@ -14045,14 +14159,14 @@ function useVisitorTransactionRepo() {
14045
14159
  );
14046
14160
  }
14047
14161
  }
14048
- async function updateById(_id, value, session, isNotManualCheckOut = true) {
14162
+ async function updateById(_id, value, session, isManualCheckOut = true) {
14049
14163
  try {
14050
14164
  _id = new import_mongodb40.ObjectId(_id);
14051
14165
  } catch (error) {
14052
14166
  throw new import_node_server_utils68.BadRequestError("Invalid visitor transaction ID format.");
14053
14167
  }
14054
14168
  value.updatedAt = /* @__PURE__ */ new Date();
14055
- if (value.checkOut && isNotManualCheckOut) {
14169
+ if (value.checkOut && isManualCheckOut) {
14056
14170
  value.manualCheckout = true;
14057
14171
  }
14058
14172
  try {
@@ -17475,7 +17589,7 @@ function useDahuaService() {
17475
17589
  );
17476
17590
  }
17477
17591
  }
17478
- if (["entry", "both"].includes(designation) && direction.toLowerCase() === "approach" && plateNumber) {
17592
+ if (["entry"].includes(designation) && plateNumber) {
17479
17593
  await closeOpenTransaction(plateNumber);
17480
17594
  const vehicle = await getVehicleByPlateNumber(plateNumber);
17481
17595
  const visitorTransaction = {
@@ -17530,11 +17644,11 @@ function useDahuaService() {
17530
17644
  } catch (error) {
17531
17645
  console.log("failed to create visitor transaction", error);
17532
17646
  loggerDahua.error(
17533
- `[${site}][${gate}] Error creating visitor transaction:`,
17647
+ `[${site}][${host}] Error creating visitor transaction:`,
17534
17648
  error
17535
17649
  );
17536
17650
  }
17537
- } else if (["exit", "both"].includes(designation) && direction.toLowerCase() === "leave" && plateNumber) {
17651
+ } else if (["exit"].includes(designation) && plateNumber) {
17538
17652
  const vehicle = await getVehicleByPlateNumber(plateNumber);
17539
17653
  const existingOpenTransaction = await getOpenByPlateNumber(
17540
17654
  plateNumber,
@@ -17559,6 +17673,92 @@ function useDahuaService() {
17559
17673
  if (onDetected2) {
17560
17674
  onDetected2({ reload: true, site: existingOpenTransaction?.site?.toString(), host, direction });
17561
17675
  }
17676
+ } else if (["both"].includes(designation) && plateNumber) {
17677
+ if (direction.toLowerCase() === "leave") {
17678
+ const vehicle = await getVehicleByPlateNumber(plateNumber);
17679
+ const existingOpenTransaction = await getOpenByPlateNumber(
17680
+ plateNumber,
17681
+ site
17682
+ );
17683
+ if (existingOpenTransaction?._id) {
17684
+ currentTransactionId = existingOpenTransaction._id.toString();
17685
+ currentSnapshotField = "snapshotExitImage";
17686
+ }
17687
+ if (vehicle?.recNo) {
17688
+ const dahuaPayload = {
17689
+ host,
17690
+ username,
17691
+ password,
17692
+ plateNumber,
17693
+ mode: "TrafficRedList" /* TRAFFIC_REDLIST */,
17694
+ recno: vehicle?.recNo
17695
+ };
17696
+ await removePlateNumber(dahuaPayload);
17697
+ }
17698
+ await closeOpenTransaction(plateNumber);
17699
+ if (onDetected2 && existingOpenTransaction?.site) {
17700
+ onDetected2({ reload: true, site: existingOpenTransaction?.site?.toString(), host, direction });
17701
+ }
17702
+ } else if (direction.toLowerCase() === "approach") {
17703
+ await closeOpenTransaction(plateNumber);
17704
+ const vehicle = await getVehicleByPlateNumber(plateNumber);
17705
+ const visitorTransaction = {
17706
+ site,
17707
+ plateNumber,
17708
+ org,
17709
+ status: "unregistered" /* UNREGISTERED */
17710
+ };
17711
+ const typesForAutoRegister = Object.values(PersonTypes);
17712
+ let startDate = vehicle?.start ? new Date(vehicle?.start) : /* @__PURE__ */ new Date();
17713
+ let endDate = vehicle?.end ? new Date(vehicle?.end) : new Date(startDate.getTime() + 60 * 60 * 1e3);
17714
+ if (vehicle && typeof vehicle.category === "string" && typesForAutoRegister.includes(vehicle.category)) {
17715
+ visitorTransaction.name = vehicle.name;
17716
+ visitorTransaction.nric = vehicle.nric;
17717
+ visitorTransaction.contact = vehicle.phoneNumber;
17718
+ visitorTransaction.block = Number(vehicle.block);
17719
+ visitorTransaction.level = vehicle.level;
17720
+ visitorTransaction.unit = vehicle.unit?.toString();
17721
+ visitorTransaction.unitName = vehicle.unitName;
17722
+ visitorTransaction.type = vehicle.category;
17723
+ visitorTransaction.status = "registered" /* REGISTERED */;
17724
+ visitorTransaction.recNo = vehicle.recNo;
17725
+ visitorTransaction.expiredAt = vehicle.end;
17726
+ }
17727
+ const dahuaPayload = {
17728
+ host,
17729
+ username,
17730
+ password,
17731
+ plateNumber,
17732
+ mode: "TrafficRedList" /* TRAFFIC_REDLIST */,
17733
+ owner: vehicle?.name ?? ""
17734
+ };
17735
+ dahuaPayload.start = formatDahuaDate(startDate);
17736
+ dahuaPayload.end = formatDahuaDate(endDate);
17737
+ const shouldHaveTimeLimit = visitorTransaction.type === "guest" /* GUEST */ || visitorTransaction.status === "unregistered" /* UNREGISTERED */;
17738
+ if (shouldHaveTimeLimit) {
17739
+ const startDate2 = /* @__PURE__ */ new Date();
17740
+ const endDate2 = new Date(startDate2.getTime() + 60 * 60 * 1e3);
17741
+ dahuaPayload.start = formatDahuaDate(startDate2);
17742
+ dahuaPayload.end = formatDahuaDate(endDate2);
17743
+ visitorTransaction.expiredAt = endDate2.toISOString();
17744
+ }
17745
+ try {
17746
+ await addPlateNumber(dahuaPayload);
17747
+ const result = await add(visitorTransaction, void 0, true);
17748
+ const transactionId = result?._id;
17749
+ currentTransactionId = transactionId?.toString();
17750
+ currentSnapshotField = "snapshotEntryImage";
17751
+ if (onDetected2) {
17752
+ onDetected2({ _id: transactionId, site: result?.site?.toString(), plateNumber: result.plateNumber, host, direction });
17753
+ }
17754
+ } catch (error) {
17755
+ console.log("failed to create visitor transaction", error);
17756
+ loggerDahua.error(
17757
+ `[${site}][${host}] Error creating visitor transaction:`,
17758
+ error
17759
+ );
17760
+ }
17761
+ }
17562
17762
  }
17563
17763
  }
17564
17764
  }
@@ -17690,6 +17890,9 @@ function useDahuaService() {
17690
17890
  }
17691
17891
  }
17692
17892
  async function listenToCamera(camera, onDetected) {
17893
+ if (camera?.type != "anpr" && camera?.status != "deleted") {
17894
+ return;
17895
+ }
17693
17896
  if (onDetected) {
17694
17897
  _savedOnDetected = onDetected;
17695
17898
  }
@@ -17698,6 +17901,14 @@ function useDahuaService() {
17698
17901
  return;
17699
17902
  }
17700
17903
  const cameraId = camera._id.toString();
17904
+ if (camera.status !== "active") {
17905
+ if (cameraRegistry.has(cameraId)) {
17906
+ loggerDahua.info(`[${camera?.host ?? camera?._id}] Camera is ${camera.status}. Stopping connection.`);
17907
+ cameraRegistry.get(cameraId)?.abort();
17908
+ cameraRegistry.delete(cameraId);
17909
+ }
17910
+ return;
17911
+ }
17701
17912
  if (cameraRegistry.has(cameraId)) {
17702
17913
  loggerDahua.info(`[${camera.host}] Stopping existing connection for update.`);
17703
17914
  cameraRegistry.get(cameraId)?.abort();
@@ -17845,6 +18056,9 @@ function useDahuaService() {
17845
18056
  continue;
17846
18057
  } else if ([400, 401, 403, 500].includes(response.statusCode)) {
17847
18058
  loggerDahua.error(`[${host}] Connection error: ${response.statusCode}`);
18059
+ if (onDetected) {
18060
+ onDetected({ site, messagePermanent: `Camera with last 3 characters ${host?.slice(-3)} Connection Error. Check username and password.` });
18061
+ }
17848
18062
  return;
17849
18063
  }
17850
18064
  loggerDahua.info(`[${host}] Successfully connected to ANPR.`);
@@ -17897,8 +18111,15 @@ function useDahuaService() {
17897
18111
  break;
17898
18112
  }
17899
18113
  loggerDahua.error(
17900
- `[${host}] Connection lost or error: ${error.message || error}. Retrying in 5s...`
18114
+ `[${host}] Connection lost or error: ${error.message || error}. Retrying in 10 seconds...`
17901
18115
  );
18116
+ if (onDetected) {
18117
+ onDetected({
18118
+ site,
18119
+ host,
18120
+ message: `Camera in ${gate} Connection Lost. Retrying in 10 seconds. Check the camera if this issue persists, Or set the camera to Inactive to stop this notification.`
18121
+ });
18122
+ }
17902
18123
  } finally {
17903
18124
  if (bufferQueue?.destroy) {
17904
18125
  try {
@@ -17919,7 +18140,7 @@ function useDahuaService() {
17919
18140
  }
17920
18141
  }
17921
18142
  if (!signal.aborted) {
17922
- await new Promise((res) => setTimeout(res, 5e3));
18143
+ await new Promise((res) => setTimeout(res, 1e4));
17923
18144
  }
17924
18145
  }
17925
18146
  loggerDahua.info(`[${host}] ANPR Listener stopped.`);
@@ -18421,6 +18642,8 @@ function useSiteCameraRepo() {
18421
18642
  }
18422
18643
  try {
18423
18644
  await collection.deleteOne({ _id }, { session });
18645
+ const { listenToCamera } = useDahuaService();
18646
+ await listenToCamera({ _id, status: "deleted" });
18424
18647
  delCachedData();
18425
18648
  return "Successfully deleted site camera.";
18426
18649
  } catch (error) {
@@ -19258,7 +19481,7 @@ function useBuildingRepo() {
19258
19481
  }
19259
19482
  }
19260
19483
  }
19261
- async function getBuildingLevel(site, block) {
19484
+ async function getBuildingLevel(site, block, isVMS) {
19262
19485
  try {
19263
19486
  site = new import_mongodb49.ObjectId(site);
19264
19487
  } catch (error) {
@@ -19268,8 +19491,7 @@ function useBuildingRepo() {
19268
19491
  site,
19269
19492
  block
19270
19493
  };
19271
- const cacheOptions = { ...query };
19272
- const cacheKey = (0, import_node_server_utils82.makeCacheKey)(buildings_namespace_collection, cacheOptions);
19494
+ const cacheKey = (0, import_node_server_utils82.makeCacheKey)(buildings_namespace_collection, { ...query, isVMS });
19273
19495
  try {
19274
19496
  const cached = await getCache(cacheKey);
19275
19497
  if (cached) {
@@ -19279,7 +19501,24 @@ function useBuildingRepo() {
19279
19501
  });
19280
19502
  return cached;
19281
19503
  }
19282
- const result = await collection.findOne(query);
19504
+ const pipeline = [{ $match: { ...query } }];
19505
+ if (isVMS) {
19506
+ pipeline.push({
19507
+ $lookup: {
19508
+ from: "building-levels",
19509
+ let: { id: "$_id" },
19510
+ pipeline: [
19511
+ {
19512
+ $match: {
19513
+ $expr: { $eq: ["$block", "$$id"] }
19514
+ }
19515
+ }
19516
+ ],
19517
+ as: "building_levels"
19518
+ }
19519
+ });
19520
+ }
19521
+ const result = await collection.aggregate(pipeline).toArray();
19283
19522
  setCache(cacheKey, result, 300).then(() => {
19284
19523
  import_node_server_utils82.logger.log({
19285
19524
  level: "info",
@@ -19467,6 +19706,9 @@ function useBuildingService() {
19467
19706
  // src/controllers/building.controller.ts
19468
19707
  var import_node_server_utils84 = require("@7365admin1/node-server-utils");
19469
19708
  var import_joi44 = __toESM(require("joi"));
19709
+ var import_exceljs = __toESM(require("exceljs"));
19710
+ var import_csv_parser = __toESM(require("csv-parser"));
19711
+ var import_fs2 = __toESM(require("fs"));
19470
19712
  function useBuildingController() {
19471
19713
  const {
19472
19714
  getAll: _getAll,
@@ -19488,7 +19730,10 @@ function useBuildingController() {
19488
19730
  serial: import_joi44.default.string().optional().allow("", null),
19489
19731
  status: import_joi44.default.string().optional().allow("", null),
19490
19732
  // buildingFloorPlan: Joi.array().items(Joi.string()).optional(),
19491
- buildingFiles: import_joi44.default.array().items({ id: import_joi44.default.string().hex().optional().allow("", null), name: import_joi44.default.string().optional().allow("", null) }).optional().allow("", null)
19733
+ buildingFiles: import_joi44.default.array().items({
19734
+ id: import_joi44.default.string().hex().optional().allow("", null),
19735
+ name: import_joi44.default.string().optional().allow("", null)
19736
+ }).optional().allow("", null)
19492
19737
  });
19493
19738
  const { error } = validation.validate(value);
19494
19739
  if (error) {
@@ -19608,6 +19853,7 @@ function useBuildingController() {
19608
19853
  async function getBuildingLevel(req, res, next) {
19609
19854
  const site = req.params.site ?? "";
19610
19855
  let block = req.query.block;
19856
+ const isVMS = req.query.isVMS;
19611
19857
  const validation = import_joi44.default.object({
19612
19858
  site: import_joi44.default.string().hex().required(),
19613
19859
  block: import_joi44.default.number().required()
@@ -19619,20 +19865,132 @@ function useBuildingController() {
19619
19865
  }
19620
19866
  block = parseInt(block) ?? 0;
19621
19867
  try {
19622
- const siteBlock = await _getBuildingLevel(site, block);
19868
+ const siteBlock = await _getBuildingLevel(site, block, isVMS);
19623
19869
  res.json(siteBlock);
19624
19870
  return;
19625
19871
  } catch (error2) {
19626
19872
  next(error2);
19627
19873
  }
19628
19874
  }
19875
+ async function uploadSpreadsheetBuilding(req, res, next) {
19876
+ try {
19877
+ if (!req.file) {
19878
+ next(new import_node_server_utils84.BadRequestError("Spreadsheet file is required."));
19879
+ return;
19880
+ }
19881
+ const { originalname, path: path4 } = req.file;
19882
+ const lowerName = originalname.toLowerCase();
19883
+ const rowSchema = import_joi44.default.object({
19884
+ block: import_joi44.default.number().integer().min(0).required(),
19885
+ level: import_joi44.default.string().optional().allow(null, ""),
19886
+ unit: import_joi44.default.string().optional().allow(null, ""),
19887
+ category: import_joi44.default.string().trim().required(),
19888
+ name: import_joi44.default.string().trim().optional().allow(null, ""),
19889
+ email: import_joi44.default.string().email().lowercase().optional().allow(null, ""),
19890
+ phoneNumber: import_joi44.default.string().trim().optional().allow(null, "")
19891
+ });
19892
+ const querySchema = import_joi44.default.object({
19893
+ site: import_joi44.default.string().hex().length(24).required(),
19894
+ org: import_joi44.default.string().hex().length(24).optional().allow(null, "")
19895
+ });
19896
+ const { error: queryError, value: queryValue } = querySchema.validate(
19897
+ req.query,
19898
+ { abortEarly: false, convert: true }
19899
+ );
19900
+ if (queryError) {
19901
+ next(
19902
+ new import_node_server_utils84.BadRequestError(
19903
+ queryError.details.map((d) => d.message).join(", ")
19904
+ )
19905
+ );
19906
+ return;
19907
+ }
19908
+ const { site, org } = queryValue;
19909
+ let rows = [];
19910
+ if (lowerName.endsWith(".xlsx") || lowerName.endsWith(".xls")) {
19911
+ const workbook = new import_exceljs.default.Workbook();
19912
+ await workbook.xlsx.readFile(path4);
19913
+ const worksheet = workbook.worksheets[0];
19914
+ if (!worksheet) {
19915
+ next(
19916
+ new import_node_server_utils84.BadRequestError("No worksheet found in uploaded Excel file.")
19917
+ );
19918
+ return;
19919
+ }
19920
+ const headerRow = worksheet.getRow(1);
19921
+ const headers = (headerRow.values || []).slice(1).map(
19922
+ (header) => String(header ?? "").trim().replace(/\(.*\)/, "").trim()
19923
+ );
19924
+ worksheet.eachRow((row, rowNumber) => {
19925
+ if (rowNumber === 1)
19926
+ return;
19927
+ const rowData = {};
19928
+ headers.forEach((header, index) => {
19929
+ rowData[header] = row.getCell(index + 1).value ?? "";
19930
+ });
19931
+ if (Object.values(rowData).some(
19932
+ (v) => v !== "" && v !== null && v !== void 0
19933
+ )) {
19934
+ rows.push(rowData);
19935
+ }
19936
+ });
19937
+ } else if (lowerName.endsWith(".csv")) {
19938
+ rows = await new Promise((resolve, reject) => {
19939
+ const parsed = [];
19940
+ import_fs2.default.createReadStream(path4).pipe((0, import_csv_parser.default)()).on("headers", (headers) => {
19941
+ headers = headers.map(
19942
+ (h) => h.trim().replace(/\(.*\)/, "").trim()
19943
+ );
19944
+ }).on("data", (row) => parsed.push(row)).on("end", () => resolve(parsed)).on("error", reject);
19945
+ });
19946
+ } else {
19947
+ next(
19948
+ new import_node_server_utils84.BadRequestError("Only .xlsx, .xls, or .csv files are allowed.")
19949
+ );
19950
+ return;
19951
+ }
19952
+ const validRows = [];
19953
+ const invalidRows = [];
19954
+ rows.forEach((row, index) => {
19955
+ const { error, value } = rowSchema.validate(row, {
19956
+ abortEarly: false,
19957
+ stripUnknown: true
19958
+ });
19959
+ if (error) {
19960
+ invalidRows.push({
19961
+ row: index + 2,
19962
+ data: row,
19963
+ errors: error.details.map((d) => d.message)
19964
+ });
19965
+ } else {
19966
+ validRows.push(value);
19967
+ }
19968
+ });
19969
+ res.status(200).json({
19970
+ message: "Spreadsheet import completed (buildings).",
19971
+ fileName: originalname,
19972
+ totalRows: rows.length,
19973
+ validRows: validRows.length,
19974
+ invalidRows: invalidRows.length,
19975
+ validationErrors: invalidRows,
19976
+ data: validRows
19977
+ // testing: return the validated building rows directly
19978
+ });
19979
+ import_fs2.default.unlink(path4, () => {
19980
+ });
19981
+ } catch (error) {
19982
+ import_node_server_utils84.logger.log({ level: "error", message: error.message });
19983
+ next(error);
19984
+ }
19985
+ }
19629
19986
  return {
19630
19987
  createBuilding,
19631
19988
  getAll,
19632
19989
  getById,
19633
19990
  updateById,
19634
19991
  deleteById,
19635
- getBuildingLevel
19992
+ getBuildingLevel,
19993
+ uploadSpreadsheetBuilding
19636
19994
  };
19637
19995
  }
19638
19996
 
@@ -19948,9 +20306,9 @@ function useBuildingUnitController() {
19948
20306
  // src/controllers/vehicle.controller.ts
19949
20307
  var import_node_server_utils87 = require("@7365admin1/node-server-utils");
19950
20308
  var import_joi46 = __toESM(require("joi"));
19951
- var import_exceljs = __toESM(require("exceljs"));
19952
- var import_csv_parser = __toESM(require("csv-parser"));
19953
- var import_fs2 = __toESM(require("fs"));
20309
+ var import_exceljs2 = __toESM(require("exceljs"));
20310
+ var import_csv_parser2 = __toESM(require("csv-parser"));
20311
+ var import_fs3 = __toESM(require("fs"));
19954
20312
  function useVehicleController() {
19955
20313
  const {
19956
20314
  add: _add,
@@ -20070,7 +20428,7 @@ function useVehicleController() {
20070
20428
  const { site, org } = queryValue;
20071
20429
  let rows = [];
20072
20430
  if (lowerName.endsWith(".xlsx") || lowerName.endsWith(".xls")) {
20073
- const workbook = new import_exceljs.default.Workbook();
20431
+ const workbook = new import_exceljs2.default.Workbook();
20074
20432
  await workbook.xlsx.readFile(path4);
20075
20433
  const worksheet = workbook.worksheets[0];
20076
20434
  if (!worksheet) {
@@ -20099,7 +20457,7 @@ function useVehicleController() {
20099
20457
  } else if (lowerName.endsWith(".csv")) {
20100
20458
  rows = await new Promise((resolve, reject) => {
20101
20459
  const parsed = [];
20102
- import_fs2.default.createReadStream(path4).pipe((0, import_csv_parser.default)()).on("headers", (headers) => {
20460
+ import_fs3.default.createReadStream(path4).pipe((0, import_csv_parser2.default)()).on("headers", (headers) => {
20103
20461
  headers = headers.map(
20104
20462
  (h) => h.trim().replace(/\(.*\)/, "").trim()
20105
20463
  );
@@ -20142,7 +20500,7 @@ function useVehicleController() {
20142
20500
  validationErrors: invalidRows,
20143
20501
  data
20144
20502
  });
20145
- import_fs2.default.unlink(path4, () => {
20503
+ import_fs3.default.unlink(path4, () => {
20146
20504
  });
20147
20505
  } catch (error) {
20148
20506
  import_node_server_utils87.logger.log({ level: "error", message: error.message });
@@ -20536,8 +20894,9 @@ function useSiteCameraService() {
20536
20894
  try {
20537
20895
  session.startTransaction();
20538
20896
  await _add(value, session);
20897
+ const message = value.type === "ip" ? "CCTV(s) added successfully." : "ANPR(s) added successfully.";
20539
20898
  await session.commitTransaction();
20540
- return "ANPR(s) added successfully.";
20899
+ return message;
20541
20900
  } catch (error2) {
20542
20901
  await session.abortTransaction();
20543
20902
  throw error2;
@@ -33671,7 +34030,7 @@ var import_node_server_utils153 = require("@7365admin1/node-server-utils");
33671
34030
  var import_mongodb90 = require("mongodb");
33672
34031
 
33673
34032
  // src/utils/access-management.ts
33674
- var import_fs3 = __toESM(require("fs"));
34033
+ var import_fs4 = __toESM(require("fs"));
33675
34034
  var import_path = __toESM(require("path"));
33676
34035
  var import_axios = __toESM(require("axios"));
33677
34036
  var import_xml2js = require("xml2js");
@@ -33701,7 +34060,7 @@ var minifyXml = (xml) => {
33701
34060
  return xml.replace(/>\s+</g, "><").replace(/\n/g, "").replace(/\r/g, "").replace(/\t/g, "").trim();
33702
34061
  };
33703
34062
  var readTemplate = (name, params) => {
33704
- const template = import_fs3.default.readFileSync(
34063
+ const template = import_fs4.default.readFileSync(
33705
34064
  import_path.default.join(__dirname, `../dist/public/xml-templates/${name}.xml`),
33706
34065
  "utf-8"
33707
34066
  );
@@ -33832,14 +34191,14 @@ var import_xml2js2 = require("xml2js");
33832
34191
 
33833
34192
  // src/utils/rsa-encryption.ts
33834
34193
  var crypto2 = __toESM(require("crypto"));
33835
- var import_fs4 = __toESM(require("fs"));
34194
+ var import_fs5 = __toESM(require("fs"));
33836
34195
  var import_path2 = __toESM(require("path"));
33837
34196
  var pub = import_path2.default.resolve(process.cwd(), "./src/public/rsa-keys/new_rsa_512_pub.pem");
33838
34197
  var priv = import_path2.default.resolve(process.cwd(), "./src/public/rsa-keys/new_rsa_512_priv.pem");
33839
34198
  var EncryptionCredentials = class {
33840
34199
  };
33841
- EncryptionCredentials.RAW_PUBLIC_KEY = import_fs4.default.readFileSync(pub, "utf8");
33842
- EncryptionCredentials.RAW_PRIVATE_KEY = import_fs4.default.readFileSync(priv, "utf8");
34200
+ EncryptionCredentials.RAW_PUBLIC_KEY = import_fs5.default.readFileSync(pub, "utf8");
34201
+ EncryptionCredentials.RAW_PRIVATE_KEY = import_fs5.default.readFileSync(priv, "utf8");
33843
34202
  var EntrypassRSAEncryption = class extends EncryptionCredentials {
33844
34203
  static hexToCardNumber(hex) {
33845
34204
  if (!/^[0-9A-Fa-f]{8}$/.test(hex)) {