@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.mjs CHANGED
@@ -3952,6 +3952,9 @@ function useVerificationRepo() {
3952
3952
  return Promise.reject(error);
3953
3953
  }
3954
3954
  }
3955
+ async function findOne(query) {
3956
+ return await collection.findOne(query);
3957
+ }
3955
3958
  return {
3956
3959
  createIndex,
3957
3960
  createTextIndex,
@@ -3960,7 +3963,8 @@ function useVerificationRepo() {
3960
3963
  getVerifications,
3961
3964
  getByIdByType,
3962
3965
  updateStatusById,
3963
- getByStatus
3966
+ getByStatus,
3967
+ findOne
3964
3968
  };
3965
3969
  }
3966
3970
 
@@ -5297,29 +5301,28 @@ function useVerificationService() {
5297
5301
  throw error;
5298
5302
  }
5299
5303
  }
5300
- async function createSimpleUserInvite({
5301
- email,
5302
- metadata
5303
- }) {
5304
+ async function createSimpleUserInvite({ email, metadata }) {
5304
5305
  const type = "user-invite";
5306
+ if (metadata?.org)
5307
+ await getOrgById(metadata.org);
5308
+ if (metadata?.siteId)
5309
+ await getSiteById(metadata.siteId);
5310
+ const existing = await useVerificationRepo().findOne({
5311
+ type,
5312
+ email,
5313
+ "metadata.org": metadata?.org,
5314
+ "metadata.siteId": metadata?.siteId
5315
+ });
5316
+ if (existing)
5317
+ return existing._id;
5305
5318
  const value = {
5306
5319
  type,
5307
5320
  email,
5308
5321
  metadata,
5309
- expireAt: new Date(
5310
- (/* @__PURE__ */ new Date()).getTime() + 72 * 60 * 60 * 1e3
5311
- ).toISOString(),
5322
+ expireAt: new Date(Date.now() + 72 * 60 * 60 * 1e3).toISOString(),
5312
5323
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
5313
5324
  };
5314
- if (value.metadata?.org) {
5315
- await getOrgById(value.metadata?.org);
5316
- }
5317
- if (value.metadata?.siteId) {
5318
- await getSiteById(value.metadata?.siteId);
5319
- }
5320
5325
  const res = await add(value);
5321
- const dir = __dirname;
5322
- const filePath = getDirectory(dir, "./public/handlebars/user-invite");
5323
5326
  const link = `${APP_MAIN}/verify/invitation/${res}`;
5324
5327
  const emailContent = compileHandlebar({
5325
5328
  context: {
@@ -5327,9 +5330,9 @@ function useVerificationService() {
5327
5330
  validity: VERIFICATION_USER_INVITE_DURATION,
5328
5331
  link
5329
5332
  },
5330
- filePath
5333
+ filePath: getDirectory(__dirname, "./public/handlebars/user-invite")
5331
5334
  });
5332
- mailer.sendMail({
5335
+ await mailer.sendMail({
5333
5336
  to: email,
5334
5337
  subject: "User Invite",
5335
5338
  html: emailContent,
@@ -7181,7 +7184,7 @@ function useMemberService() {
7181
7184
  getByRoles
7182
7185
  } = useMemberRepo();
7183
7186
  const { getById: _getVerificationById, updateStatusById } = useVerificationRepo();
7184
- const { getUserByEmail, updateDefaultOrgByEmail } = useUserRepo();
7187
+ const { getUserByEmail, updateDefaultOrgByEmail, getUserById } = useUserRepo();
7185
7188
  const { getById: getOrgById } = useOrgRepo();
7186
7189
  const { getSiteById } = useSiteRepo();
7187
7190
  const { getOwnerRolesByTypeOrg } = useRoleRepo();
@@ -7243,6 +7246,52 @@ function useMemberService() {
7243
7246
  session?.endSession();
7244
7247
  }
7245
7248
  }
7249
+ async function createMemberDirect({
7250
+ userId,
7251
+ orgId,
7252
+ roleId,
7253
+ app,
7254
+ siteId,
7255
+ siteName
7256
+ }) {
7257
+ const session = useAtlas15.getClient()?.startSession();
7258
+ session?.startTransaction();
7259
+ try {
7260
+ const org = await getOrgById(orgId);
7261
+ if (!org)
7262
+ throw new BadRequestError29("Organization not found.");
7263
+ const user = await getUserById(userId);
7264
+ if (!user)
7265
+ throw new BadRequestError29("User not found.");
7266
+ const member = await addMember(
7267
+ {
7268
+ org: org._id?.toString() || "",
7269
+ orgName: org.name || "",
7270
+ user: user._id?.toString() || "",
7271
+ name: user?.email,
7272
+ role: roleId,
7273
+ type: app,
7274
+ siteId: siteId ?? "",
7275
+ siteName: siteName ?? ""
7276
+ },
7277
+ session
7278
+ );
7279
+ if (!user.defaultOrg) {
7280
+ await updateDefaultOrgByEmail(
7281
+ user.email,
7282
+ org._id?.toString() || "",
7283
+ session
7284
+ );
7285
+ }
7286
+ await session?.commitTransaction();
7287
+ return { member };
7288
+ } catch (error) {
7289
+ await session?.abortTransaction();
7290
+ throw error;
7291
+ } finally {
7292
+ session?.endSession();
7293
+ }
7294
+ }
7246
7295
  async function updateRoleById(id, role, type, org) {
7247
7296
  const owner = await getOwnerRolesByTypeOrg(type, org);
7248
7297
  if (!owner.length) {
@@ -7270,6 +7319,7 @@ function useMemberService() {
7270
7319
  }
7271
7320
  return {
7272
7321
  createMember,
7322
+ createMemberDirect,
7273
7323
  updateRoleById
7274
7324
  };
7275
7325
  }
@@ -7284,7 +7334,7 @@ function useMemberController() {
7284
7334
  updateMemberStatus: _updateMemberStatus,
7285
7335
  updateStatusByUserId: _updateStatusByUserId
7286
7336
  } = useMemberRepo();
7287
- const { createMember: _createMember, updateRoleById: _updateRoleById } = useMemberService();
7337
+ const { createMember: _createMember, createMemberDirect: _createMemberDirect, updateRoleById: _updateRoleById } = useMemberService();
7288
7338
  async function createMember(req, res, next) {
7289
7339
  const validation = Joi15.string().hex().required();
7290
7340
  const _id = req.params.id;
@@ -7473,6 +7523,32 @@ function useMemberController() {
7473
7523
  return;
7474
7524
  }
7475
7525
  }
7526
+ async function createMemberDirect(req, res, next) {
7527
+ const validation = Joi15.object({
7528
+ userId: Joi15.string().hex().required(),
7529
+ orgId: Joi15.string().hex().required(),
7530
+ roleId: Joi15.string().hex().required(),
7531
+ app: Joi15.string().required(),
7532
+ siteId: Joi15.string().hex().optional().allow("", null),
7533
+ siteName: Joi15.string().optional().allow("", null)
7534
+ });
7535
+ const { error } = validation.validate(req.body);
7536
+ if (error) {
7537
+ logger21.log({ level: "error", message: error.message });
7538
+ next(new BadRequestError30(error.message));
7539
+ return;
7540
+ }
7541
+ const { userId, orgId, roleId, app, siteId, siteName } = req.body;
7542
+ try {
7543
+ const data = await _createMemberDirect({ userId, orgId, roleId, app, siteId, siteName });
7544
+ res.status(201).json(data);
7545
+ return;
7546
+ } catch (error2) {
7547
+ logger21.log({ level: "error", message: error2.message });
7548
+ next(error2);
7549
+ return;
7550
+ }
7551
+ }
7476
7552
  return {
7477
7553
  createMember,
7478
7554
  getByUserId,
@@ -7480,7 +7556,8 @@ function useMemberController() {
7480
7556
  getAll,
7481
7557
  getOrgsByMembership,
7482
7558
  updateMemberStatus,
7483
- updateRoleById
7559
+ updateRoleById,
7560
+ createMemberDirect
7484
7561
  };
7485
7562
  }
7486
7563
 
@@ -13016,23 +13093,42 @@ var CameraType = /* @__PURE__ */ ((CameraType2) => {
13016
13093
  })(CameraType || {});
13017
13094
  var schemaUpdateSiteCamera = Joi34.object({
13018
13095
  host: Joi34.string().required(),
13019
- username: Joi34.string().required(),
13020
- password: Joi34.string().required(),
13096
+ username: Joi34.string().when("type", {
13097
+ is: "anpr",
13098
+ then: Joi34.required(),
13099
+ otherwise: Joi34.optional().allow(null, "")
13100
+ }),
13101
+ password: Joi34.string().when("type", {
13102
+ is: "anpr",
13103
+ then: Joi34.required(),
13104
+ otherwise: Joi34.optional().allow(null, "")
13105
+ }),
13021
13106
  category: Joi34.string().allow("standard", "resident", "visitor").required(),
13022
13107
  guardPost: Joi34.number().integer().optional(),
13023
- direction: Joi34.string().optional().allow("entry", "exit", "both", "none")
13108
+ direction: Joi34.string().optional().allow("entry", "exit", "both", "none"),
13109
+ status: Joi34.string().optional().allow("active", "inactive", "deleted"),
13110
+ name: Joi34.string().optional().allow(null, "")
13024
13111
  });
13025
13112
  var schemaSiteCamera = Joi34.object({
13026
13113
  _id: Joi34.string().hex().optional(),
13027
13114
  site: Joi34.string().hex().required(),
13028
13115
  host: Joi34.string().required(),
13029
- username: Joi34.string().required(),
13030
- password: Joi34.string().required(),
13116
+ username: Joi34.string().when("type", {
13117
+ is: "anpr",
13118
+ then: Joi34.required(),
13119
+ otherwise: Joi34.optional().allow(null, "")
13120
+ }),
13121
+ password: Joi34.string().when("type", {
13122
+ is: "anpr",
13123
+ then: Joi34.required(),
13124
+ otherwise: Joi34.optional().allow(null, "")
13125
+ }),
13031
13126
  type: Joi34.string().allow("ip", "anpr").required(),
13032
13127
  category: Joi34.string().allow("standard", "resident", "visitor").required(),
13033
13128
  direction: Joi34.string().required().allow("entry", "exit", "both", "none"),
13034
13129
  guardPost: Joi34.number().integer().optional(),
13035
13130
  status: Joi34.string().optional().allow("active", "inactive", "deleted"),
13131
+ name: Joi34.string().optional().allow(null, ""),
13036
13132
  createdAt: Joi34.date().optional(),
13037
13133
  updatedAt: Joi34.date().optional(),
13038
13134
  deletedAt: Joi34.date().optional()
@@ -13061,6 +13157,7 @@ function MSiteCamera(value) {
13061
13157
  guardPost: value.guardPost ?? 0,
13062
13158
  status: value.status ?? "active",
13063
13159
  direction: value.direction ?? "",
13160
+ name: value.name ?? "",
13064
13161
  createdAt: value.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
13065
13162
  updatedAt: value.updatedAt ?? "",
13066
13163
  deletedAt: value.deletedAt ?? ""
@@ -13381,7 +13478,24 @@ var schemaUpdateVisTrans = Joi36.object({
13381
13478
  checkInRemarks: Joi36.string().optional().allow("", null),
13382
13479
  checkOutRemarks: Joi36.string().optional().allow("", null),
13383
13480
  expectedCheckIn: Joi36.string().isoDate().optional(),
13384
- purpose: Joi36.string().optional().allow(null, "")
13481
+ purpose: Joi36.string().optional().allow(null, ""),
13482
+ members: Joi36.array().items(
13483
+ Joi36.object({
13484
+ name: Joi36.string().optional().allow(null, ""),
13485
+ nric: Joi36.string().optional().allow(null, ""),
13486
+ visitorPass: Joi36.array().items(
13487
+ Joi36.object({
13488
+ keyId: Joi36.string().hex().length(24).required()
13489
+ })
13490
+ ).optional().allow(null),
13491
+ passKeys: Joi36.array().items(
13492
+ Joi36.object({
13493
+ keyId: Joi36.string().hex().length(24).required()
13494
+ })
13495
+ ).optional().allow(null),
13496
+ contact: Joi36.string().optional().allow(null, "")
13497
+ })
13498
+ ).optional().allow(null)
13385
13499
  });
13386
13500
  function MVisitorTransaction(value) {
13387
13501
  const { error } = schemaVisitorTransaction.validate(value, {
@@ -13831,14 +13945,14 @@ function useVisitorTransactionRepo() {
13831
13945
  );
13832
13946
  }
13833
13947
  }
13834
- async function updateById(_id, value, session, isNotManualCheckOut = true) {
13948
+ async function updateById(_id, value, session, isManualCheckOut = true) {
13835
13949
  try {
13836
13950
  _id = new ObjectId40(_id);
13837
13951
  } catch (error) {
13838
13952
  throw new BadRequestError66("Invalid visitor transaction ID format.");
13839
13953
  }
13840
13954
  value.updatedAt = /* @__PURE__ */ new Date();
13841
- if (value.checkOut && isNotManualCheckOut) {
13955
+ if (value.checkOut && isManualCheckOut) {
13842
13956
  value.manualCheckout = true;
13843
13957
  }
13844
13958
  try {
@@ -17294,7 +17408,7 @@ function useDahuaService() {
17294
17408
  );
17295
17409
  }
17296
17410
  }
17297
- if (["entry", "both"].includes(designation) && direction.toLowerCase() === "approach" && plateNumber) {
17411
+ if (["entry"].includes(designation) && plateNumber) {
17298
17412
  await closeOpenTransaction(plateNumber);
17299
17413
  const vehicle = await getVehicleByPlateNumber(plateNumber);
17300
17414
  const visitorTransaction = {
@@ -17349,11 +17463,11 @@ function useDahuaService() {
17349
17463
  } catch (error) {
17350
17464
  console.log("failed to create visitor transaction", error);
17351
17465
  loggerDahua.error(
17352
- `[${site}][${gate}] Error creating visitor transaction:`,
17466
+ `[${site}][${host}] Error creating visitor transaction:`,
17353
17467
  error
17354
17468
  );
17355
17469
  }
17356
- } else if (["exit", "both"].includes(designation) && direction.toLowerCase() === "leave" && plateNumber) {
17470
+ } else if (["exit"].includes(designation) && plateNumber) {
17357
17471
  const vehicle = await getVehicleByPlateNumber(plateNumber);
17358
17472
  const existingOpenTransaction = await getOpenByPlateNumber(
17359
17473
  plateNumber,
@@ -17378,6 +17492,92 @@ function useDahuaService() {
17378
17492
  if (onDetected2) {
17379
17493
  onDetected2({ reload: true, site: existingOpenTransaction?.site?.toString(), host, direction });
17380
17494
  }
17495
+ } else if (["both"].includes(designation) && plateNumber) {
17496
+ if (direction.toLowerCase() === "leave") {
17497
+ const vehicle = await getVehicleByPlateNumber(plateNumber);
17498
+ const existingOpenTransaction = await getOpenByPlateNumber(
17499
+ plateNumber,
17500
+ site
17501
+ );
17502
+ if (existingOpenTransaction?._id) {
17503
+ currentTransactionId = existingOpenTransaction._id.toString();
17504
+ currentSnapshotField = "snapshotExitImage";
17505
+ }
17506
+ if (vehicle?.recNo) {
17507
+ const dahuaPayload = {
17508
+ host,
17509
+ username,
17510
+ password,
17511
+ plateNumber,
17512
+ mode: "TrafficRedList" /* TRAFFIC_REDLIST */,
17513
+ recno: vehicle?.recNo
17514
+ };
17515
+ await removePlateNumber(dahuaPayload);
17516
+ }
17517
+ await closeOpenTransaction(plateNumber);
17518
+ if (onDetected2 && existingOpenTransaction?.site) {
17519
+ onDetected2({ reload: true, site: existingOpenTransaction?.site?.toString(), host, direction });
17520
+ }
17521
+ } else if (direction.toLowerCase() === "approach") {
17522
+ await closeOpenTransaction(plateNumber);
17523
+ const vehicle = await getVehicleByPlateNumber(plateNumber);
17524
+ const visitorTransaction = {
17525
+ site,
17526
+ plateNumber,
17527
+ org,
17528
+ status: "unregistered" /* UNREGISTERED */
17529
+ };
17530
+ const typesForAutoRegister = Object.values(PersonTypes);
17531
+ let startDate = vehicle?.start ? new Date(vehicle?.start) : /* @__PURE__ */ new Date();
17532
+ let endDate = vehicle?.end ? new Date(vehicle?.end) : new Date(startDate.getTime() + 60 * 60 * 1e3);
17533
+ if (vehicle && typeof vehicle.category === "string" && typesForAutoRegister.includes(vehicle.category)) {
17534
+ visitorTransaction.name = vehicle.name;
17535
+ visitorTransaction.nric = vehicle.nric;
17536
+ visitorTransaction.contact = vehicle.phoneNumber;
17537
+ visitorTransaction.block = Number(vehicle.block);
17538
+ visitorTransaction.level = vehicle.level;
17539
+ visitorTransaction.unit = vehicle.unit?.toString();
17540
+ visitorTransaction.unitName = vehicle.unitName;
17541
+ visitorTransaction.type = vehicle.category;
17542
+ visitorTransaction.status = "registered" /* REGISTERED */;
17543
+ visitorTransaction.recNo = vehicle.recNo;
17544
+ visitorTransaction.expiredAt = vehicle.end;
17545
+ }
17546
+ const dahuaPayload = {
17547
+ host,
17548
+ username,
17549
+ password,
17550
+ plateNumber,
17551
+ mode: "TrafficRedList" /* TRAFFIC_REDLIST */,
17552
+ owner: vehicle?.name ?? ""
17553
+ };
17554
+ dahuaPayload.start = formatDahuaDate(startDate);
17555
+ dahuaPayload.end = formatDahuaDate(endDate);
17556
+ const shouldHaveTimeLimit = visitorTransaction.type === "guest" /* GUEST */ || visitorTransaction.status === "unregistered" /* UNREGISTERED */;
17557
+ if (shouldHaveTimeLimit) {
17558
+ const startDate2 = /* @__PURE__ */ new Date();
17559
+ const endDate2 = new Date(startDate2.getTime() + 60 * 60 * 1e3);
17560
+ dahuaPayload.start = formatDahuaDate(startDate2);
17561
+ dahuaPayload.end = formatDahuaDate(endDate2);
17562
+ visitorTransaction.expiredAt = endDate2.toISOString();
17563
+ }
17564
+ try {
17565
+ await addPlateNumber(dahuaPayload);
17566
+ const result = await add(visitorTransaction, void 0, true);
17567
+ const transactionId = result?._id;
17568
+ currentTransactionId = transactionId?.toString();
17569
+ currentSnapshotField = "snapshotEntryImage";
17570
+ if (onDetected2) {
17571
+ onDetected2({ _id: transactionId, site: result?.site?.toString(), plateNumber: result.plateNumber, host, direction });
17572
+ }
17573
+ } catch (error) {
17574
+ console.log("failed to create visitor transaction", error);
17575
+ loggerDahua.error(
17576
+ `[${site}][${host}] Error creating visitor transaction:`,
17577
+ error
17578
+ );
17579
+ }
17580
+ }
17381
17581
  }
17382
17582
  }
17383
17583
  }
@@ -17509,6 +17709,9 @@ function useDahuaService() {
17509
17709
  }
17510
17710
  }
17511
17711
  async function listenToCamera(camera, onDetected) {
17712
+ if (camera?.type != "anpr" && camera?.status != "deleted") {
17713
+ return;
17714
+ }
17512
17715
  if (onDetected) {
17513
17716
  _savedOnDetected = onDetected;
17514
17717
  }
@@ -17517,6 +17720,14 @@ function useDahuaService() {
17517
17720
  return;
17518
17721
  }
17519
17722
  const cameraId = camera._id.toString();
17723
+ if (camera.status !== "active") {
17724
+ if (cameraRegistry.has(cameraId)) {
17725
+ loggerDahua.info(`[${camera?.host ?? camera?._id}] Camera is ${camera.status}. Stopping connection.`);
17726
+ cameraRegistry.get(cameraId)?.abort();
17727
+ cameraRegistry.delete(cameraId);
17728
+ }
17729
+ return;
17730
+ }
17520
17731
  if (cameraRegistry.has(cameraId)) {
17521
17732
  loggerDahua.info(`[${camera.host}] Stopping existing connection for update.`);
17522
17733
  cameraRegistry.get(cameraId)?.abort();
@@ -17664,6 +17875,9 @@ function useDahuaService() {
17664
17875
  continue;
17665
17876
  } else if ([400, 401, 403, 500].includes(response.statusCode)) {
17666
17877
  loggerDahua.error(`[${host}] Connection error: ${response.statusCode}`);
17878
+ if (onDetected) {
17879
+ onDetected({ site, messagePermanent: `Camera with last 3 characters ${host?.slice(-3)} Connection Error. Check username and password.` });
17880
+ }
17667
17881
  return;
17668
17882
  }
17669
17883
  loggerDahua.info(`[${host}] Successfully connected to ANPR.`);
@@ -17716,8 +17930,15 @@ function useDahuaService() {
17716
17930
  break;
17717
17931
  }
17718
17932
  loggerDahua.error(
17719
- `[${host}] Connection lost or error: ${error.message || error}. Retrying in 5s...`
17933
+ `[${host}] Connection lost or error: ${error.message || error}. Retrying in 10 seconds...`
17720
17934
  );
17935
+ if (onDetected) {
17936
+ onDetected({
17937
+ site,
17938
+ host,
17939
+ 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.`
17940
+ });
17941
+ }
17721
17942
  } finally {
17722
17943
  if (bufferQueue?.destroy) {
17723
17944
  try {
@@ -17738,7 +17959,7 @@ function useDahuaService() {
17738
17959
  }
17739
17960
  }
17740
17961
  if (!signal.aborted) {
17741
- await new Promise((res) => setTimeout(res, 5e3));
17962
+ await new Promise((res) => setTimeout(res, 1e4));
17742
17963
  }
17743
17964
  }
17744
17965
  loggerDahua.info(`[${host}] ANPR Listener stopped.`);
@@ -18240,6 +18461,8 @@ function useSiteCameraRepo() {
18240
18461
  }
18241
18462
  try {
18242
18463
  await collection.deleteOne({ _id }, { session });
18464
+ const { listenToCamera } = useDahuaService();
18465
+ await listenToCamera({ _id, status: "deleted" });
18243
18466
  delCachedData();
18244
18467
  return "Successfully deleted site camera.";
18245
18468
  } catch (error) {
@@ -19091,7 +19314,7 @@ function useBuildingRepo() {
19091
19314
  }
19092
19315
  }
19093
19316
  }
19094
- async function getBuildingLevel(site, block) {
19317
+ async function getBuildingLevel(site, block, isVMS) {
19095
19318
  try {
19096
19319
  site = new ObjectId49(site);
19097
19320
  } catch (error) {
@@ -19101,8 +19324,7 @@ function useBuildingRepo() {
19101
19324
  site,
19102
19325
  block
19103
19326
  };
19104
- const cacheOptions = { ...query };
19105
- const cacheKey = makeCacheKey26(buildings_namespace_collection, cacheOptions);
19327
+ const cacheKey = makeCacheKey26(buildings_namespace_collection, { ...query, isVMS });
19106
19328
  try {
19107
19329
  const cached = await getCache(cacheKey);
19108
19330
  if (cached) {
@@ -19112,7 +19334,24 @@ function useBuildingRepo() {
19112
19334
  });
19113
19335
  return cached;
19114
19336
  }
19115
- const result = await collection.findOne(query);
19337
+ const pipeline = [{ $match: { ...query } }];
19338
+ if (isVMS) {
19339
+ pipeline.push({
19340
+ $lookup: {
19341
+ from: "building-levels",
19342
+ let: { id: "$_id" },
19343
+ pipeline: [
19344
+ {
19345
+ $match: {
19346
+ $expr: { $eq: ["$block", "$$id"] }
19347
+ }
19348
+ }
19349
+ ],
19350
+ as: "building_levels"
19351
+ }
19352
+ });
19353
+ }
19354
+ const result = await collection.aggregate(pipeline).toArray();
19116
19355
  setCache(cacheKey, result, 300).then(() => {
19117
19356
  logger60.log({
19118
19357
  level: "info",
@@ -19305,6 +19544,9 @@ function useBuildingService() {
19305
19544
  // src/controllers/building.controller.ts
19306
19545
  import { BadRequestError as BadRequestError81, logger as logger62 } from "@7365admin1/node-server-utils";
19307
19546
  import Joi44 from "joi";
19547
+ import ExcelJS from "exceljs";
19548
+ import csv from "csv-parser";
19549
+ import fs from "fs";
19308
19550
  function useBuildingController() {
19309
19551
  const {
19310
19552
  getAll: _getAll,
@@ -19326,7 +19568,10 @@ function useBuildingController() {
19326
19568
  serial: Joi44.string().optional().allow("", null),
19327
19569
  status: Joi44.string().optional().allow("", null),
19328
19570
  // buildingFloorPlan: Joi.array().items(Joi.string()).optional(),
19329
- buildingFiles: Joi44.array().items({ id: Joi44.string().hex().optional().allow("", null), name: Joi44.string().optional().allow("", null) }).optional().allow("", null)
19571
+ buildingFiles: Joi44.array().items({
19572
+ id: Joi44.string().hex().optional().allow("", null),
19573
+ name: Joi44.string().optional().allow("", null)
19574
+ }).optional().allow("", null)
19330
19575
  });
19331
19576
  const { error } = validation.validate(value);
19332
19577
  if (error) {
@@ -19446,6 +19691,7 @@ function useBuildingController() {
19446
19691
  async function getBuildingLevel(req, res, next) {
19447
19692
  const site = req.params.site ?? "";
19448
19693
  let block = req.query.block;
19694
+ const isVMS = req.query.isVMS;
19449
19695
  const validation = Joi44.object({
19450
19696
  site: Joi44.string().hex().required(),
19451
19697
  block: Joi44.number().required()
@@ -19457,20 +19703,132 @@ function useBuildingController() {
19457
19703
  }
19458
19704
  block = parseInt(block) ?? 0;
19459
19705
  try {
19460
- const siteBlock = await _getBuildingLevel(site, block);
19706
+ const siteBlock = await _getBuildingLevel(site, block, isVMS);
19461
19707
  res.json(siteBlock);
19462
19708
  return;
19463
19709
  } catch (error2) {
19464
19710
  next(error2);
19465
19711
  }
19466
19712
  }
19713
+ async function uploadSpreadsheetBuilding(req, res, next) {
19714
+ try {
19715
+ if (!req.file) {
19716
+ next(new BadRequestError81("Spreadsheet file is required."));
19717
+ return;
19718
+ }
19719
+ const { originalname, path: path4 } = req.file;
19720
+ const lowerName = originalname.toLowerCase();
19721
+ const rowSchema = Joi44.object({
19722
+ block: Joi44.number().integer().min(0).required(),
19723
+ level: Joi44.string().optional().allow(null, ""),
19724
+ unit: Joi44.string().optional().allow(null, ""),
19725
+ category: Joi44.string().trim().required(),
19726
+ name: Joi44.string().trim().optional().allow(null, ""),
19727
+ email: Joi44.string().email().lowercase().optional().allow(null, ""),
19728
+ phoneNumber: Joi44.string().trim().optional().allow(null, "")
19729
+ });
19730
+ const querySchema = Joi44.object({
19731
+ site: Joi44.string().hex().length(24).required(),
19732
+ org: Joi44.string().hex().length(24).optional().allow(null, "")
19733
+ });
19734
+ const { error: queryError, value: queryValue } = querySchema.validate(
19735
+ req.query,
19736
+ { abortEarly: false, convert: true }
19737
+ );
19738
+ if (queryError) {
19739
+ next(
19740
+ new BadRequestError81(
19741
+ queryError.details.map((d) => d.message).join(", ")
19742
+ )
19743
+ );
19744
+ return;
19745
+ }
19746
+ const { site, org } = queryValue;
19747
+ let rows = [];
19748
+ if (lowerName.endsWith(".xlsx") || lowerName.endsWith(".xls")) {
19749
+ const workbook = new ExcelJS.Workbook();
19750
+ await workbook.xlsx.readFile(path4);
19751
+ const worksheet = workbook.worksheets[0];
19752
+ if (!worksheet) {
19753
+ next(
19754
+ new BadRequestError81("No worksheet found in uploaded Excel file.")
19755
+ );
19756
+ return;
19757
+ }
19758
+ const headerRow = worksheet.getRow(1);
19759
+ const headers = (headerRow.values || []).slice(1).map(
19760
+ (header) => String(header ?? "").trim().replace(/\(.*\)/, "").trim()
19761
+ );
19762
+ worksheet.eachRow((row, rowNumber) => {
19763
+ if (rowNumber === 1)
19764
+ return;
19765
+ const rowData = {};
19766
+ headers.forEach((header, index) => {
19767
+ rowData[header] = row.getCell(index + 1).value ?? "";
19768
+ });
19769
+ if (Object.values(rowData).some(
19770
+ (v) => v !== "" && v !== null && v !== void 0
19771
+ )) {
19772
+ rows.push(rowData);
19773
+ }
19774
+ });
19775
+ } else if (lowerName.endsWith(".csv")) {
19776
+ rows = await new Promise((resolve, reject) => {
19777
+ const parsed = [];
19778
+ fs.createReadStream(path4).pipe(csv()).on("headers", (headers) => {
19779
+ headers = headers.map(
19780
+ (h) => h.trim().replace(/\(.*\)/, "").trim()
19781
+ );
19782
+ }).on("data", (row) => parsed.push(row)).on("end", () => resolve(parsed)).on("error", reject);
19783
+ });
19784
+ } else {
19785
+ next(
19786
+ new BadRequestError81("Only .xlsx, .xls, or .csv files are allowed.")
19787
+ );
19788
+ return;
19789
+ }
19790
+ const validRows = [];
19791
+ const invalidRows = [];
19792
+ rows.forEach((row, index) => {
19793
+ const { error, value } = rowSchema.validate(row, {
19794
+ abortEarly: false,
19795
+ stripUnknown: true
19796
+ });
19797
+ if (error) {
19798
+ invalidRows.push({
19799
+ row: index + 2,
19800
+ data: row,
19801
+ errors: error.details.map((d) => d.message)
19802
+ });
19803
+ } else {
19804
+ validRows.push(value);
19805
+ }
19806
+ });
19807
+ res.status(200).json({
19808
+ message: "Spreadsheet import completed (buildings).",
19809
+ fileName: originalname,
19810
+ totalRows: rows.length,
19811
+ validRows: validRows.length,
19812
+ invalidRows: invalidRows.length,
19813
+ validationErrors: invalidRows,
19814
+ data: validRows
19815
+ // testing: return the validated building rows directly
19816
+ });
19817
+ fs.unlink(path4, () => {
19818
+ });
19819
+ } catch (error) {
19820
+ logger62.log({ level: "error", message: error.message });
19821
+ next(error);
19822
+ }
19823
+ }
19467
19824
  return {
19468
19825
  createBuilding,
19469
19826
  getAll,
19470
19827
  getById,
19471
19828
  updateById,
19472
19829
  deleteById,
19473
- getBuildingLevel
19830
+ getBuildingLevel,
19831
+ uploadSpreadsheetBuilding
19474
19832
  };
19475
19833
  }
19476
19834
 
@@ -19791,9 +20149,9 @@ function useBuildingUnitController() {
19791
20149
  // src/controllers/vehicle.controller.ts
19792
20150
  import { BadRequestError as BadRequestError84, logger as logger65 } from "@7365admin1/node-server-utils";
19793
20151
  import Joi46 from "joi";
19794
- import ExcelJS from "exceljs";
19795
- import csv from "csv-parser";
19796
- import fs from "fs";
20152
+ import ExcelJS2 from "exceljs";
20153
+ import csv2 from "csv-parser";
20154
+ import fs2 from "fs";
19797
20155
  function useVehicleController() {
19798
20156
  const {
19799
20157
  add: _add,
@@ -19913,7 +20271,7 @@ function useVehicleController() {
19913
20271
  const { site, org } = queryValue;
19914
20272
  let rows = [];
19915
20273
  if (lowerName.endsWith(".xlsx") || lowerName.endsWith(".xls")) {
19916
- const workbook = new ExcelJS.Workbook();
20274
+ const workbook = new ExcelJS2.Workbook();
19917
20275
  await workbook.xlsx.readFile(path4);
19918
20276
  const worksheet = workbook.worksheets[0];
19919
20277
  if (!worksheet) {
@@ -19942,7 +20300,7 @@ function useVehicleController() {
19942
20300
  } else if (lowerName.endsWith(".csv")) {
19943
20301
  rows = await new Promise((resolve, reject) => {
19944
20302
  const parsed = [];
19945
- fs.createReadStream(path4).pipe(csv()).on("headers", (headers) => {
20303
+ fs2.createReadStream(path4).pipe(csv2()).on("headers", (headers) => {
19946
20304
  headers = headers.map(
19947
20305
  (h) => h.trim().replace(/\(.*\)/, "").trim()
19948
20306
  );
@@ -19985,7 +20343,7 @@ function useVehicleController() {
19985
20343
  validationErrors: invalidRows,
19986
20344
  data
19987
20345
  });
19988
- fs.unlink(path4, () => {
20346
+ fs2.unlink(path4, () => {
19989
20347
  });
19990
20348
  } catch (error) {
19991
20349
  logger65.log({ level: "error", message: error.message });
@@ -20382,8 +20740,9 @@ function useSiteCameraService() {
20382
20740
  try {
20383
20741
  session.startTransaction();
20384
20742
  await _add(value, session);
20743
+ const message = value.type === "ip" ? "CCTV(s) added successfully." : "ANPR(s) added successfully.";
20385
20744
  await session.commitTransaction();
20386
- return "ANPR(s) added successfully.";
20745
+ return message;
20387
20746
  } catch (error2) {
20388
20747
  await session.abortTransaction();
20389
20748
  throw error2;
@@ -33716,7 +34075,7 @@ import {
33716
34075
  import { ObjectId as ObjectId90 } from "mongodb";
33717
34076
 
33718
34077
  // src/utils/access-management.ts
33719
- import fs2 from "fs";
34078
+ import fs3 from "fs";
33720
34079
  import path2 from "path";
33721
34080
  import axios from "axios";
33722
34081
  import { parseStringPromise } from "xml2js";
@@ -33746,7 +34105,7 @@ var minifyXml = (xml) => {
33746
34105
  return xml.replace(/>\s+</g, "><").replace(/\n/g, "").replace(/\r/g, "").replace(/\t/g, "").trim();
33747
34106
  };
33748
34107
  var readTemplate = (name, params) => {
33749
- const template = fs2.readFileSync(
34108
+ const template = fs3.readFileSync(
33750
34109
  path2.join(__dirname, `../dist/public/xml-templates/${name}.xml`),
33751
34110
  "utf-8"
33752
34111
  );
@@ -33877,14 +34236,14 @@ import { parseStringPromise as parseStringPromise2 } from "xml2js";
33877
34236
 
33878
34237
  // src/utils/rsa-encryption.ts
33879
34238
  import * as crypto2 from "crypto";
33880
- import fs3 from "fs";
34239
+ import fs4 from "fs";
33881
34240
  import path3 from "path";
33882
34241
  var pub = path3.resolve(process.cwd(), "./src/public/rsa-keys/new_rsa_512_pub.pem");
33883
34242
  var priv = path3.resolve(process.cwd(), "./src/public/rsa-keys/new_rsa_512_priv.pem");
33884
34243
  var EncryptionCredentials = class {
33885
34244
  };
33886
- EncryptionCredentials.RAW_PUBLIC_KEY = fs3.readFileSync(pub, "utf8");
33887
- EncryptionCredentials.RAW_PRIVATE_KEY = fs3.readFileSync(priv, "utf8");
34245
+ EncryptionCredentials.RAW_PUBLIC_KEY = fs4.readFileSync(pub, "utf8");
34246
+ EncryptionCredentials.RAW_PRIVATE_KEY = fs4.readFileSync(priv, "utf8");
33888
34247
  var EntrypassRSAEncryption = class extends EncryptionCredentials {
33889
34248
  static hexToCardNumber(hex) {
33890
34249
  if (!/^[0-9A-Fa-f]{8}$/.test(hex)) {