@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.
- package/.changeset/occurrence-entry-site-scope.md +58 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +25 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/harness.mjs +21 -0
- package/test/e2e/occurrence-entry-scope.e2e.test.mjs +483 -0
|
@@ -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;
|