@7365admin1/core 3.48.1-staging.160 → 3.48.1-staging.161

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,58 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Scope the occurrence entry routes to the estate the entry belongs to.
6
+
7
+ An occurrence entry is a single line the guards write into an estate's daily
8
+ security log — the time, the subject, the free-text account of what happened, the
9
+ signature of the guard who wrote it, and any incident report attached to it.
10
+ Every route on `/api/occurrence-entries` carried `requireAuth` and nothing more,
11
+ and `occurrence-entry.controller.ts` held no authorization identifier of any
12
+ kind. `requireAuth` proves a session exists; it makes no organisation or site
13
+ decision. So being signed in anywhere on the platform was enough to read another
14
+ client's log line by line, rewrite what an entry says, or delete an entry from
15
+ the record.
16
+
17
+ All six handlers now decide before any work is done. The route count was
18
+ verified at the registration site — `occurrence-entry.route.ts` mounts **six**
19
+ routes, not the four the earlier inventory recorded.
20
+
21
+ - `POST /` and `GET /` already REQUIRE a site (in the body and the query
22
+ respectively), so the guard checks a site the request already names and no
23
+ caller's contract changes.
24
+ - `GET /id/:id`, `PUT /id/:id` and `DELETE /id/:id` name only a record, so the
25
+ site is read off the STORED row through the new `requireOccurrenceEntryReach`,
26
+ built on a new raw reader `getRawOccurrenceEntryById` (the existing
27
+ `getOccurrenceEntryById` is CACHED).
28
+ - `GET /count/bookId/:id` is the one shape not seen on the earlier mounts: the
29
+ id identifies a **book**, not an entry. Its scope therefore comes from the
30
+ BOOK's row, through `requireOccurrenceBookSiteReach`.
31
+
32
+ That last one matters. Deriving reach from "some entry carrying this book id"
33
+ would have refused the FIRST entry of every new day, because a freshly opened
34
+ book has no entries to read — the security app calls this route to work out the
35
+ next serial number before writing that first line. There is a regression case
36
+ for exactly that.
37
+
38
+ `requireOccurrenceBookSiteReach` deliberately uses the book repository's
39
+ ordinary (cached) reader. A book's `site` is immutable — no route on
40
+ `/api/occurrence-books` accepts `site` on update — so a cached row cannot carry
41
+ a site that differs from the stored one, and the usual objection to deciding
42
+ reach from a cache does not apply here.
43
+
44
+ The rule is the existing one, `requireSiteReach` -> `entitleSite`. The
45
+ `customer.sites` branch is load-bearing: the guards who WRITE these entries are
46
+ the contracted security agency's staff and hold no membership in the estate's
47
+ own organisation.
48
+
49
+ Each guard sits AFTER the handler's own Joi validation, so a malformed id still
50
+ answers 400 exactly as before. A row that does not exist has no site, and
51
+ `requireSiteReach` refuses a missing site, so an id matching nothing is refused
52
+ rather than falling through.
53
+
54
+ `PUT /id/:id` cannot MOVE an entry — `schemaUpdateOccurrenceEntry` does not
55
+ accept `site` at all — so there is no destination to check.
56
+
57
+ `/api/occurrence-books` and `/api/occurrence-subjects` are separate mounts and
58
+ are not touched here; each gets its own change.
package/dist/index.d.ts CHANGED
@@ -7581,6 +7581,7 @@ declare function useOccurrenceEntryRepo(): {
7581
7581
  pageRange: string;
7582
7582
  } | TOccurrenceEntry>;
7583
7583
  getOccurrenceEntryById: (_id: string | ObjectId, session?: ClientSession) => Promise<bson.Document>;
