@7365admin1/core 3.48.1-staging.157 → 3.48.1-staging.159

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,51 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Scope the feedback routes to the estate the feedback was raised at, and take the
6
+ author's identity from the session.
7
+
8
+ A feedback row is a resident's complaint about their own estate: the subject,
9
+ the free-text description, the location inside the block, the attachments and
10
+ the name of the person who raised it. Every route on `/api/feedbacks` carried
11
+ `requireAuth` and nothing more, and the controller held no authorization
12
+ identifier of any kind, so being signed in anywhere on the platform was enough
13
+ to read another client's complaints, rewrite them, re-route them to a different
14
+ service line, close them, or delete them.
15
+
16
+ `DELETE /:id` is the sharp one, twice over. It is the **only** route on this
17
+ mount with a live caller — `layer-common`'s `useFeedback().deleteFeedback`,
18
+ wired to the delete button in `FeedbackDetail.vue` and `FeedbackMain.vue`, which
19
+ every web app draws — and it does not write to the v1 table at all. It writes to
20
+ `feedbacks2`, the authoritative table every client reads. So the one reachable
21
+ route on the legacy mount was an unguarded delete against live data. It is now
22
+ checked against the `feedbacks2` row it actually touches, not the v1 row that
23
+ happens to share the id.
24
+
25
+ All eight routes go through `requireSiteReach`, the rule
26
+ `/api/manpower-monitoring`, `/api/attendances`, `/api/remarks`,
27
+ `/api/vehicles`, `/api/documents` and `/api/incident-reports` already use:
28
+
29
+ - `GET /site/:site/status/:status` names the site in the URL, so it is checked
30
+ before the read.
31
+ - The six record routes name only a record, so the site is read off the
32
+ **stored** row and a `site` in the caller's own body cannot stand in for it.
33
+ Two readers are needed, because the two tables are not the same shape: v1
34
+ `feedbacks` rows keep the site at `metadata.site`, `feedbacks2` rows keep it
35
+ at the top level.
36
+ - `POST /` took the author from a `user` cookie the caller writes and never
37
+ compared it to the session, so a complaint could be filed in somebody else's
38
+ name. It comes from `callerId(req)` now, which also removes a latent crash:
39
+ `cookies?.["user"].toString()` short-circuits only on `cookies` being nullish
40
+ and threw on any request carrying cookies but no `user` cookie.
41
+
42
+ `requireSiteReach` matters here because the contracted service provider that
43
+ answers these complaints holds no membership in the estate's organisation and
44
+ reaches it through `customer.sites`.
45
+
46
+ Not fixed here, and reported separately: `POST /api/feedbacks` cannot succeed
47
+ and could not before this change either. `feedback.service.ts` replaces
48
+ `createdBy` with an `ObjectId` and `MFeedback` then re-validates the document
49
+ against a schema where `createdBy` is `Joi.string().hex()`, so the create always
50
+ answers 400. No client calls it — every client creates through
51
+ `/api/feedbacks2`.
@@ -0,0 +1,77 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Scope the work order routes to the estate the job was raised at, and take the
6
+ author's identity from the session.
7
+
8
+ A work order is a job at one estate: the subject, the description, the block and
9
+ unit it sits in, the attachments, the assigned provider, and the name and
10
+ signature captured when it is signed off. Every route on `/api/work-orders`
11
+ carried `requireAuth` and nothing more, and `work-order.controller.ts` held no
12
+ authorization identifier of any kind. `requireAuth` proves a session exists; it
13
+ makes no organisation or site decision. So being signed in ANYWHERE on the
14
+ platform was enough to read another client's jobs, rewrite them, drive them
15
+ between columns, sign them off in a stranger's name, or delete them.
16
+
17
+ `DELETE /:id` is the sharp one, and for the same reason it was sharp on
18
+ `/api/feedbacks`: **it does not write to the v1 table at all.** `deleteWorkOrder`
19
+ in `work-order.repo.ts` updates `workOrders2Collection` — `work-orders2`, the
20
+ authoritative table every client actually reads. A guard written against the v1
21
+ row would have been the wrong guard, so this one is written against the row the
22
+ write really touches. `DELETE /:id` and `PUT /:id` are also the only two routes
23
+ on this mount with a live caller: `layer-common`'s
24
+ `useWorkOrder().deleteWorkOrder` and `updateWorkOrder`, behind the shared
25
+ `WorkOrder/Main.vue` and `WorkOrder/Detail.vue` screens that every web app draws.
26
+
27
+ All seven handlers now decide before any work is done:
28
+
29
+ - `GET /site/:site/status/:status` names the site in the URL, so it is checked
30
+ directly with `requireSiteReach`.
31
+ - `GET /:id`, `PUT /:id`, `PUT /:id/:status` and `PATCH /:id/completed` name only
32
+ a record, so the site is read off the STORED row through the new
33
+ `requireWorkOrderReach`. A `site` in the caller's own body or query cannot
34
+ stand in for it.
35
+ - `DELETE /:id` uses `requireWorkOrder2Reach`, against the `work-orders2` row.
36
+ - `POST /` reads the destination from the body.
37
+
38
+ Two readers were needed, not one, because the tables are not the same shape: v1
39
+ `work-orders` rows keep the site at `metadata.site`, `work-orders2` rows keep it
40
+ at the top level. Both are new raw readers on the repository —
41
+ `getRawWorkOrderById` and `getRawWorkOrder2ById`. The existing `getWorkOrderById`
42
+ is CACHED and shaped for the screen, so a reach check built on it would have
43
+ decided who may touch a job from a cache entry.
44
+
45
+ Two further defects fixed in the same pass:
46
+
47
+ 1. **Identity from a cookie the caller writes.** `POST /` parsed `createdBy` out
48
+ of the raw `Cookie` header and never compared it to the session, so a job
49
+ could be raised in somebody else's name. It comes from `callerId(req)` now.
50
+ That also closes a latent crash: `cookies?.["user"].toString()`
51
+ short-circuits only on `cookies` being nullish, so any request carrying
52
+ cookies but no `user` cookie threw a TypeError.
53
+ 2. **A body that names the site twice, with unequal weight.** `POST /` accepts
54
+ `metadata.site` AND a top-level `site`, and `work-order.service.ts` copies the
55
+ top-level one OVER `metadata.site` (`if (value.site) value.metadata.site =
56
+ value.site`). A caller could therefore name their own estate in `metadata`
57
+ while the row landed at somebody else's. The guard reads them in the order the
58
+ service resolves them.
59
+
60
+ `PUT /:id` is a write that can MOVE a record — the repository `$set`s whatever
61
+ the body carries, including `site` — so the DESTINATION is checked as well as the
62
+ origin. Without that, a legacy row carrying no `metadata.site` could be given a
63
+ top-level `site` of the caller's own and become theirs.
64
+
65
+ Each guard is placed AFTER the handler's own Joi validation, so a malformed id
66
+ still answers 400 exactly as before. A row that does not exist has no site, and
67
+ `requireSiteReach` refuses a missing site, so an id matching nothing is refused
68
+ rather than falling through.
69
+
70
+ The rule is the existing one, `requireSiteReach` -> `entitleSite`, already used
71
+ by `/api/feedbacks`, `/api/manpower-monitoring`, `/api/attendances`,
72
+ `/api/remarks`, `/api/vehicles`, `/api/documents` and `/api/incident-reports`.
73
+ The `customer.sites` branch is required, not incidental: the contracted provider
74
+ that does this work holds no membership in the estate's own organisation.
75
+
76
+ `/api/work-orders2` is a different mount, lives in API-core's own controller, and
77
+ is not touched here.
package/dist/index.d.ts CHANGED
@@ -1621,6 +1621,8 @@ declare function useFeedbackRepo(): {
1621
1621
  pageRange: string;
1622
1622
  }>;
