@7365admin1/core 3.47.1-staging.129 → 3.47.1-staging.131

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,41 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Read the Terms and Privacy Policy acceptance record as a list.
6
+
7
+ Who accepted the platform Terms, and when, was already stored - `acceptedTerms`
8
+ and `acceptedTermsAt` on the user - but the only way to read it was
9
+ `GET /terms/:user/status`, one named user at a time. The staff console has no
10
+ list of user ids to walk, so it could not show the acceptance record at all, and
11
+ nobody could answer the question that matters the day new Terms are published:
12
+ who has NOT accepted them yet.
13
+
14
+ `usePlatformTermsController().getAcceptance` answers it as a paged list, filtered
15
+ by version number and by accepted / not accepted, either or both, neither
16
+ meaning everybody. With a version AND "not accepted", the answer includes the
17
+ accounts still sitting on an older version, not only the ones that never
18
+ accepted anything. A version number that was never published is a 404 rather
19
+ than an empty page, so a mistyped filter cannot read as "nobody has accepted".
20
+
21
+ `requirePlatformStaff` gates it, resolved from the session - the same gate the
22
+ console audit list and the platform user list carry, and for the same reason: it
23
+ is a cross-tenant read by definition. Staff-level, not owner-level; the owner
24
+ tier stays reserved for the actions that change a client's service.
25
+
26
+ Each row carries an id, a display name, the account type and status, the
27
+ accepted version and the timestamp. No e-mail, no contact number, no birthday -
28
+ a consent record does not need them, and this list spans every client at once.
29
+ Soft-deleted accounts are excluded. The version number is joined at read time,
30
+ so a row cannot hold a stale copy of it, and the join runs after the page is
31
+ cut, on at most `limit` rows.
32
+
33
+ Paging is `$skip` before `$limit`, the offset derived from the page - the defect
34
+ fixed in the members list is not repeated here. Not cached: whether a person has
35
+ accepted the current Terms is the one question where a stale answer is worse
36
+ than a slow one.
37
+
38
+ Additive only. `GET /terms/:user/status`, `POST /terms/:user/accept`,
39
+ `GET /terms/latest`, `GET /terms/:id` and `POST /terms` are untouched, and no
40
+ existing response shape or status code changed. The route that mounts this is a
41
+ separate change in `iservice365-API-core`.
@@ -0,0 +1,13 @@
1
+ ---
2
+ "@7365admin1/core": patch
3
+ ---
4
+
5
+ Require Seven365 staff to read the platform-wide user list on /api/users/v2 too
6
+
7
+ `/api/users` and `/api/users/v2` are separate mounts onto separate controllers,
8
+ each with its own copy of the same unfiltered platform-wide list. Gating only
9
+ v1 left the gate bypassable by adding `/v2` to the URL.
10
+
11
+ `user-v2.controller.ts` `getUsers` now calls `requirePlatformStaff`, the same
12
+ gate. The v2 per-user endpoints are unchanged, and the test that pins the v1
13
+ profile endpoints as ungated now covers both controllers.
package/dist/index.d.ts CHANGED
@@ -157,6 +157,16 @@ declare function useUserRepo(): {
157
157
  acceptedTerms?: ObjectId | null | undefined;
158
158
  acceptedTermsAt?: string | undefined;
159
159
  } | null>;
160
+ listTermsAcceptance: ({ terms, accepted, page, limit, }: {
161
+ terms?: ObjectId | null | undefined;
162
+ accepted?: boolean | undefined;
163
+ page?: number | undefined;
164
+ limit?: number | undefined;
165
+ }) => Promise<{
166
+ items: any[];
167
+ pages: number;
168
+ pageRange: string;
169
+ }>;
160
170
  getUserByEmail: (email: string) => Promise<TUser | null>;
161
171
  getUserByReferralCode: (referralCode: string) => Promise<TUser | null>;
162
172
  getByEmailApp: (email: string, app: string) => Promise<TUser | null>;
@@ -10599,6 +10609,7 @@ declare function usePlatformTermsRepo(): {
10599
10609
  updatedAt?: string | undefined;
10600
10610
  }>;
10601
10611
  getById: (id: string | ObjectId) => Promise<TPlatformTerms>;
10612
+ getByVersion: (version: number) => Promise<mongodb.WithId<TPlatformTerms> | null>;
10602
10613
  getLatest: () => Promise<TPlatformTerms>;
10603
10614
  getNextVersion: () => Promise<number>;
10604
10615
  };
@@ -10645,6 +10656,16 @@ declare function usePlatformTermsService(): {
10645
10656
  acceptedTerms: ObjectId | undefined;
10646
10657
  version: number;
10647
10658
  }>;
