@7365admin1/core 3.75.1 → 3.76.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
@@ -21240,6 +21240,37 @@ function signPlaybackLink(params) {
21240
21240
  expiresAt: new Date(exp * 1e3).toISOString()
21241
21241
  };
21242
21242
  }
21243
+ function signLiveLink(host, {
21244
+ relays = playbackRelays(),
21245
+ now = /* @__PURE__ */ new Date()
21246
+ } = {}) {
21247
+ const parsed = host ? parseCameraHost(host) : null;
21248
+ if (!parsed || parsed.channel === null)
21249
+ return null;
21250
+ const key = relays[parsed.authority];
21251
+ if (!key)
21252
+ return null;
21253
+ const exp = Math.floor(now.getTime() / 1e3) + PLAYBACK_LINK_SECONDS;
21254
+ const channel = String(parsed.channel);
21255
+ const sig = createHmac("sha256", key).update(`${channel}|${exp}`).digest("hex");
21256
+ const liveAuth = `exp=${exp}&sig=${sig}`;
21257
+ return {
21258
+ /** The player page, ready for an iframe or a WebView. */
21259
+ liveUrl: `https://${parsed.authority}/${channel}?${liveAuth}`,
21260
+ /**
21261
+ * The same signature, on its own, to append to any of the relay's live
21262
+ * routes — the socket, the still, the clip. One signature serves all four
21263
+ * because it covers the CHANNEL and not the path, which is what lets
21264
+ * `insertScreenshot` keep splicing `screenshot` into the path.
21265
+ */
21266
+ liveAuth,
21267
+ liveExpiresAt: new Date(exp * 1e3).toISOString()
21268
+ };
21269
+ }
21270
+ function withLiveLink(fields, options) {
21271
+ const live = signLiveLink(fields.host, options);
21272
+ return live ? { ...fields, ...live } : fields;
21273
+ }
21243
21274
 
21244
21275
  // src/utils/camera-capability.util.ts
