@7365admin1/core 3.73.1 → 3.75.0
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/CHANGELOG.md +12 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +91 -50
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +91 -50
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/backfill-org-owner-roles.test.mjs +6 -1
- package/test/e2e/client-invite-service365.e2e.test.mjs +168 -0
- package/test/org-default-roles.test.mjs +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @iservice365/core
|
|
2
2
|
|
|
3
|
+
## 3.75.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 75fb139: Real Estate Developer clients use the Property Management application: membership and role types `real_estate_developer` are stored and looked up as `property_management_agency`, so a developer's owner can open the Property Management app and the owner grant caps their roles.
|
|
8
|
+
|
|
9
|
+
## 3.74.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- a30c5ea: Invite Client for a Property Management or real estate developer client can carry the Service365 resident app modules; onboarding writes them on the organisation
|
|
14
|
+
|
|
3
15
|
## 3.73.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -11262,6 +11262,8 @@ type TVerificationMetadataV2 = {
|
|
|
11262
11262
|
modules?: string[];
|
|
11263
11263
|
/** The new client owner's permissions in that application, chosen with the modules. */
|
|
11264
11264
|
permissions?: string[];
|
|
11265
|
+
/** A Property Management client's Service365 resident app switches, chosen with the modules. */
|
|
11266
|
+
residentAppModules?: Record<string, boolean>;
|
|
11265
11267
|
};
|
|
11266
11268
|
/**
|
|
11267
11269
|
* The one-time code an invitee types to prove they own the mailbox, held on the
|
package/dist/index.js
CHANGED
|
@@ -3974,6 +3974,16 @@ function useUserRepo() {
|
|
|
3974
3974
|
var import_node_server_utils17 = require("@7365admin1/node-server-utils");
|
|
3975
3975
|
var import_uuid = require("uuid");
|
|
3976
3976
|
|
|
3977
|
+
// src/utils/service-app-type.util.ts
|
|
3978
|
+
var APP_TYPE_ALIASES = Object.freeze({
|
|
3979
|
+
real_estate_developer: "property_management_agency"
|
|
3980
|
+
});
|
|
3981
|
+
function serviceAppType(type) {
|
|
3982
|
+
if (typeof type !== "string")
|
|
3983
|
+
return type;
|
|
3984
|
+
return APP_TYPE_ALIASES[type] ?? type;
|
|
3985
|
+
}
|
|
3986
|
+
|
|
3977
3987
|
// src/repositories/member.repo.ts
|
|
3978
3988
|
var import_node_server_utils14 = require("@7365admin1/node-server-utils");
|
|
3979
3989
|
|
|
@@ -4126,7 +4136,7 @@ function useMemberRepo() {
|
|
|
4126
4136
|
}
|
|
4127
4137
|
async function add(value, session) {
|
|
4128
4138
|
try {
|
|
4129
|
-
value = MMember(value);
|
|
4139
|
+
value = MMember({ ...value, type: serviceAppType(value.type) });
|
|
4130
4140
|
await collection.insertOne(value, { session });
|
|
4131
4141
|
delNamespace().then(() => {
|
|
4132
4142
|
import_node_server_utils14.logger.info(`Cache cleared for namespace: ${namespace_collection3}`);
|
|
@@ -4254,6 +4264,7 @@ function useMemberRepo() {
|
|
|
4254
4264
|
}
|
|
4255
4265
|
}
|
|
4256
4266
|
async function getByUserIdType(user, type) {
|
|
4267
|
+
type = serviceAppType(type);
|
|
4257
4268
|
try {
|
|
4258
4269
|
user = new import_mongodb13.ObjectId(user);
|
|
4259
4270
|
} catch (error) {
|
|
@@ -4413,7 +4424,7 @@ function useMemberRepo() {
|
|
|
4413
4424
|
search = search || "";
|
|
4414
4425
|
page = page > 0 ? page - 1 : 0;
|
|
4415
4426
|
const query2 = {
|
|
4416
|
-
type,
|
|
4427
|
+
type: serviceAppType(type),
|
|
4417
4428
|
status
|
|
4418
4429
|
};
|
|
4419
4430
|
const cacheOptions = {
|
|
@@ -4935,6 +4946,7 @@ function useMemberRepo() {
|
|
|
4935
4946
|
}
|
|
4936
4947
|
}
|
|
4937
4948
|
async function getByUserIdTypeOrg(user, type, org) {
|
|
4949
|
+
type = serviceAppType(type);
|
|
4938
4950
|
try {
|
|
4939
4951
|
user = new import_mongodb13.ObjectId(user);
|
|
4940
4952
|
} catch {
|
|
@@ -12442,7 +12454,7 @@ function useRoleRepo() {
|
|
|
12442
12454
|
const { delNamespace, setCache, getCache } = (0, import_node_server_utils42.useCache)(namespace_collection3);
|
|
12443
12455
|
const { delNamespace: _delDashboardNameSpace } = (0, import_node_server_utils42.useCache)("dashboard");
|
|
12444
12456
|
async function addRole(value, session) {
|
|
12445
|
-
value = new MRole(value);
|
|
12457
|
+
value = new MRole({ ...value, type: serviceAppType(value.type) });
|
|
12446
12458
|
try {
|
|
12447
12459
|
const res = await collection.insertOne(value, { session });
|
|
12448
12460
|
delNamespace().then(() => {
|
|
@@ -12545,6 +12557,7 @@ function useRoleRepo() {
|
|
|
12545
12557
|
if (!name) {
|
|
12546
12558
|
throw new import_node_server_utils42.BadRequestError("Role name is required.");
|
|
12547
12559
|
}
|
|
12560
|
+
type = serviceAppType(type);
|
|
12548
12561
|
const query2 = type ? { name, type } : { name };
|
|
12549
12562
|
try {
|
|
12550
12563
|
const cacheKey = (0, import_node_server_utils42.makeCacheKey)(namespace_collection3, query2);
|
|
@@ -12601,8 +12614,8 @@ function useRoleRepo() {
|
|
|
12601
12614
|
cacheOptions.search = search;
|
|
12602
12615
|
}
|
|
12603
12616
|
if (type) {
|
|
12604
|
-
query2.type = type;
|
|
12605
|
-
cacheOptions.type = type;
|
|
12617
|
+
query2.type = serviceAppType(type);
|
|
12618
|
+
cacheOptions.type = query2.type;
|
|
12606
12619
|
}
|
|
12607
12620
|
const cacheKey = (0, import_node_server_utils42.makeCacheKey)(namespace_collection3, cacheOptions);
|
|
12608
12621
|
const cachedData = await getCache(cacheKey);
|
|
@@ -15216,13 +15229,14 @@ function defaultOwnerRoles(org, nature, appOwnerPermissions) {
|
|
|
15216
15229
|
{ ...owner, type: "organization" },
|
|
15217
15230
|
{ ...administrator, type: "organization" }
|
|
15218
15231
|
];
|
|
15219
|
-
|
|
15232
|
+
const appType = serviceAppType(nature);
|
|
15233
|
+
if (appType && APP_SERVICE_TYPES.has(appType)) {
|
|
15220
15234
|
roles.push({
|
|
15221
15235
|
...owner,
|
|
15222
|
-
type:
|
|
15236
|
+
type: appType,
|
|
15223
15237
|
...appOwnerPermissions?.length && { permissions: [...appOwnerPermissions] }
|
|
15224
15238
|
});
|
|
15225
|
-
roles.push({ ...administrator, type:
|
|
15239
|
+
roles.push({ ...administrator, type: appType });
|
|
15226
15240
|
}
|
|
15227
15241
|
return roles;
|
|
15228
15242
|
}
|
|
@@ -15307,15 +15321,17 @@ async function moduleCeiling(org, site) {
|
|
|
15307
15321
|
}
|
|
15308
15322
|
async function ownerGrantCeiling(org, type) {
|
|
15309
15323
|
const orgId = org?.toString() ?? "";
|
|
15324
|
+
type = serviceAppType(type);
|
|
15310
15325
|
if (!orgId || !type)
|
|
15311
15326
|
return null;
|
|
15312
15327
|
try {
|
|
15313
15328
|
const orgDoc = await useOrgRepo().getById(orgId).catch(() => null);
|
|
15314
|
-
|
|
15329
|
+
const appType = serviceAppType(orgDoc?.nature);
|
|
15330
|
+
if (!orgDoc?.invitedRole || appType !== type)
|
|
15315
15331
|
return null;
|
|
15316
15332
|
const role = await useRoleRepo().getRoleById(String(orgDoc.invitedRole)).catch(() => null);
|
|
15317
15333
|
const permissions = role?.permissions;
|
|
15318
|
-
const chosen = role && String(role.org ?? "") === orgId && role.default === true && role.type ===
|
|
15334
|
+
const chosen = role && String(role.org ?? "") === orgId && role.default === true && role.type === appType && Array.isArray(permissions) && permissions.length > 0 && !permissions.includes("*");
|
|
15319
15335
|
return chosen ? permissions.map(String) : null;
|
|
15320
15336
|
} catch {
|
|
15321
15337
|
return null;
|
|
@@ -24198,6 +24214,44 @@ function useMemberController() {
|
|
|
24198
24214
|
// src/controllers/verification.controller.ts
|
|
24199
24215
|
var import_node_server_utils67 = require("@7365admin1/node-server-utils");
|
|
24200
24216
|
var import_joi28 = __toESM(require("joi"));
|
|
24217
|
+
|
|
24218
|
+
// src/utils/resident-app-modules.util.ts
|
|
24219
|
+
var RESIDENT_APP_NATURES = Object.freeze([
|
|
24220
|
+
"property_management_agency",
|
|
24221
|
+
"real_estate_developer"
|
|
24222
|
+
]);
|
|
24223
|
+
function effectiveResidentAppModules(orgModules, siteModules) {
|
|
24224
|
+
const org = asModules(orgModules);
|
|
24225
|
+
const site = asModules(siteModules);
|
|
24226
|
+
const effective = {};
|
|
24227
|
+
for (const key of residentAppModuleKeys) {
|
|
24228
|
+
effective[key] = org[key] !== false && site[key] !== false;
|
|
24229
|
+
}
|
|
24230
|
+
return effective;
|
|
24231
|
+
}
|
|
24232
|
+
function residentAppModulesAboveCeiling(orgModules, requested, stored) {
|
|
24233
|
+
const org = asModules(orgModules);
|
|
24234
|
+
const wanted = asModules(requested);
|
|
24235
|
+
const had = asModules(stored);
|
|
24236
|
+
return residentAppModuleKeys.filter(
|
|
24237
|
+
(key) => org[key] === false && wanted[key] === true && had[key] === false
|
|
24238
|
+
);
|
|
24239
|
+
}
|
|
24240
|
+
function residentAppModulesAboveCeilingMessage(rejected) {
|
|
24241
|
+
return `This client has not been given ${rejected.join(", ")} in the resident app, so a site cannot switch it on. Ask Seven365 to add it to the organisation first.`;
|
|
24242
|
+
}
|
|
24243
|
+
function withoutDerivedSiteMetadata(metadata) {
|
|
24244
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
24245
|
+
return metadata;
|
|
24246
|
+
}
|
|
24247
|
+
const { residentAppModulesEffective: _derived, ...stored } = metadata;
|
|
24248
|
+
return stored;
|
|
24249
|
+
}
|
|
24250
|
+
function asModules(value) {
|
|
24251
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
24252
|
+
}
|
|
24253
|
+
|
|
24254
|
+
// src/controllers/verification.controller.ts
|
|
24201
24255
|
var withoutEmail = (payload) => JSON.stringify({ ...payload, email: payload?.email ? "[redacted]" : void 0 });
|
|
24202
24256
|
function callerId2(req) {
|
|
24203
24257
|
const user = req.user;
|
|
@@ -24279,7 +24333,11 @@ function useVerificationController() {
|
|
|
24279
24333
|
modules: modulesSchema.optional(),
|
|
24280
24334
|
// The new client owner's permissions in that application, chosen with
|
|
24281
24335
|
// the modules (owner, 2026-09-15). Only accepted together with modules.
|
|
24282
|
-
permissions: permissionsSchema.optional()
|
|
24336
|
+
permissions: permissionsSchema.optional(),
|
|
24337
|
+
// The Service365 resident app switches for a Property Management (or
|
|
24338
|
+
// real estate developer) client, same shape the Client details card saves
|
|
24339
|
+
// to `organizations.residentAppModules`. Only accepted with modules.
|
|
24340
|
+
residentAppModules: residentAppModulesSchema.optional()
|
|
24283
24341
|
});
|
|
24284
24342
|
const { error, value } = validation.validate(payload);
|
|
24285
24343
|
if (error) {
|
|
@@ -24299,7 +24357,8 @@ function useVerificationController() {
|
|
|
24299
24357
|
siteId,
|
|
24300
24358
|
siteName,
|
|
24301
24359
|
modules,
|
|
24302
|
-
permissions
|
|
24360
|
+
permissions,
|
|
24361
|
+
residentAppModules
|
|
24303
24362
|
} = value;
|
|
24304
24363
|
const newClient = !org && !app.includes(PLATFORM_STAFF_MEMBER_TYPE);
|
|
24305
24364
|
const role = newClient ? "" : value.role;
|
|
@@ -24329,6 +24388,14 @@ function useVerificationController() {
|
|
|
24329
24388
|
}
|
|
24330
24389
|
clientModules.permissions = chosen;
|
|
24331
24390
|
}
|
|
24391
|
+
if (residentAppModules !== void 0) {
|
|
24392
|
+
if (!clientModules || !RESIDENT_APP_NATURES.includes(app[0])) {
|
|
24393
|
+
throw new import_node_server_utils67.BadRequestError(
|
|
24394
|
+
"Service365 resident app modules can only be chosen with the modules of a new Property Management client."
|
|
24395
|
+
);
|
|
24396
|
+
}
|
|
24397
|
+
clientModules.residentAppModules = residentAppModules;
|
|
24398
|
+
}
|
|
24332
24399
|
await requireInviteReach(req, org, app, role);
|
|
24333
24400
|
const verificationIds = await _createSimpleUserInvite({
|
|
24334
24401
|
email,
|
|
@@ -26145,10 +26212,12 @@ function useOrgController() {
|
|
|
26145
26212
|
const invitedBy = invite.metadata?.invitedBy;
|
|
26146
26213
|
if (chosen.length && invitedBy && invite.metadata?.app === body.nature) {
|
|
26147
26214
|
const permissions = clientInviteOwnerPermissionsOn() ? normalisePermissions(invite.metadata?.permissions) : [];
|
|
26215
|
+
const residentApp = RESIDENT_APP_NATURES.includes(body.nature) ? residentAppModulesSchema.validate(invite.metadata?.residentAppModules) : null;
|
|
26148
26216
|
invitedModules = {
|
|
26149
26217
|
modules: chosen,
|
|
26150
26218
|
invitedBy: String(invitedBy),
|
|
26151
|
-
permissions: permissionsOutsideModules(permissions, chosen).length ? [] : permissions
|
|
26219
|
+
permissions: permissionsOutsideModules(permissions, chosen).length ? [] : permissions,
|
|
26220
|
+
...residentApp?.value && !residentApp.error ? { residentAppModules: residentApp.value } : {}
|
|
26152
26221
|
};
|
|
26153
26222
|
}
|
|
26154
26223
|
} else {
|
|
@@ -26171,7 +26240,10 @@ function useOrgController() {
|
|
|
26171
26240
|
createdBy,
|
|
26172
26241
|
invitedFrom,
|
|
26173
26242
|
invitedRole,
|
|
26174
|
-
...invitedModules && { modules: invitedModules.modules }
|
|
26243
|
+
...invitedModules && { modules: invitedModules.modules },
|
|
26244
|
+
...invitedModules?.residentAppModules && {
|
|
26245
|
+
residentAppModules: invitedModules.residentAppModules
|
|
26246
|
+
}
|
|
26175
26247
|
});
|
|
26176
26248
|
if (invitedModules) {
|
|
26177
26249
|
await recordConsoleAction({
|
|
@@ -26185,7 +26257,10 @@ function useOrgController() {
|
|
|
26185
26257
|
moduleCount: invitedModules.modules.length,
|
|
26186
26258
|
gateListHash: moduleListHash(invitedModules.modules),
|
|
26187
26259
|
acknowledged: false,
|
|
26188
|
-
impactMemberCount: 0
|
|
26260
|
+
impactMemberCount: 0,
|
|
26261
|
+
...invitedModules.residentAppModules && {
|
|
26262
|
+
residentAppModulesOff: Object.keys(invitedModules.residentAppModules).filter((key) => invitedModules.residentAppModules[key] === false).join(", ")
|
|
26263
|
+
}
|
|
26189
26264
|
})
|
|
26190
26265
|
});
|
|
26191
26266
|
}
|
|
@@ -26208,7 +26283,7 @@ function useOrgController() {
|
|
|
26208
26283
|
invitedModules?.permissions
|
|
26209
26284
|
);
|
|
26210
26285
|
const appOwnerRole = invitedModules?.permissions.length ? seededRoles.find(
|
|
26211
|
-
(role) => role.type === body.nature && role.name === "owner"
|
|
26286
|
+
(role) => role.type === serviceAppType(body.nature) && role.name === "owner"
|
|
26212
26287
|
) : void 0;
|
|
26213
26288
|
if (appOwnerRole) {
|
|
26214
26289
|
try {
|
|
@@ -34788,40 +34863,6 @@ function useSiteService() {
|
|
|
34788
34863
|
|
|
34789
34864
|
// src/controllers/site.controller.ts
|
|
34790
34865
|
var import_node_server_utils109 = require("@7365admin1/node-server-utils");
|
|
34791
|
-
|
|
34792
|
-
// src/utils/resident-app-modules.util.ts
|
|
34793
|
-
function effectiveResidentAppModules(orgModules, siteModules) {
|
|
34794
|
-
const org = asModules(orgModules);
|
|
34795
|
-
const site = asModules(siteModules);
|
|
34796
|
-
const effective = {};
|
|
34797
|
-
for (const key of residentAppModuleKeys) {
|
|
34798
|
-
effective[key] = org[key] !== false && site[key] !== false;
|
|
34799
|
-
}
|
|
34800
|
-
return effective;
|
|
34801
|
-
}
|
|
34802
|
-
function residentAppModulesAboveCeiling(orgModules, requested, stored) {
|
|
34803
|
-
const org = asModules(orgModules);
|
|
34804
|
-
const wanted = asModules(requested);
|
|
34805
|
-
const had = asModules(stored);
|
|
34806
|
-
return residentAppModuleKeys.filter(
|
|
34807
|
-
(key) => org[key] === false && wanted[key] === true && had[key] === false
|
|
34808
|
-
);
|
|
34809
|
-
}
|
|
34810
|
-
function residentAppModulesAboveCeilingMessage(rejected) {
|
|
34811
|
-
return `This client has not been given ${rejected.join(", ")} in the resident app, so a site cannot switch it on. Ask Seven365 to add it to the organisation first.`;
|
|
34812
|
-
}
|
|
34813
|
-
function withoutDerivedSiteMetadata(metadata) {
|
|
34814
|
-
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
34815
|
-
return metadata;
|
|
34816
|
-
}
|
|
34817
|
-
const { residentAppModulesEffective: _derived, ...stored } = metadata;
|
|
34818
|
-
return stored;
|
|
34819
|
-
}
|
|
34820
|
-
function asModules(value) {
|
|
34821
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
34822
|
-
}
|
|
34823
|
-
|
|
34824
|
-
// src/controllers/site.controller.ts
|
|
34825
34866
|
var import_joi51 = __toESM(require("joi"));
|
|
34826
34867
|
var import_mongodb74 = require("mongodb");
|
|
34827
34868
|
async function requireSiteWrite(req, siteId) {
|