10659
+ listAcceptance: ({ version, accepted, page, limit, }: {
10660
+ version?: number | null | undefined;
10661
+ accepted?: boolean | undefined;
10662
+ page?: number | undefined;
10663
+ limit?: number | undefined;
10664
+ }) => Promise<{
10665
+ items: any[];
10666
+ pages: number;
10667
+ pageRange: string;
10668
+ }>;
10648
10669
  add: ({ terms, policies, createdBy, }: {
10649
10670
  terms: string;
10650
10671
  policies: string;
@@ -10665,6 +10686,7 @@ declare function usePlatformTermsController(): {
10665
10686
  getLatest: (_req: Request, res: Response, next: NextFunction) => Promise<void>;
10666
10687
  getById: (req: Request, res: Response, next: NextFunction) => Promise<void>;
10667
10688
  getStatus: (req: Request, res: Response, next: NextFunction) => Promise<void>;
10689
+ getAcceptance: (req: Request, res: Response, next: NextFunction) => Promise<void>;
10668
10690
  accept: (req: Request, res: Response, next: NextFunction) => Promise<void>;
10669
10691
  add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
10670
10692
  };
package/dist/index.js CHANGED
@@ -9491,6 +9491,68 @@ function useUserRepo() {
9491
9491
  throw new import_node_server_utils11.InternalServerError("Failed to read terms acceptance.");
9492
9492
  }
9493
9493
  }
9494
+ async function listTermsAcceptance({
9495
+ terms = null,
9496
+ accepted,
9497
+ page = 1,
9498
+ limit = 20
9499
+ }) {
9500
+ page = Math.max(Number(page) || 1, 1) - 1;
9501
+ limit = Math.min(Math.max(Number(limit) || 20, 1), 100);
9502
+ const query2 = { status: { $ne: "deleted" /* DELETED */ } };
9503
+ if (accepted === true) {
9504
+ query2.acceptedTerms = terms ?? { $ne: null };
9505
+ } else if (accepted === false) {
9506
+ query2.acceptedTerms = terms ? { $ne: terms } : null;
9507
+ } else if (terms) {
9508
+ query2.acceptedTerms = terms;
9509
+ }
9510
+ const pipeline = [
9511
+ { $match: query2 },
9512
+ { $sort: { acceptedTermsAt: -1, _id: -1 } },
9513
+ { $skip: page * limit },
9514
+ { $limit: limit },
9515
+ {
9516
+ $lookup: {
9517
+ from: "platform_terms",
9518
+ localField: "acceptedTerms",
9519
+ foreignField: "_id",
9520
+ as: "acceptedVersion",
9521
+ pipeline: [{ $project: { version: 1 } }]
9522
+ }
9523
+ },
9524
+ {
9525
+ $project: {
9526
+ _id: 0,
9527
+ id: { $toString: "$_id" },
9528
+ name: { $ifNull: ["$name", ""] },
9529
+ type: { $ifNull: ["$type", ""] },
9530
+ status: 1,
9531
+ acceptedTerms: {
9532
+ $cond: [
9533
+ { $ifNull: ["$acceptedTerms", false] },
9534
+ {
9535
+ id: { $toString: "$acceptedTerms" },
9536
+ version: {
9537
+ $ifNull: [{ $first: "$acceptedVersion.version" }, null]
9538
+ }
9539
+ },
9540
+ null
9541
+ ]
9542
+ },
9543
+ acceptedTermsAt: { $ifNull: ["$acceptedTermsAt", null] }
9544
+ }
9545
+ }
9546
+ ];
9547
+ try {
9548
+ const items = await collection.aggregate(pipeline).toArray();
9549
+ const length = await collection.countDocuments(query2);
9550
+ return (0, import_node_server_utils11.paginate)(items, page, limit, length);
9551
+ } catch (error) {
9552
+ import_node_server_utils11.logger.log({ level: "error", message: `${error}` });
9553
+ throw new import_node_server_utils11.InternalServerError("Failed to read terms acceptance.");
9554
+ }
9555
+ }
9494
9556
  return {
9495
9557
  createIndex,
9496
9558
  createTextIndex,
@@ -9498,6 +9560,7 @@ function useUserRepo() {
9498
9560
  createUser,
9499
9561
  setAcceptedTerms,
9500
9562
  getAcceptedTerms,
9563
+ listTermsAcceptance,
9501
9564
  getUserByEmail,
9502
9565
  getUserByReferralCode,
9503
9566
  getByEmailApp,
@@ -40883,6 +40946,13 @@ function usePlatformTermsRepo() {
40883
40946
  }
40884
40947
  return data;
40885
40948
  }
40949
+ async function getByVersion(version) {
40950
+ try {
40951
+ return await collection.findOne({ version, ...notDeleted });
40952
+ } catch (error) {
40953
+ throw new import_node_server_utils126.InternalServerError("Failed to retrieve terms version.");
40954
+ }
40955
+ }
40886
40956
  async function getNextVersion() {
40887
40957
  try {
40888
40958
  const found = await collection.find({}).sort({ version: -1 }).limit(1).project({ version: 1, _id: 0 }).toArray();
@@ -40895,6 +40965,7 @@ function usePlatformTermsRepo() {
40895
40965
  createIndexes,
40896
40966
  add,
40897
40967
  getById,
40968
+ getByVersion,
40898
40969
  getLatest,
40899
40970
  getNextVersion
40900
40971
  };
@@ -40918,11 +40989,13 @@ function usePlatformTermsService() {
40918
40989
  add: _add,
40919
40990
  getById: _getById,
40920
40991
  getLatest: _getLatest,
40992
+ getByVersion: _getByVersion,
40921
40993
  getNextVersion: _getNextVersion
40922
40994
  } = usePlatformTermsRepo();
40923
40995
  const {
40924
40996
  getAcceptedTerms: _getAcceptedTerms,
40925
- setAcceptedTerms: _setAcceptedTerms
40997
+ setAcceptedTerms: _setAcceptedTerms,
40998
+ listTermsAcceptance: _listTermsAcceptance
40926
40999
  } = useUserRepo();
40927
41000
  const {
40928
41001
  getById: _getPersonById,
@@ -41006,6 +41079,22 @@ function usePlatformTermsService() {
41006
41079
  await _setPersonAcceptedTerms(personId, latest._id);
41007
41080
  return { acceptedTerms: latest._id, version: latest.version };
41008
41081
  }
41082
+ async function listAcceptance({
41083
+ version,
41084
+ accepted,
41085
+ page,
41086
+ limit
41087
+ }) {
41088
+ let terms = null;
41089
+ if (version) {
41090
+ const found = await _getByVersion(version);
41091
+ if (!found) {
41092
+ throw new import_node_server_utils127.NotFoundError("Terms version not found.");
41093
+ }
41094
+ terms = found._id;
41095
+ }
41096
+ return await _listTermsAcceptance({ terms, accepted, page, limit });
41097
+ }
41009
41098
  async function add({
41010
41099
  terms,
41011
41100
  policies,
@@ -41031,6 +41120,7 @@ function usePlatformTermsService() {
41031
41120
  acceptByUserId,
41032
41121
  getStatusByPersonId,
41033
41122
  acceptByPersonId,
41123
+ listAcceptance,
41034
41124
  add
41035
41125
  };
41036
41126
  }
@@ -80406,6 +80496,7 @@ function useUserControllerV2() {
80406
80496
  if (error)
80407
80497
  return rejectValidation(error, next);
80408
80498
  try {
80499
+ await requirePlatformStaff(req);
80409
80500
  const users = await _getUsers({
80410
80501
  search: value.search ?? "",
80411
80502
  page: value.page,
@@ -85193,6 +85284,7 @@ function usePlatformTermsController() {
85193
85284
  getLatest: _getLatest,
85194
85285
  getById: _getById,
85195
85286
  getStatusByUserId: _getStatusByUserId,
85287
+ listAcceptance: _listAcceptance,
85196
85288
  acceptByUserId: _acceptByUserId,
85197
85289
  add: _add
85198
85290
  } = usePlatformTermsService();
@@ -85281,6 +85373,41 @@ function usePlatformTermsController() {
85281
85373
  return;
85282
85374
  }
85283
85375
  }
85376
+ async function getAcceptance(req, res, next) {
85377
+ const schema2 = import_joi160.default.object({
85378
+ version: import_joi160.default.number().integer().min(1).optional().allow("", null),
85379
+ accepted: import_joi160.default.boolean().optional().allow("", null),
85380
+ page: import_joi160.default.number().integer().min(1).allow("", null).default(1),
85381
+ limit: import_joi160.default.number().integer().min(1).max(100).allow("", null).default(20)
85382
+ });
85383
+ const { error, value } = schema2.validate(
85384
+ { ...req.query },
85385
+ { convert: true, stripUnknown: true }
85386
+ );
85387
+ if (error) {
85388
+ import_node_server_utils287.logger.log({ level: "error", message: `${error.message}` });
85389
+ next(new import_node_server_utils287.BadRequestError(error.message));
85390
+ return;
85391
+ }
85392
+ try {
85393
+ await requirePlatformStaff(req);
85394
+ res.json(
85395
+ await _listAcceptance({
85396
+ version: value.version || null,
85397
+ // Left undefined when absent — `false` is a real filter here
85398
+ // ("has NOT accepted"), so it must not be what "no filter" means.
85399
+ accepted: typeof value.accepted === "boolean" ? value.accepted : void 0,
85400
+ page: value.page,
85401
+ limit: value.limit
85402
+ })
85403
+ );
85404
+ return;
85405
+ } catch (error2) {
85406
+ import_node_server_utils287.logger.log({ level: "error", message: `${error2.message}` });
85407
+ next(error2);
85408
+ return;
85409
+ }
85410
+ }
85284
85411
  async function add(req, res, next) {
85285
85412
  const schema2 = import_joi160.default.object({
85286
85413
  user: schemaUserParam.optional(),
@@ -85320,6 +85447,7 @@ function usePlatformTermsController() {
85320
85447
  getLatest,
85321
85448
  getById,
85322
85449
  getStatus,
85450
+ getAcceptance,
85323
85451
  accept,
85324
85452
  add
85325
85453
  };