1623
1623
  getFeedbackById: (_id: string | ObjectId) => Promise<bson.Document>;
1624
+ getRawFeedbackById: (_id: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
1625
+ getRawFeedback2ById: (_id: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
1624
1626
  updateFeedback: (_id: string | ObjectId, value: TFeedbackUpdate) => Promise<number>;
1625
1627
  updateFeedbackStatus: (_id: string | ObjectId, value: TFeedbackUpdateStatus) => Promise<number>;
1626
1628
  updateFeedbackCreatedByName: (_id: string | ObjectId, value: string | ObjectId, session?: ClientSession) => Promise<number>;
@@ -1649,6 +1651,46 @@ declare function useFeedbackService(): {
1649
1651
  }>;
1650
1652
  };
1651
1653
 
1654
+ /**
1655
+ * Who may see, or change, a site's feedback.
1656
+ *
1657
+ * A feedback row is a resident's complaint about their own estate - the
1658
+ * subject, the free-text description, the location inside the block, the
1659
+ * attachments, and the name of the person who raised it. Every route on
1660
+ * `/api/feedbacks` carried `requireAuth` and nothing more, and this controller
1661
+ * held no authorization identifier of any kind, so being signed in anywhere on
1662
+ * the platform was enough to read another client's complaints, re-categorise
1663
+ * them, mark them completed, or delete them.
1664
+ *
1665
+ * `DELETE /:id` is the sharp one, for two reasons. It is the ONLY route on this
1666
+ * mount with a live caller - `layer-common`'s `useFeedback().deleteFeedback`,
1667
+ * wired to the delete button in `FeedbackDetail.vue` and `FeedbackMain.vue`,
1668
+ * which every web app draws - and it does not write to the v1 table at all. It
1669
+ * writes to `feedbacks2`, the authoritative one every client reads. So the one
1670
+ * reachable route on the legacy mount was an unguarded delete against live
1671
+ * data. It is guarded here against the row it actually touches.
1672
+ *
1673
+ * The estate is the scope. `requireSiteReach` is the rule
1674
+ * `/api/manpower-monitoring`, `/api/attendances`, `/api/remarks`,
1675
+ * `/api/vehicles`, `/api/documents` and `/api/incident-reports` already use, so
1676
+ * the contracted agency that answers these complaints reaches the estate
1677
+ * through `customer.sites` and needs no membership in its organisation.
1678
+ *
1679
+ * - `GET /site/:site/status/:status` names the site in the URL, so it is
1680
+ * checked before the read.
1681
+ * - The six record routes name only a record, so the site is read off the
1682
+ * STORED row and a `site` in the caller's own body cannot stand in for it.
1683
+ * - `POST /` took the author's identity from a `user` cookie the caller writes
1684
+ * and never compared it to the session, so a feedback could be filed in
1685
+ * somebody else's name. It comes from `callerId(req)` now - which also
1686
+ * removes a latent crash, because `cookies?.["user"].toString()`
1687
+ * short-circuits only on `cookies` being nullish and threw on any request
1688
+ * that carried cookies but no `user` cookie.
1689
+ *
1690
+ * Two readers, not one, because the two tables are not the same shape: v1
1691
+ * `feedbacks` rows keep the site at `metadata.site`, `feedbacks2` rows keep it
1692
+ * at the top level.
1693
+ */
1652
1694
  declare function useFeedbackController(): {
1653
1695
  createFeedback: (req: Request, res: Response, next: NextFunction) => Promise<void>;
1654
1696
  getFeedbacks: (req: Request, res: Response, next: NextFunction) => Promise<void>;
@@ -1755,6 +1797,8 @@ declare function useWorkOrderRepo(): {
1755
1797
  category?: string | undefined;
1756
1798
  }) => Promise<{}>;
1757
1799
  getWorkOrderById: (_id: string | ObjectId) => Promise<{}>;
1800
+ getRawWorkOrderById: (_id: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
1801
+ getRawWorkOrder2ById: (_id: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
1758
1802
  updateWorkOrder: (_id: string | ObjectId, value: TWorkOrderUpdate) => Promise<number>;
1759
1803
  updateWorkOrderStatus: (_id: string | ObjectId, value: TWorkOrderUpdateStatus) => Promise<number>;
1760
1804
  updateWorkOrderCreatedByName: (_id: string | ObjectId, value: string | ObjectId, session?: ClientSession) => Promise<number>;
@@ -1776,6 +1820,55 @@ declare function useWorkOrderService(): {
1776
1820
  }) => Promise<{}>;
1777
1821
  };
1778
1822
 
1823
+ /**
1824
+ * Who may see, or change, a site's work orders.
1825
+ *
1826
+ * A work order is a job raised at one estate - the subject, the description,
1827
+ * the block and unit it sits in, the attachments, the assigned provider, and
1828
+ * the name and signature captured when it is signed off. Every route on
1829
+ * `/api/work-orders` carried `requireAuth` and nothing more, and this
1830
+ * controller held no authorization identifier of any kind, so being signed in
1831
+ * anywhere on the platform was enough to read another client's jobs, edit
1832
+ * them, drive their status, sign them off in a stranger's name, or delete them.
1833
+ *
1834
+ * `DELETE /:id` is the sharp one, and for the same reason it was sharp on
1835
+ * `/api/feedbacks`: it does not write to the v1 table at all. `deleteWorkOrder`
1836
+ * in the repository updates `workOrders2Collection` - `work-orders2`, the
1837
+ * authoritative table every client actually reads. A guard written against the
1838
+ * v1 row would have been the wrong guard, so this one is written against the
1839
+ * row the write really touches.
1840
+ *
1841
+ * The estate is the scope. `requireSiteReach` is the rule `/api/feedbacks`,
1842
+ * `/api/manpower-monitoring`, `/api/attendances`, `/api/remarks`,
1843
+ * `/api/vehicles`, `/api/documents` and `/api/incident-reports` already use, so
1844
+ * the contracted provider that does this work reaches the estate through
1845
+ * `customer.sites` and needs no membership in its organisation.
1846
+ *
1847
+ * - `GET /site/:site/status/:status` names the site in the URL, so it is
1848
+ * checked before the read.
1849
+ * - The five record routes name only a record, so the site is read off the
1850
+ * STORED row and a `site` in the caller's own body cannot stand in for it.
1851
+ * - `POST /` names the site in the body - possibly twice. `metadata.site`
1852
+ * and a top-level `site` are not equal: the service copies the top-level one
1853
+ * OVER `metadata.site`, so the guard reads them in that order and a caller
1854
+ * cannot name their own site at the top level while the row lands somewhere
1855
+ * else. A create naming no site at all is refused.
1856
+ * - `POST /` also took the author's identity from a `user` cookie the caller
1857
+ * writes and never compared it to the session, so a job could be raised in
1858
+ * somebody else's name. It comes from `callerId(req)` now - which also
1859
+ * removes a latent crash, because `cookies?.["user"].toString()`
1860
+ * short-circuits only on `cookies` being nullish and threw on any request
1861
+ * that carried cookies but no `user` cookie.
1862
+ * - `PUT /:id` accepts `site` and `organization` in the body and the
1863
+ * repository `$set`s them, so where the body names a destination it is
1864
+ * checked as well as the origin. That closes the one way the fallback below
1865
+ * could be walked: a legacy row with no `metadata.site` would otherwise let
1866
+ * a caller write a top-level `site` of their own and make the row reachable.
1867
+ *
1868
+ * Two readers, not one, because the two tables are not the same shape: v1
1869
+ * `work_orders` rows keep the site at `metadata.site`, `work-orders2` rows keep
1870
+ * it at the top level.
1871
+ */
1779
1872
  declare function useWorkOrderController(): {
1780
1873
  createWorkOrder: (req: Request, res: Response, next: NextFunction) => Promise<void>;
1781
1874
  getWorkOrders: (req: Request, res: Response, next: NextFunction) => Promise<void>;
package/dist/index.js CHANGED
@@ -7188,6 +7188,22 @@ function useFeedbackRepo() {
7188
7188
  throw error;
7189
7189
  }
7190
7190
  }
7191
+ async function getRawFeedbackById(_id) {
7192
+ try {
7193
+ _id = new import_mongodb4.ObjectId(_id);
7194
+ } catch (error) {
7195
+ throw new import_node_server_utils5.BadRequestError("Invalid feedback ID format.");
7196
+ }
7197
+ return await collection.findOne({ _id });
7198
+ }
7199
+ async function getRawFeedback2ById(_id) {
7200
+ try {
7201
+ _id = new import_mongodb4.ObjectId(_id);
7202
+ } catch (error) {
7203
+ throw new import_node_server_utils5.BadRequestError("Invalid feedback ID format.");
7204
+ }
7205
+ return await feedbacks2Collection.findOne({ _id });
7206
+ }
7191
7207
  async function updateFeedback(_id, value) {
7192
7208
  try {
7193
7209
  _id = new import_mongodb4.ObjectId(_id);
@@ -7414,6 +7430,8 @@ function useFeedbackRepo() {
7414
7430
  createFeedback,
7415
7431
  getFeedbacks,
7416
7432
  getFeedbackById,
7433
+ getRawFeedbackById,
7434
+ getRawFeedback2ById,
7417
7435
  updateFeedback,
7418
7436
  updateFeedbackStatus,
7419
7437
  updateFeedbackCreatedByName,
@@ -7837,6 +7855,22 @@ function useWorkOrderRepo() {
7837
7855
  throw error;
7838
7856
  }
7839
7857
  }
7858
+ async function getRawWorkOrderById(_id) {
7859
+ try {
7860
+ _id = new import_mongodb6.ObjectId(_id);
7861
+ } catch (error) {
7862
+ throw new import_node_server_utils7.BadRequestError("Invalid work order ID format.");
7863
+ }
7864
+ return await collection.findOne({ _id });
7865
+ }
7866
+ async function getRawWorkOrder2ById(_id) {
7867
+ try {
7868
+ _id = new import_mongodb6.ObjectId(_id);
7869
+ } catch (error) {
7870
+ throw new import_node_server_utils7.BadRequestError("Invalid work order ID format.");
7871
+ }
7872
+ return await workOrders2Collection.findOne({ _id });
7873
+ }
7840
7874
  async function updateWorkOrder(_id, value) {
7841
7875
  try {
7842
7876
  _id = new import_mongodb6.ObjectId(_id);
@@ -8028,6 +8062,8 @@ function useWorkOrderRepo() {
8028
8062
  createWorkOrder,
8029
8063
  getWorkOrders,
8030
8064
  getWorkOrderById,
8065
+ getRawWorkOrderById,
8066
+ getRawWorkOrder2ById,
8031
8067
  updateWorkOrder,
8032
8068
  updateWorkOrderStatus,
8033
8069
  updateWorkOrderCreatedByName,
@@ -31708,18 +31744,24 @@ function useFeedbackController() {
31708
31744
  const { createFeedback: _createFeedback, getFeedbacks: _getFeedbacks } = useFeedbackService();
31709
31745
  const {
31710
31746
  getFeedbackById: _getFeedbackById,
31747
+ getRawFeedbackById: _getRawFeedbackById,
31748
+ getRawFeedback2ById: _getRawFeedback2ById,
31711
31749
  updateFeedback: _updateFeedback,
31712
31750
  updateFeedbackStatus: _updateFeedbackStatus,
31713
31751
  updateFeedbackCategory: _updateFeedbackCategory,
31714
31752
  updateFeedbackToCompleted: _updateFeedbackToCompleted,
31715
31753
  deleteFeedback: _deleteFeedback
31716
31754
  } = useFeedbackRepo();
31755
+ async function requireFeedbackReach(req, _id) {
31756
+ const row = await _getRawFeedbackById(_id);
31757
+ await requireSiteReach(req, row?.metadata?.site ?? row?.site);
31758
+ }
31759
+ async function requireFeedback2Reach(req, _id) {
31760
+ const row = await _getRawFeedback2ById(_id);
31761
+ await requireSiteReach(req, row?.site ?? row?.metadata?.site);
31762
+ }
31717
31763
  async function createFeedback(req, res, next) {
31718
- const cookies = req.headers.cookie?.split(";").map((cookie) => cookie.trim().split("=")).reduce(
31719
- (acc, [key, value]) => ({ ...acc, [key]: value }),
31720
- {}
31721
- );
31722
- const createdBy = cookies?.["user"].toString() ?? "";
31764
+ const createdBy = callerId(req);
31723
31765
  const payload = { ...req.body, createdBy };
31724
31766
  const { error } = feedbackSchema.validate(payload);
31725
31767
  if (error) {
@@ -31728,6 +31770,7 @@ function useFeedbackController() {
31728
31770
  return;
31729
31771
  }
31730
31772
  try {
31773
+ await requireSiteReach(req, payload.metadata?.site ?? payload.site);
31731
31774
  await _createFeedback(payload);
31732
31775
  res.status(201).json({ message: "Successfully created feedback." });
31733
31776
  return;
@@ -31768,6 +31811,7 @@ function useFeedbackController() {
31768
31811
  const to = req.query.to ?? "";
31769
31812
  const category = req.query.category ?? "";
31770
31813
  try {
31814
+ await requireSiteReach(req, site);
31771
31815
  const data = await _getFeedbacks({
31772
31816
  search,
31773
31817
  page,
@@ -31797,6 +31841,7 @@ function useFeedbackController() {
31797
31841
  return;
31798
31842
  }
31799
31843
  try {
31844
+ await requireFeedbackReach(req, _id);
31800
31845
  const data = await _getFeedbackById(_id);
31801
31846
  res.json(data);
31802
31847
  return;
@@ -31825,6 +31870,7 @@ function useFeedbackController() {
31825
31870
  return;
31826
31871
  }
31827
31872
  try {
31873
+ await requireFeedbackReach(req, _id);
31828
31874
  await _updateFeedback(_id, payload);
31829
31875
  res.json({ message: "Successfully updated feedback." });
31830
31876
  return;
@@ -31849,6 +31895,7 @@ function useFeedbackController() {
31849
31895
  const _id = req.params.id;
31850
31896
  const status = req.params.status;
31851
31897
  try {
31898
+ await requireFeedbackReach(req, _id);
31852
31899
  await _updateFeedbackStatus(_id, { status });
31853
31900
  res.json({ message: "Successfully updated feedback status." });
31854
31901
  return;
@@ -31872,6 +31919,7 @@ function useFeedbackController() {
31872
31919
  return;
31873
31920
  }
31874
31921
  try {
31922
+ await requireFeedbackReach(req, _id);
31875
31923
  await _updateFeedbackCategory(_id, { category });
31876
31924
  res.json({ message: "Successfully updated feedback category." });
31877
31925
  return;
@@ -31897,6 +31945,7 @@ function useFeedbackController() {
31897
31945
  return;
31898
31946
  }
31899
31947
  try {
31948
+ await requireFeedbackReach(req, _id);
31900
31949
  await _updateFeedbackToCompleted(_id, payload);
31901
31950
  res.json({ message: "Successfully updated feedback to completed." });
31902
31951
  return;
@@ -31916,6 +31965,7 @@ function useFeedbackController() {
31916
31965
  return;
31917
31966
  }
31918
31967
  try {
31968
+ await requireFeedback2Reach(req, _id);
31919
31969
  await _deleteFeedback(_id);
31920
31970
  res.json({ message: "Successfully deleted feedback." });
31921
31971
  return;
@@ -32096,17 +32146,23 @@ function useWorkOrderController() {
32096
32146
  const { createWorkOrder: _createWorkOrder, getWorkOrders: _getWorkOrders } = useWorkOrderService();
32097
32147
  const {
32098
32148
  getWorkOrderById: _getWorkOrderById,
32149
+ getRawWorkOrderById: _getRawWorkOrderById,
32150
+ getRawWorkOrder2ById: _getRawWorkOrder2ById,
32099
32151
  updateWorkOrder: _updateWorkOrder,
32100
32152
  updateWorkOrderStatus: _updateWorkOrderStatus,
32101
32153
  updateWorkOrderToCompleted: _updateWorkOrderToCompleted,
32102
32154
  deleteWorkOrder: _deleteWorkOrder
32103
32155
  } = useWorkOrderRepo();
32156
+ async function requireWorkOrderReach(req, _id) {
32157
+ const row = await _getRawWorkOrderById(_id);
32158
+ await requireSiteReach(req, row?.metadata?.site ?? row?.site);
32159
+ }
32160
+ async function requireWorkOrder2Reach(req, _id) {
32161
+ const row = await _getRawWorkOrder2ById(_id);
32162
+ await requireSiteReach(req, row?.site ?? row?.metadata?.site);
32163
+ }
32104
32164
  async function createWorkOrder(req, res, next) {
32105
- const cookies = req.headers.cookie?.split(";").map((cookie) => cookie.trim().split("=")).reduce(
32106
- (acc, [key, value]) => ({ ...acc, [key]: value }),
32107
- {}
32108
- );
32109
- const createdBy = cookies?.["user"].toString() ?? "";
32165
+ const createdBy = callerId(req);
32110
32166
  const payload = { ...req.body, createdBy };
32111
32167
  const { error } = workOrderSchema.validate({ ...payload });
32112
32168
  if (error) {
@@ -32116,6 +32172,7 @@ function useWorkOrderController() {
32116
32172
  }
32117
32173
  const fullHost = req.headers.origin;
32118
32174
  try {
32175
+ await requireSiteReach(req, payload.site || payload.metadata?.site);
32119
32176
  const workOrder = await _createWorkOrder(payload, fullHost);
32120
32177
  res.status(201).json({
32121
32178
  message: "Successfully created work order.",
@@ -32156,6 +32213,7 @@ function useWorkOrderController() {
32156
32213
  const serviceProvider = req.query.serviceProvider ?? "";
32157
32214
  const category = req.query.category ?? "";
32158
32215
  try {
32216
+ await requireSiteReach(req, site);
32159
32217
  const data = await _getWorkOrders({
32160
32218
  search,
32161
32219
  page,
@@ -32183,6 +32241,7 @@ function useWorkOrderController() {
32183
32241
  return;
32184
32242
  }
32185
32243
  try {
32244
+ await requireWorkOrderReach(req, _id);
32186
32245
  const data = await _getWorkOrderById(_id);
32187
32246
  res.json(data);
32188
32247
  return;
@@ -32214,6 +32273,10 @@ function useWorkOrderController() {
32214
32273
  return;
32215
32274
  }
32216
32275
  try {
32276
+ await requireWorkOrderReach(req, _id);
32277
+ if (payload.site) {
32278
+ await requireSiteReach(req, payload.site);
32279
+ }
32217
32280
  await _updateWorkOrder(_id, payload);
32218
32281
  res.json({ message: "Successfully updated work order." });
32219
32282
  return;
@@ -32237,6 +32300,7 @@ function useWorkOrderController() {
32237
32300
  const _id = req.params.id;
32238
32301
  const status = req.params.status;
32239
32302
  try {
32303
+ await requireWorkOrderReach(req, _id);
32240
32304
  await _updateWorkOrderStatus(_id, { status });
32241
32305
  res.json({ message: "Successfully updated work order status." });
32242
32306
  return;
@@ -32262,6 +32326,7 @@ function useWorkOrderController() {
32262
32326
  return;
32263
32327
  }
32264
32328
  try {
32329
+ await requireWorkOrderReach(req, _id);
32265
32330
  await _updateWorkOrderToCompleted(_id, payload);
32266
32331
  res.json({ message: "Successfully updated work order to completed." });
32267
32332
  return;
@@ -32280,6 +32345,7 @@ function useWorkOrderController() {
32280
32345
  return;
32281
32346
  }
32282
32347
  try {
32348
+ await requireWorkOrder2Reach(req, _id);
32283
32349
  await _deleteWorkOrder(_id);
32284
32350
  res.json({ message: "Successfully deleted work order." });
32285
32351
  return;