@7365admin1/core 3.48.1-staging.175 → 3.48.1-staging.177
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/site-billing-item-scope.md +5 -0
- package/.changeset/visitor-guest-scope.md +12 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +41 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +55 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/harness.mjs +27 -0
- package/test/e2e/site-billing-item-scope.e2e.test.mjs +518 -0
- package/test/e2e/visitor-guest-scope.e2e.test.mjs +420 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scope the visitor guest routes to the estate
|
|
6
|
+
|
|
7
|
+
`/api/visitor-guests` carried `requireAuth` and nothing more on all four
|
|
8
|
+
routes, so any signed-in account could add, read, edit or delete another
|
|
9
|
+
estate's expected-visitor list. `site` was optional on the list query and the
|
|
10
|
+
repository only applies it when valid, so omitting it returned every estate's
|
|
11
|
+
visitor records at once. All four handlers now ask the existing site-reach
|
|
12
|
+
rule, and `site` is required on the list query.
|
package/dist/index.d.ts
CHANGED
|
@@ -4693,6 +4693,7 @@ declare function useGuestManagementRepo(): {
|
|
|
4693
4693
|
org?: string | undefined;
|
|
4694
4694
|
site?: string | undefined;
|
|
4695
4695
|
}, session?: ClientSession) => Promise<{}>;
|
|
4696
|
+
getById: (_id: string | ObjectId, session?: ClientSession) => Promise<mongodb.WithId<bson.Document> | null>;
|
|
4696
4697
|
updateVisitorGuestById: (_id: string | ObjectId, value: Partial<TGuestManagement>, session?: ClientSession) => Promise<mongodb.UpdateResult<bson.Document>>;
|
|
4697
4698
|
deleteVisitorGuest: (_id: string | ObjectId) => Promise<number>;
|
|
4698
4699
|
createTextIndex: () => Promise<void>;
|
|
@@ -4703,6 +4704,25 @@ declare function useGuestManagementService(): {
|
|
|
4703
4704
|
updateVisitorGuestById: (id: string | ObjectId, value: TGuestManagement) => Promise<string>;
|
|
4704
4705
|
};
|
|
4705
4706
|
|
|
4707
|
+
/**
|
|
4708
|
+
* A visitor guest is a named person an estate expects at its gate: name,
|
|
4709
|
+
* contact, NRIC, vehicle plate, and the block/level/unit they are visiting.
|
|
4710
|
+
*
|
|
4711
|
+
* All four routes carried `requireAuth` and nothing more, and this controller
|
|
4712
|
+
* held no authorization identifier of any kind. `site` and `org` were read
|
|
4713
|
+
* straight off the caller — the body on `POST`, the query on `GET` — and never
|
|
4714
|
+
* compared to anything, and `PUT`/`DELETE` reached the row by id alone. Being
|
|
4715
|
+
* signed in ANYWHERE on the platform was therefore enough to read, edit or
|
|
4716
|
+
* delete any estate's guest list.
|
|
4717
|
+
*
|
|
4718
|
+
* The `GET` was the worst of the four: `site` and `org` were OPTIONAL, and the
|
|
4719
|
+
* repository only applies them `ObjectId.isValid(...)`. Omitting both left the
|
|
4720
|
+
* `$match` with no tenancy term at all, so one request returned every estate's
|
|
4721
|
+
* visitor records on the platform — names, NRIC, contact numbers and plates.
|
|
4722
|
+
*
|
|
4723
|
+
* The rule is the existing one, `requireSiteReach` (i.e. `entitleSite`), not a
|
|
4724
|
+
* second competing mechanism.
|
|
4725
|
+
*/
|
|
4706
4726
|
declare function useGuestManagementController(): {
|
|
4707
4727
|
add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
4708
4728
|
getAll: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
@@ -6171,6 +6191,30 @@ declare function useSiteBillingItemRepo(): {
|
|
|
6171
6191
|
createIndexes: () => Promise<string>;
|
|
6172
6192
|
};
|
|
6173
6193
|
|
|
6194
|
+
/**
|
|
6195
|
+
* A billing item is a charge the estate raises on its residents — the name, the
|
|
6196
|
+
* amount, the tax that rides on it, how often it repeats, and which units it is
|
|
6197
|
+
* attached to.
|
|
6198
|
+
*
|
|
6199
|
+
* All five handlers carried `requireAuth` and nothing else, and the controller
|
|
6200
|
+
* held no authorization identifier of any kind. The record id came from the URL
|
|
6201
|
+
* and the site came from the caller's own query or body, so any signed-in
|
|
6202
|
+
* account on the platform could list another client's charges, add a charge to
|
|
6203
|
+
* their estate, change what a charge costs, MOVE a charge onto a different
|
|
6204
|
+
* estate (`site` is writable on the update schema), or delete it.
|
|
6205
|
+
*
|
|
6206
|
+
* The rule is the existing one — `requireSiteReach`, i.e. `entitleSite` — asked
|
|
6207
|
+
* about the site the request names on the way in, and about the STORED site for
|
|
6208
|
+
* the id routes, which name no site at all. `PUT` asks twice, once about where
|
|
6209
|
+
* the record is and once about where the body wants to move it, because a MOVE
|
|
6210
|
+
* needs its destination checked too. This is the same shape as
|
|
6211
|
+
* `site-billing-configuration.controller.ts`, not a second mechanism.
|
|
6212
|
+
*
|
|
6213
|
+
* The unit list is checked separately, in the service — see
|
|
6214
|
+
* `site-billing-item.service.ts`. Reaching a site is not the same question as
|
|
6215
|
+
* "do these units belong to it", and the service is where the unit ids are
|
|
6216
|
+
* actually consumed.
|
|
6217
|
+
*/
|
|
6174
6218
|
declare function useSiteBillingItemController(): {
|
|
6175
6219
|
add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
6176
6220
|
getAll: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -50372,6 +50372,14 @@ function useGuestManagementRepo() {
|
|
|
50372
50372
|
throw error;
|
|
50373
50373
|
}
|
|
50374
50374
|
}
|
|
50375
|
+
async function getById(_id, session) {
|
|
50376
|
+
try {
|
|
50377
|
+
_id = new import_mongodb86.ObjectId(_id);
|
|
50378
|
+
} catch (error) {
|
|
50379
|
+
throw new import_node_server_utils135.BadRequestError("Invalid visitor guest ID format.");
|
|
50380
|
+
}
|
|
50381
|
+
return await collection.findOne({ _id }, { session });
|
|
50382
|
+
}
|
|
50375
50383
|
async function updateVisitorGuestById(_id, value, session) {
|
|
50376
50384
|
try {
|
|
50377
50385
|
_id = new import_mongodb86.ObjectId(_id);
|
|
@@ -50434,6 +50442,7 @@ function useGuestManagementRepo() {
|
|
|
50434
50442
|
return {
|
|
50435
50443
|
add,
|
|
50436
50444
|
getAll,
|
|
50445
|
+
getById,
|
|
50437
50446
|
updateVisitorGuestById,
|
|
50438
50447
|
deleteVisitorGuest,
|
|
50439
50448
|
createTextIndex
|
|
@@ -50483,7 +50492,11 @@ var import_joi67 = __toESM(require("joi"));
|
|
|
50483
50492
|
var import_node_server_utils137 = require("@7365admin1/node-server-utils");
|
|
50484
50493
|
function useGuestManagementController() {
|
|
50485
50494
|
const { add: _add, updateVisitorGuestById: _updateVisitorGuestById } = useGuestManagementService();
|
|
50486
|
-
const {
|
|
50495
|
+
const {
|
|
50496
|
+
getAll: _getAll,
|
|
50497
|
+
getById: _getById,
|
|
50498
|
+
deleteVisitorGuest: _deleteVisitorGuest
|
|
50499
|
+
} = useGuestManagementRepo();
|
|
50487
50500
|
async function add(req, res, next) {
|
|
50488
50501
|
const payload = { ...req.body };
|
|
50489
50502
|
const { error } = schemaGuestManagement.validate(payload, {
|
|
@@ -50496,6 +50509,7 @@ function useGuestManagementController() {
|
|
|
50496
50509
|
return;
|
|
50497
50510
|
}
|
|
50498
50511
|
try {
|
|
50512
|
+
await requireSiteReach(req, payload.site);
|
|
50499
50513
|
const data = await _add(payload);
|
|
50500
50514
|
res.status(201).json({ message: data });
|
|
50501
50515
|
return;
|
|
@@ -50516,7 +50530,9 @@ function useGuestManagementController() {
|
|
|
50516
50530
|
dateFrom: import_joi67.default.date().iso().optional().allow(null, ""),
|
|
50517
50531
|
dateTo: import_joi67.default.date().iso().optional().allow(null, ""),
|
|
50518
50532
|
org: import_joi67.default.string().optional().allow(null, ""),
|
|
50519
|
-
|
|
50533
|
+
// required, so the repository can never build a `$match` with no
|
|
50534
|
+
// tenancy term and return the whole platform's guest list
|
|
50535
|
+
site: import_joi67.default.string().hex().length(24).required()
|
|
50520
50536
|
});
|
|
50521
50537
|
const query2 = { ...req.query };
|
|
50522
50538
|
const { error } = validation.validate(query2, {
|
|
@@ -50546,6 +50562,7 @@ function useGuestManagementController() {
|
|
|
50546
50562
|
}
|
|
50547
50563
|
});
|
|
50548
50564
|
try {
|
|
50565
|
+
await requireSiteReach(req, site);
|
|
50549
50566
|
const data = await _getAll({
|
|
50550
50567
|
search,
|
|
50551
50568
|
page,
|
|
@@ -50578,6 +50595,8 @@ function useGuestManagementController() {
|
|
|
50578
50595
|
return;
|
|
50579
50596
|
}
|
|
50580
50597
|
try {
|
|
50598
|
+
const current = await _getById(_id);
|
|
50599
|
+
await requireSiteReach(req, current?.site);
|
|
50581
50600
|
const result = await _updateVisitorGuestById(_id, req.body);
|
|
50582
50601
|
res.status(200).json({ message: result });
|
|
50583
50602
|
return;
|
|
@@ -50597,6 +50616,8 @@ function useGuestManagementController() {
|
|
|
50597
50616
|
return;
|
|
50598
50617
|
}
|
|
50599
50618
|
try {
|
|
50619
|
+
const current = await _getById(_id);
|
|
50620
|
+
await requireSiteReach(req, current?.site);
|
|
50600
50621
|
await _deleteVisitorGuest(_id);
|
|
50601
50622
|
res.status(200).json({ message: "Successfully deleted visitor guest." });
|
|
50602
50623
|
return;
|
|
@@ -58520,6 +58541,12 @@ function useSiteBillingItemService() {
|
|
|
58520
58541
|
} = useSiteBillingItemRepo();
|
|
58521
58542
|
const { updateById: _updateBuildingUnitById, getById: _getById } = useBuildingUnitRepo();
|
|
58522
58543
|
const { getAll } = useSiteBillingConfigurationRepo();
|
|
58544
|
+
function assertUnitAtSite(buildingUnit, site) {
|
|
58545
|
+
const siteId = site?.toString?.() ?? "";
|
|
58546
|
+
if (!siteId || (buildingUnit?.site?.toString() ?? "") !== siteId) {
|
|
58547
|
+
throw new import_node_server_utils179.UnauthorizedError("Not authorized.");
|
|
58548
|
+
}
|
|
58549
|
+
}
|
|
58523
58550
|
async function add(value) {
|
|
58524
58551
|
const session = import_node_server_utils179.useAtlas.getClient()?.startSession();
|
|
58525
58552
|
if (!session) {
|
|
@@ -58550,6 +58577,7 @@ function useSiteBillingItemService() {
|
|
|
58550
58577
|
const promises = value.units.map(async (unit) => {
|
|
58551
58578
|
const unitId = unit?._id?.toString() || "";
|
|
58552
58579
|
const buildingUnit = await _getById(unitId);
|
|
58580
|
+
assertUnitAtSite(buildingUnit, value.site);
|
|
58553
58581
|
const normalizedExistingBilling = (buildingUnit.billing || []).map(
|
|
58554
58582
|
(bill) => ({
|
|
58555
58583
|
_id: bill._id?.toString(),
|
|
@@ -58646,6 +58674,7 @@ function useSiteBillingItemService() {
|
|
|
58646
58674
|
unit._id?.toString() || "",
|
|
58647
58675
|
session
|
|
58648
58676
|
);
|
|
58677
|
+
assertUnitAtSite(buildingUnit, value.site ?? billing_item?.site);
|
|
58649
58678
|
const normalizedBilling = (buildingUnit.billing || []).map(
|
|
58650
58679
|
(bill) => ({
|
|
58651
58680
|
_id: bill._id?.toString() || "",
|
|
@@ -58686,7 +58715,7 @@ function useSiteBillingItemService() {
|
|
|
58686
58715
|
message: error.message
|
|
58687
58716
|
});
|
|
58688
58717
|
await session.abortTransaction();
|
|
58689
|
-
if (error instanceof import_node_server_utils179.BadRequestError) {
|
|
58718
|
+
if (error instanceof import_node_server_utils179.BadRequestError || error instanceof import_node_server_utils179.UnauthorizedError) {
|
|
58690
58719
|
throw error;
|
|
58691
58720
|
}
|
|
58692
58721
|
throw new import_node_server_utils179.BadRequestError("Failed to update Site Billing Item.");
|
|
@@ -58717,6 +58746,7 @@ function useSiteBillingItemController() {
|
|
|
58717
58746
|
return;
|
|
58718
58747
|
}
|
|
58719
58748
|
try {
|
|
58749
|
+
await requireSiteReach(req, value.site);
|
|
58720
58750
|
const question = await _add(value);
|
|
58721
58751
|
res.status(201).json({
|
|
58722
58752
|
message: "Site Billing Item added successfully.",
|
|
@@ -58761,6 +58791,7 @@ function useSiteBillingItemController() {
|
|
|
58761
58791
|
}
|
|
58762
58792
|
});
|
|
58763
58793
|
try {
|
|
58794
|
+
await requireSiteReach(req, site);
|
|
58764
58795
|
const data = await _getAll({
|
|
58765
58796
|
search,
|
|
58766
58797
|
page,
|
|
@@ -58789,6 +58820,7 @@ function useSiteBillingItemController() {
|
|
|
58789
58820
|
}
|
|
58790
58821
|
try {
|
|
58791
58822
|
const building = await _getById(id);
|
|
58823
|
+
await requireSiteReach(req, building?.site);
|
|
58792
58824
|
res.json({
|
|
58793
58825
|
message: "Successfully retrieved site billing item.",
|
|
58794
58826
|
data: { building }
|
|
@@ -58811,6 +58843,10 @@ function useSiteBillingItemController() {
|
|
|
58811
58843
|
return;
|
|
58812
58844
|
}
|
|
58813
58845
|
try {
|
|
58846
|
+
const current = await _getById(_id);
|
|
58847
|
+
await requireSiteReach(req, current?.site);
|
|
58848
|
+
if (req.body?.site)
|
|
58849
|
+
await requireSiteReach(req, req.body.site);
|
|
58814
58850
|
const result = await _updateById(_id, req.body);
|
|
58815
58851
|
res.status(200).json({ message: result });
|
|
58816
58852
|
return;
|
|
@@ -58830,6 +58866,8 @@ function useSiteBillingItemController() {
|
|
|
58830
58866
|
return;
|
|
58831
58867
|
}
|
|
58832
58868
|
try {
|
|
58869
|
+
const current = await _getById(_id);
|
|
58870
|
+
await requireSiteReach(req, current?.site);
|
|
58833
58871
|
await _deleteById(_id);
|
|
58834
58872
|
res.status(200).json({ message: "Successfully deleted site billing item." });
|
|
58835
58873
|
return;
|