21245
21276
  var CAMERA_CAPABILITIES = [
@@ -22595,7 +22626,9 @@ function useCameraViewService() {
22595
22626
  // player-page URL, which is the one picture path proven to work, and the
22596
22627
  // plain `GET /site-cameras` already returns it to every authenticated
22597
22628
  // caller. The credential fields are still never included.
22598
- ...publicCameraFields({ ...camera, siteName: site?.name }),
22629
+ ...withLiveLink(
22630
+ publicCameraFields({ ...camera, siteName: site?.name })
22631
+ ),
22599
22632
  // Answered before anything is requested, so a camera with no address or
22600
22633
  // an inactive one renders with a reason and costs zero device requests.
22601
22634
  // (An ANPR unit never reaches here — it is filtered out above.)
@@ -22638,7 +22671,7 @@ function useCameraViewService() {
22638
22671
  async function probeCamera(camera, cacheSeconds) {
22639
22672
  const id = camera?._id?.toString?.() ?? "";
22640
22673
  const identity = {
22641
- ...publicCameraFields(camera),
22674
+ ...withLiveLink(publicCameraFields(camera)),
22642
22675
  unavailableReason: snapshotRefusalReason(camera)
22643
22676
  };
22644
22677
  const lastFrameAt = await getCache(`lastframe:${id}`) || null;
@@ -22772,7 +22805,7 @@ function useCameraViewService() {
22772
22805
  );
22773
22806
  const { capabilities } = capabilitiesOf(camera, probe);
22774
22807
  const base = {
22775
- camera: publicCameraFields(camera),
22808
+ camera: withLiveLink(publicCameraFields(camera)),
22776
22809
  snapshotSupported: refusal === null,
22777
22810
  capabilities,
22778
22811
  // Filled in from the probe when the device answers. Today it does not, so
@@ -37981,6 +38014,87 @@ function useVehicleController() {
37981
38014
  next(error);
37982
38015
  }
37983
38016
  }
38017
+ const MAX_BULK_VEHICLE_ROWS = 100;
38018
+ async function addBulk(req, res, next) {
38019
+ const rowSchema = Joi57.object({
38020
+ // The sheet's own row id. Opaque to the server, handed straight back.
38021
+ id: Joi57.string().trim().max(64).required(),
38022
+ block: Joi57.string().hex().length(24).required(),
38023
+ level: Joi57.string().hex().length(24).required(),
38024
+ unit: Joi57.string().hex().length(24).required(),
38025
+ name: Joi57.string().trim().min(1).required(),
38026
+ plateNumber: Joi57.string().trim().min(1).required(),
38027
+ nric: Joi57.string().trim().optional().allow("", null),
38028
+ phoneNumber: Joi57.string().trim().optional().allow("", null)
38029
+ });
38030
+ const schema2 = Joi57.object({
38031
+ site: Joi57.string().hex().length(24).required(),
38032
+ org: Joi57.string().hex().length(24).required(),
38033
+ category: Joi57.string().optional().valid(...Object.values(VehicleCategory)).default("resident" /* RESIDENT */),
38034
+ rows: Joi57.array().items(rowSchema).min(1).max(MAX_BULK_VEHICLE_ROWS).required()
38035
+ });
38036
+ const { error, value } = schema2.validate(req.body, { abortEarly: false });
38037
+ if (error) {
38038
+ const messages = error.details.map((d) => d.message).join(", ");
38039
+ logger79.log({ level: "error", message: messages });
38040
+ next(new BadRequestError102(messages));
38041
+ return;
38042
+ }
38043
+ try {
38044
+ await requireSiteReach(req, value.site);
38045
+ const user = req.user?.user || req.user?._id;
38046
+ const results = [];
38047
+ for (const row of value.rows) {
38048
+ try {
38049
+ const message = await _add(
38050
+ {
38051
+ site: value.site,
38052
+ org: value.org,
38053
+ type: "whitelist" /* WHITELIST */,
38054
+ category: value.category,
38055
+ name: row.name,
38056
+ nric: row.nric || "",
38057
+ phoneNumber: row.phoneNumber || "",
38058
+ block: row.block,
38059
+ level: row.level,
38060
+ unit: row.unit,
38061
+ plateNumber: [row.plateNumber]
38062
+ },
38063
+ void 0,
38064
+ user
38065
+ );
38066
+ results.push({
38067
+ id: row.id,
38068
+ ok: true,
38069
+ message: String(message ?? "Vehicle added successfully.")
38070
+ });
38071
+ } catch (rowError) {
38072
+ logger79.log({
38073
+ level: "error",
38074
+ message: `bulk vehicle row ${row.id}: ${rowError?.message ?? rowError}`
38075
+ });
38076
+ results.push({
38077
+ id: row.id,
38078
+ ok: false,
38079
+ message: String(
38080
+ rowError?.message ?? "The vehicle could not be added."
38081
+ )
38082
+ });
38083
+ }
38084
+ }
38085
+ const added = results.filter((result) => result.ok).length;
38086
+ const failed = results.length - added;
38087
+ res.status(200).json({
38088
+ message: failed === 0 ? `${added} vehicle${added === 1 ? "" : "s"} added.` : `${added} of ${results.length} vehicles added; ${failed} could not be added.`,
38089
+ added,
38090
+ failed,
38091
+ results
38092
+ });
38093
+ } catch (error2) {
38094
+ logger79.log({ level: "error", message: error2.message });
38095
+ next(error2);
38096
+ }
38097
+ }
37984
38098
  async function add(req, res, next) {
37985
38099
  const { error, value } = vehicleSchema.validate(req.body, {
37986
38100
  abortEarly: false
@@ -38456,6 +38570,7 @@ function useVehicleController() {
38456
38570
  }
38457
38571
  return {
38458
38572
  add,
38573
+ addBulk,
38459
38574
  getVehicles,
38460
38575
  getSeasonPassTypes,
38461
38576
  getVehicleById,
@@ -91783,6 +91898,7 @@ export {
91783
91898
  sessionSchema,
91784
91899
  setIO,
91785
91900
  shiftSchema,
91901
+ signLiveLink,
91786
91902
  signPlaybackLink,
91787
91903
  siteDayBounds,
91788
91904
  siteModulesHandlers,
@@ -92033,6 +92149,7 @@ export {
92033
92149
  vehicles_namespace_collection,
92034
92150
  visitors_namespace_collection,
92035
92151
  wallConfig,
92152
+ withLiveLink,
92036
92153
  workOrderSchema,
92037
92154
  work_orders2_namespace_collection,
92038
92155
  work_orders_namespace_collection