7584
+ getRawOccurrenceEntryById: (_id: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
7584
7585
  getOccurrenceEntryByBookId: (dailyOccurrenceBookId: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
7585
7586
  updateOccurrenceEntryById: (_id: ObjectId | string, value: Partial<TOccurrenceEntry>, session?: ClientSession) => Promise<mongodb.UpdateResult<bson.Document>>;
7586
7587
  updateUserNameBySignatureId: (_id: string | ObjectId, value: string | ObjectId, session?: ClientSession) => Promise<number>;
@@ -7597,6 +7598,39 @@ declare function useOccurrenceEntryService(): {
7597
7598
  updateOccurrenceEntryById: (id: string | ObjectId, value: TOccurrenceEntry) => Promise<"Successfully updated occurrence entry (incident report)." | "Successfully updated daily occurrence entry.">;
7598
7599
  };
7599
7600
 
7601
+ /**
7602
+ * Who may see, or change, one line of a site's occurrence book.
7603
+ *
7604
+ * An occurrence entry is a single line the guards write into an estate's daily
7605
+ * security log - the time, the subject, the free-text account of what happened,
7606
+ * the signature of the guard who wrote it, and any incident report attached to
7607
+ * it. Every route on `/api/occurrence-entries` carried `requireAuth` and
7608
+ * nothing more, and this controller held no authorization identifier of any
7609
+ * kind, so being signed in anywhere on the platform was enough to read another
7610
+ * client's log line by line, rewrite what an entry says, or delete an entry
7611
+ * from the record.
7612
+ *
7613
+ * The estate is the scope. `requireSiteReach` is the rule `/api/feedbacks`,
7614
+ * `/api/work-orders`, `/api/occurrence-books`, `/api/manpower-monitoring`,
7615
+ * `/api/attendances`, `/api/remarks`, `/api/vehicles`, `/api/documents` and
7616
+ * `/api/incident-reports` already use. The `customer.sites` branch is
7617
+ * load-bearing here: the guards who WRITE these entries are the contracted
7618
+ * security agency's staff and hold no membership in the estate's organisation.
7619
+ *
7620
+ * - `POST /` and `GET /` already REQUIRE a site, in the body and the query
7621
+ * respectively, so the guard checks the site the request already names and
7622
+ * no caller's contract changes.
7623
+ * - `GET /id/:id`, `PUT /id/:id` and `DELETE /id/:id` name only a record, so
7624
+ * the site is read off the STORED row via `requireOccurrenceEntryReach`.
7625
+ * - `GET /count/bookId/:id` is the one different shape on this mount: `:id`
7626
+ * identifies a BOOK, not an entry. Its scope therefore comes from the BOOK's
7627
+ * row, not from an entry - `requireOccurrenceBookSiteReach` below. Deriving
7628
+ * it from "some entry with this book id" would refuse the FIRST entry of
7629
+ * every new day, because a freshly opened book has no entries to read.
7630
+ *
7631
+ * `PUT /id/:id` cannot MOVE an entry: `schemaUpdateOccurrenceEntry` does not
7632
+ * accept `site` at all, so there is no destination to check.
7633
+ */
7600
7634
  declare function useOccurrenceEntryController(): {
7601
7635
  add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
7602
7636
  getAll: (req: Request, res: Response, next: NextFunction) => Promise<void>;
package/dist/index.js CHANGED
@@ -8551,6 +8551,14 @@ function useOccurrenceEntryRepo() {
8551
8551
  throw error;
8552
8552
  }
8553
8553
  }
8554
+ async function getRawOccurrenceEntryById(_id) {
8555
+ try {
8556
+ _id = new import_mongodb8.ObjectId(_id);
8557
+ } catch (error) {
8558
+ throw new import_node_server_utils9.BadRequestError("Invalid occurrence entry ID format.");
8559
+ }
8560
+ return await collection.findOne({ _id });
8561
+ }
8554
8562
  async function getOccurrenceEntryByBookId(dailyOccurrenceBookId) {
8555
8563
  try {
8556
8564
  dailyOccurrenceBookId = new import_mongodb8.ObjectId(dailyOccurrenceBookId);
@@ -8770,6 +8778,7 @@ function useOccurrenceEntryRepo() {
8770
8778
  add,
8771
8779
  getAll,
8772
8780
  getOccurrenceEntryById,
8781
+ getRawOccurrenceEntryById,
8773
8782
  getOccurrenceEntryByBookId,
8774
8783
  updateOccurrenceEntryById,
8775
8784
  updateUserNameBySignatureId,
@@ -71486,9 +71495,19 @@ function useOccurrenceEntryController() {
71486
71495
  const {
71487
71496
  getAll: _getAll,
71488
71497
  getOccurrenceEntryById: _getOccurrenceEntryById,
71498
+ getRawOccurrenceEntryById: _getRawOccurrenceEntryById,
71489
71499
  deleteOccurrenceEntryById: _deleteOccurrenceEntryById,
71490
71500
  getLatestSerialNumber: _getLatestSerialNumber
71491
71501
  } = useOccurrenceEntryRepo();
71502
+ const { getOccurrenceBookById: _getOccurrenceBookById } = useOccurrenceBookRepo();
71503
+ async function requireOccurrenceEntryReach(req, _id) {
71504
+ const row = await _getRawOccurrenceEntryById(_id);
71505
+ await requireSiteReach(req, row?.site);
71506
+ }
71507
+ async function requireOccurrenceBookSiteReach(req, bookId) {
71508
+ const book = await _getOccurrenceBookById(bookId);
71509
+ await requireSiteReach(req, book?.site);
71510
+ }
71492
71511
  async function add(req, res, next) {
71493
71512
  try {
71494
71513
  const { error, value } = schemaOccurrenceEntry.validate(req.body, {
@@ -71500,6 +71519,7 @@ function useOccurrenceEntryController() {
71500
71519
  next(new import_node_server_utils220.BadRequestError(messages));
71501
71520
  return;
71502
71521
  }
71522
+ await requireSiteReach(req, value.site);
71503
71523
  const data = await _add(value);
71504
71524
  res.status(201).json(data);
71505
71525
  return;
@@ -71533,6 +71553,7 @@ function useOccurrenceEntryController() {
71533
71553
  const sortObj = {
71534
71554
  [sort ? sort : "_id" /* ID */]: order === "asc" /* ASC */ ? 1 : -1
71535
71555
  };
71556
+ await requireSiteReach(req, site);
71536
71557
  const data = await _getAll({
71537
71558
  search,
71538
71559
  page,
@@ -71561,6 +71582,7 @@ function useOccurrenceEntryController() {
71561
71582
  return;
71562
71583
  }
71563
71584
  const { _id } = value;
71585
+ await requireOccurrenceEntryReach(req, _id);
71564
71586
  const data = await _getOccurrenceEntryById(_id);
71565
71587
  res.status(200).json(data);
71566
71588
  return;
@@ -71583,6 +71605,7 @@ function useOccurrenceEntryController() {
71583
71605
  return;
71584
71606
  }
71585
71607
  const { _id, ...rest } = value;
71608
+ await requireOccurrenceEntryReach(req, _id);
71586
71609
  const result = await _updateOccurrenceEntryById(_id, rest);
71587
71610
  res.status(200).json({ message: result });
71588
71611
  return;
@@ -71604,6 +71627,7 @@ function useOccurrenceEntryController() {
71604
71627
  return;
71605
71628
  }
71606
71629
  const { _id } = value;
71630
+ await requireOccurrenceEntryReach(req, _id);
71607
71631
  await _deleteOccurrenceEntryById(_id);
71608
71632
  res.status(200).json({ message: "Successfully deleted occurrence entry." });
71609
71633
  return;
@@ -71623,6 +71647,7 @@ function useOccurrenceEntryController() {
71623
71647
  return;
71624
71648
  }
71625
71649
  try {
71650
+ await requireOccurrenceBookSiteReach(req, _id);
71626
71651
  const data = await _getLatestSerialNumber(_id);
71627
71652
  res.status(200).json({ count: data });
71628
71653
  return;