@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.
- package/.changeset/terms-acceptance-list.md +41 -0
- package/.changeset/users-v2-list-staff-guard.md +13 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +129 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +129 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/staff-console-authz.test.mjs +31 -12
- package/test/terms-acceptance-list.test.mjs +204 -0
|
@@ -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
|
};
|