@7365admin1/core 3.64.3-staging.284 → 3.64.3-staging.285

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.d.ts CHANGED
@@ -4851,7 +4851,6 @@ declare function MPerson(value: TPerson): {
4851
4851
  end: string | Date | undefined;
4852
4852
  type: "resident" | "walk-in" | "drop-off" | "contractor" | "delivery" | "pick-up" | "guest" | "tenant" | undefined;
4853
4853
  email: string | undefined;
4854
- password: string;
4855
4854
  status: string;
4856
4855
  nric: string | undefined;
4857
4856
  remarks: string | undefined;
package/dist/index.js CHANGED
@@ -9035,6 +9035,14 @@ var MVerificationV2 = class {
9035
9035
  }
9036
9036
  };
9037
9037
 
9038
+ // src/utils/email-match.util.ts
9039
+ function escapeRegex(value) {
9040
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9041
+ }
9042
+ function emailFilter(email) {
9043
+ return { $regex: `^${escapeRegex(email)}$`, $options: "i" };
9044
+ }
9045
+
9038
9046
  // src/repositories/user.repo.ts
9039
9047
  function useUserRepo() {
9040
9048
  const { updateFeedbackCreatedByName } = useFeedbackRepo();
@@ -9099,7 +9107,7 @@ function useUserRepo() {
9099
9107
  async function getUserByEmail(email) {
9100
9108
  try {
9101
9109
  const data = await collection.findOne({
9102
- email: { $regex: `^${email}$`, $options: "i" }
9110
+ email: emailFilter(email)
9103
9111
  });
9104
9112
  return data;
9105
9113
  } catch (error) {
@@ -9109,7 +9117,7 @@ function useUserRepo() {
9109
9117
  async function userExistsByEmail(email) {
9110
9118
  try {
9111
9119
  const data = await collection.findOne(
9112
- { email: { $regex: `^${email}$`, $options: "i" } },
9120
+ { email: emailFilter(email) },
9113
9121
  { projection: { _id: 1 } }
9114
9122
  );
9115
9123
  return Boolean(data);
@@ -9130,7 +9138,7 @@ function useUserRepo() {
9130
9138
  return cachedData;
9131
9139
  }
9132
9140
  const query2 = {
9133
- email: { $regex: `^${email}$`, $options: "i" },
9141
+ email: emailFilter(email),
9134
9142
  status: "complete" /* COMPLETE */
9135
9143
  };
9136
9144
  const data = await collection.findOne(query2);
@@ -18301,7 +18309,12 @@ function MPerson(value) {
18301
18309
  end: value.end,
18302
18310
  type: value.type,
18303
18311
  email: value.email,
18304
- password: value.password ?? "",
18312
+ // No `password` here, deliberately. This return is the whitelist that
18313
+ // decides what reaches the `people` collection, and it used to carry the
18314
+ // admin-typed password in clear text — into a document that
18315
+ // `GET /api/people` hands back to the console. The only copy that should
18316
+ // exist is the hash `person.service` writes to the `users` row before the
18317
+ // insert, and nothing anywhere reads it back off the person.
18305
18318
  status: value.status ?? "active",
18306
18319
  nric: value.nric,
18307
18320
  remarks: value.remarks,
@@ -22720,7 +22733,7 @@ function useVehicleRepo() {
22720
22733
  let query2 = { ...baseQuery };
22721
22734
  if (search)
22722
22735
  query2.$text = { $search: search };
22723
- const escapeRegex = (input) => input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22736
+ const escapeRegex2 = (input) => input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22724
22737
  const buildGroupedPipeline = (matchQuery) => [
22725
22738
  { $match: matchQuery },
22726
22739
  {
@@ -22892,7 +22905,7 @@ function useVehicleRepo() {
22892
22905
  const countResult = await collection.aggregate(buildGroupedCountPipeline(query2)).toArray();
22893
22906
  length = countResult[0]?.total || 0;
22894
22907
  if ((!items || items.length === 0) && search) {
22895
- const escaped = escapeRegex(search);
22908
+ const escaped = escapeRegex2(search);
22896
22909
  const regexQuery = {
22897
22910
  ...baseQuery,
22898
22911
  $or: [
@@ -23498,7 +23511,7 @@ function useVehicleRepo() {
23498
23511
  ...baseQuery,
23499
23512
  ...search && { $text: { $search: search } }
23500
23513
  };
23501
- const escapeRegex = (input) => input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
23514
+ const escapeRegex2 = (input) => input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
23502
23515
  const buildPipeline = (matchQuery) => [
23503
23516
  { $match: matchQuery },
23504
23517
  {
@@ -23526,7 +23539,7 @@ function useVehicleRepo() {
23526
23539
  const countResult = await collection.aggregate(buildCountPipeline(query2)).toArray();
23527
23540
  length = countResult[0]?.total || 0;
23528
23541
  if ((!items || items.length === 0) && search) {
23529
- const escaped = escapeRegex(search);
23542
+ const escaped = escapeRegex2(search);
23530
23543
  const regexQuery = {
23531
23544
  ...baseQuery,
23532
23545
  $or: [
@@ -44769,6 +44782,24 @@ var import_mongodb88 = require("mongodb");
44769
44782
  var import_node_server_utils134 = require("@7365admin1/node-server-utils");
44770
44783
  var import_mongodb83 = require("mongodb");
44771
44784
 
44785
+ // src/utils/resident-account.util.ts
44786
+ function residentAccountGaps(value) {
44787
+ const gaps = [];
44788
+ if (!value.name)
44789
+ gaps.push("a name");
44790
+ if (!value.email)
44791
+ gaps.push("an email address");
44792
+ if (!value.password)
44793
+ gaps.push("a password");
44794
+ return gaps;
44795
+ }
44796
+ function residentAccountGapMessage(value) {
44797
+ const gaps = residentAccountGaps(value);
44798
+ if (gaps.length === 0)
44799
+ return "";
44800
+ return `A ${value.type} needs ${gaps.join(", ")} to be able to sign in. Please complete the form and try again.`;
44801
+ }
44802
+
44772
44803
  // src/services/platform-terms.service.ts
44773
44804
  var import_node_server_utils133 = require("@7365admin1/node-server-utils");
44774
44805
 
@@ -45116,6 +45147,69 @@ function usePersonService() {
45116
45147
  acceptByUserId: _acceptTermsByUserId,
45117
45148
  assertLatestTerms: _assertLatestTerms
45118
45149
  } = usePlatformTermsService();
45150
+ async function ensureResidentAccount(value, session) {
45151
+ const gap = residentAccountGapMessage(value);
45152
+ if (gap) {
45153
+ throw new import_node_server_utils134.BadRequestError(gap);
45154
+ }
45155
+ const _user = await getUserByEmail(value.email);
45156
+ if (_user) {
45157
+ throw new import_node_server_utils134.BadRequestError(`User already exists: ${value.email}.`);
45158
+ }
45159
+ const hashedPassword = await (0, import_node_server_utils134.hashPassword)(value.password);
45160
+ const user = {
45161
+ email: value.email,
45162
+ password: hashedPassword,
45163
+ name: value.name,
45164
+ status: value.platform == "mobile" ? "pending" : "active",
45165
+ defaultOrg: value.org?.toString() || "",
45166
+ block: value.block,
45167
+ level: value.level,
45168
+ type: value.type,
45169
+ unitName: value.unitName,
45170
+ contact: value.contact,
45171
+ site: value.site?.toString() || "",
45172
+ unitId: value.unit?.toString() || ""
45173
+ };
45174
+ const userId = await addUser(user, session);
45175
+ if (!userId) {
45176
+ throw new import_node_server_utils134.BadRequestError(
45177
+ `Could not create the sign-in account for ${value.email}.`
45178
+ );
45179
+ }
45180
+ let org = null;
45181
+ if (value?.org) {
45182
+ org = await getOrgById(value.org);
45183
+ if (!org) {
45184
+ value.org = "";
45185
+ }
45186
+ }
45187
+ let site;
45188
+ if (value.site) {
45189
+ site = await getSiteById(value.site);
45190
+ }
45191
+ await addMember(
45192
+ {
45193
+ org: value.org?.toString() || "",
45194
+ orgName: org?.name || "",
45195
+ user: userId.toString(),
45196
+ name: value.name,
45197
+ role: "",
45198
+ type: "resident",
45199
+ siteId: value.site?.toString() || "",
45200
+ siteName: site?.name || ""
45201
+ },
45202
+ session
45203
+ );
45204
+ if (value.acceptedTerms) {
45205
+ await _acceptTermsByUserId(
45206
+ userId.toString(),
45207
+ value.acceptedTerms,
45208
+ session
45209
+ );
45210
+ }
45211
+ return userId.toString();
45212
+ }
45119
45213
  async function add(value) {
45120
45214
  const session = import_node_server_utils134.useAtlas.getClient()?.startSession();
45121
45215
  if (!session) {
@@ -45139,61 +45233,8 @@ function usePersonService() {
45139
45233
  const isValidType = ["resident" /* RESIDENT */, "tenant" /* TENANT */].includes(
45140
45234
  value?.type
45141
45235
  );
45142
- if (value.email && value.password && value.name && isValidType) {
45143
- const _user = await getUserByEmail(value.email);
45144
- if (_user) {
45145
- throw new import_node_server_utils134.BadRequestError(`User already exists: ${value.email}.`);
45146
- }
45147
- const hashedPassword = await (0, import_node_server_utils134.hashPassword)(value.password);
45148
- const user = {
45149
- email: value.email,
45150
- password: hashedPassword,
45151
- name: value.name,
45152
- status: value.platform == "mobile" ? "pending" : "active",
45153
- defaultOrg: value.org?.toString() || "",
45154
- block: value.block,
45155
- level: value.level,
45156
- type: value.type,
45157
- unitName: value.unitName,
45158
- contact: value.contact,
45159
- site: value.site?.toString() || "",
45160
- unitId: value.unit?.toString() || ""
45161
- };
45162
- const userId = await addUser(user, session);
45163
- value.user = userId.toString();
45164
- let org = null;
45165
- if (userId) {
45166
- if (value?.org) {
45167
- org = await getOrgById(value.org);
45168
- if (!org) {
45169
- value.org = "";
45170
- }
45171
- }
45172
- let site;
45173
- if (value.site) {
45174
- site = await getSiteById(value.site);
45175
- }
45176
- await addMember(
45177
- {
45178
- org: value.org?.toString() || "",
45179
- orgName: org?.name || "",
45180
- user: userId.toString(),
45181
- name: value.name,
45182
- role: "",
45183
- type: "resident",
45184
- siteId: value.site?.toString() || "",
45185
- siteName: site?.name || ""
45186
- },
45187
- session
45188
- );
45189
- if (value.acceptedTerms) {
45190
- await _acceptTermsByUserId(
45191
- userId.toString(),
45192
- value.acceptedTerms,
45193
- session
45194
- );
45195
- }
45196
- }
45236
+ if (isValidType) {
45237
+ value.user = await ensureResidentAccount(value, session);
45197
45238
  }
45198
45239
  if (value.isOwner && value.unit) {
45199
45240
  const unit = await _getUnitById(value.unit.toString());
@@ -45264,6 +45305,18 @@ function usePersonService() {
45264
45305
  const isBlockChanged = "block" in value && toKey(value.block) !== toKey(person.block);
45265
45306
  const isLevelChanged = "level" in value && toKey(value.level) !== toKey(person.level);
45266
45307
  const isUnitChanged = "unit" in value && toKey(value.unit) !== toKey(person.unit);
45308
+ const personType = value.type ?? person.type;
45309
+ const carriesLogin = [
45310
+ "resident" /* RESIDENT */,
45311
+ "tenant" /* TENANT */
45312
+ ].includes(personType);
45313
+ if (carriesLogin && !person.user && value.password) {
45314
+ value.user = await ensureResidentAccount(
45315
+ { ...person, ...value, type: personType },
45316
+ session
45317
+ );
45318
+ }
45319
+ delete value.password;
45267
45320
  await _updateById(_id, value, session);
45268
45321
  if (isOrgChanged && person.user) {
45269
45322
  await _updateUserOrgById(
@@ -45510,7 +45563,7 @@ function usePersonService() {
45510
45563
  let set = {};
45511
45564
  const cleanInput = (str) => str.replace(/\s+/g, "");
45512
45565
  const cleanPhone = (str) => str.replace(/\D/g, "");
45513
- const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45566
+ const escapeRegex2 = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45514
45567
  const nric = value.nric ? cleanInput(value.nric) : "";
45515
45568
  const name = value.name?.trim();
45516
45569
  const contact = value.contact?.trim();
@@ -45518,8 +45571,8 @@ function usePersonService() {
45518
45571
  const email = value.email?.trim();
45519
45572
  const type = value.type?.trim();
45520
45573
  const company = value.company?.trim();
45521
- const nricQuery = nric ? { nric: { $regex: `^${escapeRegex(nric)}$`, $options: "i" } } : null;
45522
- const nameQuery = name ? { name: { $regex: `^${escapeRegex(name)}$`, $options: "i" } } : null;
45574
+ const nricQuery = nric ? { nric: { $regex: `^${escapeRegex2(nric)}$`, $options: "i" } } : null;
45575
+ const nameQuery = name ? { name: { $regex: `^${escapeRegex2(name)}$`, $options: "i" } } : null;
45523
45576
  let nricCondition = null;
45524
45577
  if (nric) {
45525
45578
  if (nric.length < 9) {
@@ -45532,8 +45585,8 @@ function usePersonService() {
45532
45585
  }
45533
45586
  const orConditions = [
45534
45587
  ...nricCondition ? [nricCondition] : [],
45535
- ...contact ? [{ contacts: { $regex: `^\\+?${cleanPhone(escapeRegex(contact))}$`, $options: "i" } }] : [],
45536
- ...plateNumber ? [{ plateNumbers: { $regex: `^${escapeRegex(plateNumber)}$`, $options: "i" } }] : []
45588
+ ...contact ? [{ contacts: { $regex: `^\\+?${cleanPhone(escapeRegex2(contact))}$`, $options: "i" } }] : [],
45589
+ ...plateNumber ? [{ plateNumbers: { $regex: `^${escapeRegex2(plateNumber)}$`, $options: "i" } }] : []
45537
45590
  ];
45538
45591
  const existingPerson2 = await findFirstTen(
45539
45592
  {
@@ -47442,7 +47495,7 @@ function useHidAmicoRepo() {
47442
47495
  passwordSet: Boolean(password)
47443
47496
  };
47444
47497
  }
47445
- function escapeRegex(value) {
47498
+ function escapeRegex2(value) {
47446
47499
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
47447
47500
  }
47448
47501
  function buildIdentityKeyQuery(value) {
@@ -47651,7 +47704,7 @@ function useHidAmicoRepo() {
47651
47704
  { serviceProvider: { $in: subjectIds } },
47652
47705
  { user: { $in: subjectIds } }
47653
47706
  ] : null;
47654
- const search = value.search ? escapeRegex(value.search) : "";
47707
+ const search = value.search ? escapeRegex2(value.search) : "";
47655
47708
  const searchOr = search ? [
47656
47709
  { hidUserId: { $regex: search, $options: "i" } },
47657
47710
  { registration: { $regex: search, $options: "i" } },
@@ -47915,7 +47968,7 @@ function useHidAmicoRepo() {
47915
47968
  const text2 = search.trim();
47916
47969
  if (!text2)
47917
47970
  return {};
47918
- const pattern = { $regex: escapeRegex(text2), $options: "i" };
47971
+ const pattern = { $regex: escapeRegex2(text2), $options: "i" };
47919
47972
  return {
47920
47973
  $or: [
47921
47974
  { name: pattern },
@@ -62789,7 +62842,7 @@ function usePatrolEmailRepo() {
62789
62842
  throw new import_node_server_utils163.BadRequestError(`Invalid ${label} format.`);
62790
62843
  }
62791
62844
  }
62792
- function escapeRegex(value) {
62845
+ function escapeRegex2(value) {
62793
62846
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
62794
62847
  }
62795
62848
  async function createIndexes() {
@@ -62926,8 +62979,8 @@ function usePatrolEmailRepo() {
62926
62979
  ...status ? { status } : { status: { $ne: "deleted" } },
62927
62980
  ...search && {
62928
62981
  $or: [
62929
- { subject: { $regex: escapeRegex(search), $options: "i" } },
62930
- { recipients: { $regex: escapeRegex(search), $options: "i" } }
62982
+ { subject: { $regex: escapeRegex2(search), $options: "i" } },
62983
+ { recipients: { $regex: escapeRegex2(search), $options: "i" } }
62931
62984
  ]
62932
62985
  }
62933
62986
  };