@7365admin1/core 3.47.1-staging.141 → 3.47.1-staging.143

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.
@@ -0,0 +1,26 @@
1
+ ---
2
+ "@7365admin1/core": patch
3
+ ---
4
+
5
+ Make the people search filters narrow each other instead of overwriting each
6
+ other.
7
+
8
+ `person.repo.getAll` built its Mongo filter as one object literal in which four
9
+ different filters each wrote a `$or` key — the date range (`dateTo`), the
10
+ free-text `search`, `contact` and `plateNumber`. An object literal can only
11
+ carry one `$or`, so the last one written silently replaced every earlier one.
12
+ In practice:
13
+
14
+ - searching by name **and** contact number searched only the contact number,
15
+ - searching by name **and** plate number searched only the plate number,
16
+ - and a **date range was dropped entirely** the moment any of the three was
17
+ used — which is the live case, because `layer-common`
18
+ `composables/usePeople.ts:2-30` sends `search`, `dateFrom` and `dateTo`
19
+ together from the visitor/people list.
20
+
21
+ No error was raised; the endpoint simply answered a narrower question than it
22
+ was asked, with rows that should not have matched.
23
+
24
+ The four groups are now collected and combined with `$and`, so each one narrows
25
+ the result. A single filter behaves exactly as before, and a request with no
26
+ filters is unchanged.
@@ -0,0 +1,38 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Scope the resident and visitor WRITES to a site the caller can actually reach.
6
+
7
+ `PUT /api/people/id/:id` (edit), `PUT /api/people/:id` (delete),
8
+ `PATCH /api/people/:id` (approve / reject / ask to resubmit) and the exported
9
+ `suspendResidentById` identify the person by an id in the URL and carried
10
+ `requireAuth` and nothing more. Any signed-in account on the platform — a
11
+ resident, a cleaner, a guard at a different estate — could rename, delete,
12
+ approve or reject any other client's resident. #1907 closed the read
13
+ (`GET /api/people`); these are the writes behind it.
14
+
15
+ The scope is resolved from the **stored record**, never from the request, and
16
+ that is not a stylistic choice: **not one live caller sends a site.**
17
+ `layer-common usePeople.deleteById` sends no body at all,
18
+ `reviewResidentPerson` sends only `{ status, remarks }`, and both
19
+ `web-app-property-management pages/[org]/[site]/people-mgmt/index.vue:915-917`
20
+ and `components/PeopleFormMgmt.vue:618-620` explicitly `delete payload.site`
21
+ before calling `updateById`. A guard that read the site out of the body would
22
+ have refused every real caller and protected nobody.
23
+
24
+ The rule is the site-reach rule the camera, HID and people-list already use —
25
+ membership of the site, an org-wide membership at the site's owner, or an active
26
+ `customer.sites` engagement — so an agency contracted to an estate keeps
27
+ working. A record carrying an organisation but no site falls back to that
28
+ organisation. Seven365 staff are unchanged.
29
+
30
+ A resident resubmitting their own registration from the mobile app still works:
31
+ `person.service.ts:155-166` writes a `members` row pinned to the site at
32
+ sign-up, before any approval, so a rejected resident still reaches their own
33
+ site. There is an end-to-end case for exactly that.
34
+
35
+ Also: **who approved a resident now comes from the session.** It was read out of
36
+ the `user` cookie the browser sends, falling back to whatever `approvedBy` the
37
+ request body claimed, so an approval could be attributed to anybody. No caller
38
+ sends `approvedBy`, so nothing is lost.
package/dist/index.js CHANGED
@@ -17370,46 +17370,47 @@ function usePersonRepo() {
17370
17370
  }, session) {
17371
17371
  page = page > 0 ? page - 1 : 0;
17372
17372
  const start = dateFrom ? { $gte: new Date(dateFrom).toISOString() } : void 0;
17373
- const end = dateTo ? {
17374
- $or: [
17373
+ const anyOf = [];
17374
+ if (dateTo) {
17375
+ anyOf.push([
17375
17376
  { end: { $lte: new Date(dateTo).toISOString() } },
17376
17377
  { end: null }
17377
- ]
17378
- } : void 0;
17378
+ ]);
17379
+ }
17380
+ if (search) {
17381
+ anyOf.push([
17382
+ { name: { $regex: search, $options: "i" } },
17383
+ { email: { $regex: search, $options: "i" } },
17384
+ { nric: { $regex: search, $options: "i" } },
17385
+ { "plates.plateNumber": { $regex: search, $options: "i" } },
17386
+ { contact: { $regex: makeContainsRegex(search) } },
17387
+ { contacts: { $regex: makeContainsRegex(search) } },
17388
+ { plateNumbers: { $regex: makeContainsRegex(search) } }
17389
+ ]);
17390
+ }
17391
+ if (contact) {
17392
+ anyOf.push([
17393
+ { contact: { $regex: makeContainsRegex(contact) } },
17394
+ { contacts: { $regex: makeContainsRegex(contact) } }
17395
+ ]);
17396
+ }
17397
+ if (plateNumber) {
17398
+ anyOf.push([
17399
+ { "plates.plateNumber": { $regex: makeContainsRegex(plateNumber) } },
17400
+ { plateNumbers: { $regex: makeContainsRegex(plateNumber) } }
17401
+ ]);
17402
+ }
17379
17403
  const query2 = {
17380
17404
  ...status && status !== "all" ? { status } : { status: { $nin: ["deleted", "rejected"] } },
17381
17405
  ...start && { start },
17382
- ...end,
17383
- ...search && {
17384
- $or: [
17385
- { name: { $regex: search, $options: "i" } },
17386
- { email: { $regex: search, $options: "i" } },
17387
- { nric: { $regex: search, $options: "i" } },
17388
- { "plates.plateNumber": { $regex: search, $options: "i" } },
17389
- { contact: { $regex: makeContainsRegex(search) } },
17390
- { contacts: { $regex: makeContainsRegex(search) } },
17391
- { plateNumbers: { $regex: makeContainsRegex(search) } }
17392
- ]
17393
- },
17406
+ ...anyOf.length > 0 && { $and: anyOf.map(($or) => ({ $or })) },
17394
17407
  ...import_mongodb36.ObjectId.isValid(org) && { org: new import_mongodb36.ObjectId(org) },
17395
17408
  ...!import_mongodb36.ObjectId.isValid(org) && orgs && {
17396
17409
  org: { $in: orgs.filter(import_mongodb36.ObjectId.isValid).map((o) => new import_mongodb36.ObjectId(o)) }
17397
17410
  },
17398
17411
  ...import_mongodb36.ObjectId.isValid(site) && { site: new import_mongodb36.ObjectId(site) },
17399
17412
  ...PERSON_TYPES.includes(type) && { type },
17400
- ...nric && { nric: { $regex: makeContainsRegex(nric), $options: "i" } },
17401
- ...contact && {
17402
- $or: [
17403
- { contact: { $regex: makeContainsRegex(contact) } },
17404
- { contacts: { $regex: makeContainsRegex(contact) } }
17405
- ]
17406
- },
17407
- ...plateNumber && {
17408
- $or: [
17409
- { "plates.plateNumber": { $regex: makeContainsRegex(plateNumber) } },
17410
- { plateNumbers: { $regex: makeContainsRegex(plateNumber) } }
17411
- ]
17412
- }
17413
+ ...nric && { nric: { $regex: makeContainsRegex(nric), $options: "i" } }
17413
17414
  };
17414
17415
  sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
17415
17416
  try {
@@ -50337,7 +50338,8 @@ function usePersonController() {
50337
50338
  getCompany: _getCompany,
50338
50339
  getPeopleByPlateNumber: _getPeopleByPlateNumber,
50339
50340
  getPeopleByNRIC: _getPeopleByNRIC,
50340
- getByUserId: _getByUserId
50341
+ getByUserId: _getByUserId,
50342
+ getById: _getById
50341
50343
  } = usePersonRepo();
50342
50344
  const {
50343
50345
  add: _add,
@@ -50345,6 +50347,24 @@ function usePersonController() {
50345
50347
  reviewResidentPerson: _reviewResidentPerson,
50346
50348
  suspendResidentById: _suspendResidentById
50347
50349
  } = usePersonService();
50350
+ async function entitlePerson(req, _id) {
50351
+ const actor = await resolveInviteActor(callerId(req));
50352
+ if (actor.isSuperAdmin)
50353
+ return actor;
50354
+ const person = await _getById(_id);
50355
+ if (!person)
50356
+ throw new import_node_server_utils139.NotFoundError("Person not found.");
50357
+ const site = person.site?.toString() ?? "";
50358
+ if (site) {
50359
+ await useCameraViewService().entitleSite({ siteId: site, userId: actor.id });
50360
+ return actor;
50361
+ }
50362
+ const org = person.org?.toString() ?? "";
50363
+ if (!org || !actor.orgIds.includes(org)) {
50364
+ throw new import_node_server_utils139.UnauthorizedError("Not authorized.");
50365
+ }
50366
+ return actor;
50367
+ }
50348
50368
  async function add(req, res, next) {
50349
50369
  const payload = { ...req.body };
50350
50370
  const { error } = schemaPerson.validate(payload, {
@@ -50478,6 +50498,7 @@ function usePersonController() {
50478
50498
  return;
50479
50499
  }
50480
50500
  try {
50501
+ await entitlePerson(req, _id);
50481
50502
  const result = await _updateById(_id, req.body);
50482
50503
  res.status(200).json({ message: result });
50483
50504
  return;
@@ -50497,6 +50518,7 @@ function usePersonController() {
50497
50518
  return;
50498
50519
  }
50499
50520
  try {
50521
+ await entitlePerson(req, _id);
50500
50522
  await _deleteById(_id);
50501
50523
  res.status(200).json({ message: "Successfully deleted visitor guest." });
50502
50524
  return;
@@ -50695,11 +50717,7 @@ function usePersonController() {
50695
50717
  }
50696
50718
  }
50697
50719
  async function reviewResidentPerson(req, res, next) {
50698
- const cookies = req.headers.cookie?.split(";").map((cookie) => cookie.trim().split("=")).reduce(
50699
- (acc, [key, value]) => ({ ...acc, [key]: value }),
50700
- {}
50701
- );
50702
- req.body.approvedBy = cookies?.["user"] ? cookies["user"].toString() : req.body.approvedBy;
50720
+ req.body.approvedBy = callerId(req) || req.body.approvedBy;
50703
50721
  const _id = req.params.id;
50704
50722
  const payload = { _id, ...req.body };
50705
50723
  const schema2 = import_joi68.default.object({
@@ -50716,6 +50734,7 @@ function usePersonController() {
50716
50734
  return;
50717
50735
  }
50718
50736
  try {
50737
+ await entitlePerson(req, _id);
50719
50738
  req.body.approvedBy = { id: req.body.approvedBy, name: "" };
50720
50739
  const result = await _reviewResidentPerson(_id, req.body);
50721
50740
  res.status(200).json({ message: result });
@@ -50737,6 +50756,7 @@ function usePersonController() {
50737
50756
  return;
50738
50757
  }
50739
50758
  try {
50759
+ await entitlePerson(req, _id);
50740
50760
  const result = await _suspendResidentById(_id);
50741
50761
  res.status(200).json({ message: result });
50742
50762
  return;