@7365admin1/core 3.138.0 → 3.139.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 +6 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +168 -66
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +168 -66
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/service-provider-invite-email-new-company.e2e.test.mjs +105 -1
- package/test/member-working-membership.test.mjs +28 -0
package/dist/index.mjs
CHANGED
|
@@ -3200,6 +3200,53 @@ function sameAppType(a, b) {
|
|
|
3200
3200
|
return !!a && !!b && serviceAppType(a) === serviceAppType(b);
|
|
3201
3201
|
}
|
|
3202
3202
|
|
|
3203
|
+
// src/utils/member-lookup.util.ts
|
|
3204
|
+
var idOf = (v) => v == null ? "" : String(v?._id ?? v).toLowerCase();
|
|
3205
|
+
function isLiveMembership(m) {
|
|
3206
|
+
return m.status !== "deleted" && !m.deletedAt;
|
|
3207
|
+
}
|
|
3208
|
+
function pickSiteMembership(rows, type, site, org = "") {
|
|
3209
|
+
site = site.toLowerCase();
|
|
3210
|
+
org = org.toLowerCase();
|
|
3211
|
+
if (!site)
|
|
3212
|
+
return null;
|
|
3213
|
+
const live2 = rows.filter(
|
|
3214
|
+
(m) => sameAppType(m.type, type) && isLiveMembership(m) && (!org || idOf(m.org) === org)
|
|
3215
|
+
);
|
|
3216
|
+
const atSite = live2.find((m) => idOf(m.siteId) === site);
|
|
3217
|
+
if (atSite)
|
|
3218
|
+
return atSite;
|
|
3219
|
+
if (!org)
|
|
3220
|
+
return null;
|
|
3221
|
+
return live2.find((m) => !idOf(m.siteId)) ?? live2[0] ?? null;
|
|
3222
|
+
}
|
|
3223
|
+
function pickLookupMembership(rows, type, site = "", org = "") {
|
|
3224
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
3225
|
+
const atSite = site ? pickSiteMembership(list, type, site, org) : null;
|
|
3226
|
+
if (atSite)
|
|
3227
|
+
return atSite;
|
|
3228
|
+
const inOrg = org ? list.find((m) => sameAppType(m.type, type) && idOf(m.org) === org.toLowerCase()) : void 0;
|
|
3229
|
+
if (inOrg && isLiveMembership(inOrg))
|
|
3230
|
+
return inOrg;
|
|
3231
|
+
return preferWorkingMembership(list.filter((m) => sameAppType(m.type, type)));
|
|
3232
|
+
}
|
|
3233
|
+
var WORKING = /* @__PURE__ */ new Set(["active", "approved"]);
|
|
3234
|
+
function preferWorkingMembership(rows, defaultOrg = "") {
|
|
3235
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
3236
|
+
const working = list.filter((m) => WORKING.has(String(m.status ?? "")) && !m.deletedAt);
|
|
3237
|
+
const home = idOf(defaultOrg);
|
|
3238
|
+
return (home ? working.find((m) => idOf(m.org) === home) : void 0) ?? working[0] ?? list.find(isLiveMembership) ?? list[0] ?? null;
|
|
3239
|
+
}
|
|
3240
|
+
function memberListSiteScope(memberships, org) {
|
|
3241
|
+
const inOrg = (memberships ?? []).filter((m) => idOf(m.org) === idOf(org));
|
|
3242
|
+
const here = inOrg.filter((m) => m.type !== "resident");
|
|
3243
|
+
if (!inOrg.length)
|
|
3244
|
+
return null;
|
|
3245
|
+
if (here.some((m) => !idOf(m.siteId) || m.orgLevelRole))
|
|
3246
|
+
return null;
|
|
3247
|
+
return [...new Set(here.map((m) => idOf(m.siteId)))];
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3203
3250
|
// src/repositories/member.repo.ts
|
|
3204
3251
|
import {
|
|
3205
3252
|
AppError as AppError2,
|
|
@@ -3332,10 +3379,10 @@ import { ObjectId as ObjectId13 } from "mongodb";
|
|
|
3332
3379
|
function crossSiteRoleMode() {
|
|
3333
3380
|
return (process.env.CROSS_SITE_ROLE_MODE ?? "").trim().toLowerCase() === "enforce" ? "enforce" : "log";
|
|
3334
3381
|
}
|
|
3335
|
-
var
|
|
3382
|
+
var idOf2 = (value) => value === null || value === void 0 ? "" : String(value?.toString?.() ?? value);
|
|
3336
3383
|
function isCrossSiteRole(role, memberSite) {
|
|
3337
|
-
const roleSite =
|
|
3338
|
-
return !!roleSite && roleSite !==
|
|
3384
|
+
const roleSite = idOf2(role?.site);
|
|
3385
|
+
return !!roleSite && roleSite !== idOf2(memberSite);
|
|
3339
3386
|
}
|
|
3340
3387
|
var THROTTLE_MS = 10 * 60 * 1e3;
|
|
3341
3388
|
var lastLogged = /* @__PURE__ */ new Map();
|
|
@@ -3344,7 +3391,7 @@ function crossSiteRoleGrants(input) {
|
|
|
3344
3391
|
return true;
|
|
3345
3392
|
const mode = crossSiteRoleMode();
|
|
3346
3393
|
const granted = mode !== "enforce";
|
|
3347
|
-
const key = [mode,
|
|
3394
|
+
const key = [mode, idOf2(input.member) || idOf2(input.user), idOf2(input.role?._id), input.where].join("|");
|
|
3348
3395
|
const now = Date.now();
|
|
3349
3396
|
if ((lastLogged.get(key) ?? 0) + THROTTLE_MS <= now) {
|
|
3350
3397
|
lastLogged.set(key, now);
|
|
@@ -3352,7 +3399,7 @@ function crossSiteRoleGrants(input) {
|
|
|
3352
3399
|
lastLogged.clear();
|
|
3353
3400
|
logger7.log({
|
|
3354
3401
|
level: "warn",
|
|
3355
|
-
message: `cross-site-role mode=${mode} ${granted ? "granted" : "refused"} where=${input.where} member=${
|
|
3402
|
+
message: `cross-site-role mode=${mode} ${granted ? "granted" : "refused"} where=${input.where} member=${idOf2(input.member) || "-"} user=${idOf2(input.user) || "-"} site=${idOf2(input.memberSite) || "-"} role=${idOf2(input.role?._id) || "-"} roleSite=${idOf2(input.role?.site)}`
|
|
3356
3403
|
});
|
|
3357
3404
|
}
|
|
3358
3405
|
return granted;
|
|
@@ -3363,7 +3410,7 @@ function crossSiteRoleRefusal(roleName, roleSiteName) {
|
|
|
3363
3410
|
return `${role} belongs to ${site}. Choose a role for this site, or an org-wide role.`;
|
|
3364
3411
|
}
|
|
3365
3412
|
async function requireRoleFitsSite(roleId, siteId) {
|
|
3366
|
-
const id =
|
|
3413
|
+
const id = idOf2(roleId);
|
|
3367
3414
|
if (!id || !ObjectId13.isValid(id))
|
|
3368
3415
|
return;
|
|
3369
3416
|
const db2 = useAtlas7.getDb();
|
|
@@ -3372,7 +3419,7 @@ async function requireRoleFitsSite(roleId, siteId) {
|
|
|
3372
3419
|
const role = await db2.collection("roles").findOne({ _id: new ObjectId13(id) }, { projection: { name: 1, site: 1 } });
|
|
3373
3420
|
if (!isCrossSiteRole(role, siteId))
|
|
3374
3421
|
return;
|
|
3375
|
-
const site = ObjectId13.isValid(
|
|
3422
|
+
const site = ObjectId13.isValid(idOf2(role?.site)) ? await db2.collection("sites").findOne({ _id: new ObjectId13(idOf2(role?.site)) }, { projection: { name: 1 } }) : null;
|
|
3376
3423
|
throw new BadRequestError13(crossSiteRoleRefusal(role?.name, site?.name));
|
|
3377
3424
|
}
|
|
3378
3425
|
async function otherSiteRoleClauses(roles, org) {
|
|
@@ -3380,7 +3427,7 @@ async function otherSiteRoleClauses(roles, org) {
|
|
|
3380
3427
|
{ ...org ? { org } : { org: { $nin: ["", null] } }, site: { $nin: ["", null] }, status: "active" },
|
|
3381
3428
|
{ projection: { site: 1, org: 1 } }
|
|
3382
3429
|
).toArray();
|
|
3383
|
-
const both3 = (v) => ObjectId13.isValid(
|
|
3430
|
+
const both3 = (v) => ObjectId13.isValid(idOf2(v)) ? [new ObjectId13(idOf2(v)), idOf2(v)] : [v];
|
|
3384
3431
|
const clauses = (list ?? []).map((r) => ({
|
|
3385
3432
|
role: r._id,
|
|
3386
3433
|
org: { $in: both3(r.org) },
|
|
@@ -3563,9 +3610,12 @@ function useMemberRepo() {
|
|
|
3563
3610
|
} catch (error) {
|
|
3564
3611
|
throw new BadRequestError14("Invalid user ID format.");
|
|
3565
3612
|
}
|
|
3613
|
+
const account = await db2?.collection("users").findOne({ _id: user }, { projection: { defaultOrg: 1 } }).catch(() => null);
|
|
3614
|
+
const defaultOrg = String(account?.defaultOrg ?? "");
|
|
3566
3615
|
const cacheKey = makeCacheKey5(namespace_collection3, {
|
|
3567
3616
|
user: user.toString(),
|
|
3568
|
-
type
|
|
3617
|
+
type,
|
|
3618
|
+
defaultOrg
|
|
3569
3619
|
});
|
|
3570
3620
|
const cachedData = await getCache(cacheKey);
|
|
3571
3621
|
if (cachedData) {
|
|
@@ -3573,7 +3623,10 @@ function useMemberRepo() {
|
|
|
3573
3623
|
return cachedData;
|
|
3574
3624
|
}
|
|
3575
3625
|
try {
|
|
3576
|
-
const data =
|
|
3626
|
+
const data = preferWorkingMembership(
|
|
3627
|
+
await collection.find({ user, type: appTypeQuery(type) }).toArray(),
|
|
3628
|
+
defaultOrg
|
|
3629
|
+
);
|
|
3577
3630
|
if (!data) {
|
|
3578
3631
|
throw new NotFoundError5("Member not found.");
|
|
3579
3632
|
}
|
|
@@ -4036,7 +4089,12 @@ function useMemberRepo() {
|
|
|
4036
4089
|
const query2 = {
|
|
4037
4090
|
user,
|
|
4038
4091
|
org: { $nin: [null, ""] },
|
|
4039
|
-
type: appTypeQuery(type)
|
|
4092
|
+
type: appTypeQuery(type),
|
|
4093
|
+
// The organisation switcher. A membership that was taken away (Repair
|
|
4094
|
+
// sets it inactive, removal deletes it) must not keep the organisation
|
|
4095
|
+
// on the person's list: latitude.sec@ still saw JLL Singapore beside its
|
|
4096
|
+
// own company after the repair (owner report 2026-09-30).
|
|
4097
|
+
status: { $nin: ["inactive", "deleted"] }
|
|
4040
4098
|
};
|
|
4041
4099
|
const cacheOptions = {
|
|
4042
4100
|
page: page2,
|
|
@@ -10541,7 +10599,7 @@ function enforcedModuleList(stored, save) {
|
|
|
10541
10599
|
return NO_LIST;
|
|
10542
10600
|
return { list, since: typeof save?.createdAt === "string" ? save.createdAt : "" };
|
|
10543
10601
|
}
|
|
10544
|
-
var
|
|
10602
|
+
var idOf3 = (v) => v == null ? "" : String(v?._id ?? v).toLowerCase();
|
|
10545
10603
|
function deniedForMembership({
|
|
10546
10604
|
membership,
|
|
10547
10605
|
org,
|
|
@@ -10549,12 +10607,12 @@ function deniedForMembership({
|
|
|
10549
10607
|
site,
|
|
10550
10608
|
siteSave
|
|
10551
10609
|
}) {
|
|
10552
|
-
const memberOrg =
|
|
10610
|
+
const memberOrg = idOf3(membership?.org);
|
|
10553
10611
|
if (!membership || membership.type === "resident" || !memberOrg) {
|
|
10554
10612
|
return { denied: [], since: "" };
|
|
10555
10613
|
}
|
|
10556
10614
|
const orgTier = enforcedModuleList(org?.modules, orgSave);
|
|
10557
|
-
const siteTier = site &&
|
|
10615
|
+
const siteTier = site && idOf3(site.orgId) === memberOrg ? enforcedModuleList(site.metadata?.modules, siteSave) : NO_LIST;
|
|
10558
10616
|
const denied = deniedModules(orgTier.list, siteTier.list);
|
|
10559
10617
|
if (!denied.length)
|
|
10560
10618
|
return { denied: [], since: "" };
|
|
@@ -11128,46 +11186,6 @@ function staffMayBypass(actor, grant = CROSS_CLIENT_GRANT) {
|
|
|
11128
11186
|
return consoleRoleAllows(actor.staffPermissions, grant.resource, grant.action);
|
|
11129
11187
|
}
|
|
11130
11188
|
|
|
11131
|
-
// src/utils/member-lookup.util.ts
|
|
11132
|
-
var idOf3 = (v) => v == null ? "" : String(v?._id ?? v).toLowerCase();
|
|
11133
|
-
function isLiveMembership(m) {
|
|
11134
|
-
return m.status !== "deleted" && !m.deletedAt;
|
|
11135
|
-
}
|
|
11136
|
-
function pickSiteMembership(rows, type, site, org = "") {
|
|
11137
|
-
site = site.toLowerCase();
|
|
11138
|
-
org = org.toLowerCase();
|
|
11139
|
-
if (!site)
|
|
11140
|
-
return null;
|
|
11141
|
-
const live2 = rows.filter(
|
|
11142
|
-
(m) => sameAppType(m.type, type) && isLiveMembership(m) && (!org || idOf3(m.org) === org)
|
|
11143
|
-
);
|
|
11144
|
-
const atSite = live2.find((m) => idOf3(m.siteId) === site);
|
|
11145
|
-
if (atSite)
|
|
11146
|
-
return atSite;
|
|
11147
|
-
if (!org)
|
|
11148
|
-
return null;
|
|
11149
|
-
return live2.find((m) => !idOf3(m.siteId)) ?? live2[0] ?? null;
|
|
11150
|
-
}
|
|
11151
|
-
function pickLookupMembership(rows, type, site = "", org = "") {
|
|
11152
|
-
const list = Array.isArray(rows) ? rows : [];
|
|
11153
|
-
const atSite = site ? pickSiteMembership(list, type, site, org) : null;
|
|
11154
|
-
if (atSite)
|
|
11155
|
-
return atSite;
|
|
11156
|
-
const inOrg = org ? list.find((m) => sameAppType(m.type, type) && idOf3(m.org) === org.toLowerCase()) : void 0;
|
|
11157
|
-
if (inOrg && isLiveMembership(inOrg))
|
|
11158
|
-
return inOrg;
|
|
11159
|
-
return list.find((m) => sameAppType(m.type, type)) ?? null;
|
|
11160
|
-
}
|
|
11161
|
-
function memberListSiteScope(memberships, org) {
|
|
11162
|
-
const inOrg = (memberships ?? []).filter((m) => idOf3(m.org) === idOf3(org));
|
|
11163
|
-
const here = inOrg.filter((m) => m.type !== "resident");
|
|
11164
|
-
if (!inOrg.length)
|
|
11165
|
-
return null;
|
|
11166
|
-
if (here.some((m) => !idOf3(m.siteId) || m.orgLevelRole))
|
|
11167
|
-
return null;
|
|
11168
|
-
return [...new Set(here.map((m) => idOf3(m.siteId)))];
|
|
11169
|
-
}
|
|
11170
|
-
|
|
11171
11189
|
// src/services/module-gate.service.ts
|
|
11172
11190
|
var idOf4 = (v) => v == null ? "" : String(v?._id ?? v).toLowerCase();
|
|
11173
11191
|
var isId = (v) => /^[0-9a-f]{24}$/i.test(v);
|
|
@@ -24560,7 +24578,7 @@ function useServiceProviderInviteService() {
|
|
|
24560
24578
|
);
|
|
24561
24579
|
}
|
|
24562
24580
|
const existing = await getByCreatorAndNature(actor.id, nature).catch(() => null);
|
|
24563
|
-
if (existing) {
|
|
24581
|
+
if (existing && String(existing.invitedFrom ?? "") !== String(invite._id)) {
|
|
24564
24582
|
throw new BadRequestError47(
|
|
24565
24583
|
`You already have a ${serviceTypeLabel(nature)} company on iService365 (${existing.name}). Accept the invitation from that company instead.`
|
|
24566
24584
|
);
|
|
@@ -24591,12 +24609,21 @@ function useServiceProviderInviteService() {
|
|
|
24591
24609
|
country
|
|
24592
24610
|
}) {
|
|
24593
24611
|
const inviteeId = String(user?._id ?? "");
|
|
24594
|
-
|
|
24612
|
+
let name = sanitizeCompanyName(companyName) || sanitizeCompanyName(invite.metadata?.name) || sanitizeCompanyName(user?.name);
|
|
24595
24613
|
if (!name)
|
|
24596
24614
|
throw new BadRequestError47("Enter your company's name.");
|
|
24597
24615
|
const email = String(invite.email ?? "").trim().toLowerCase();
|
|
24598
24616
|
const actor = { id: inviteeId };
|
|
24599
|
-
const
|
|
24617
|
+
const db2 = useAtlas38.getDb();
|
|
24618
|
+
const actorIds = [actor.id, ...ObjectId56.isValid(actor.id) ? [new ObjectId56(actor.id)] : []];
|
|
24619
|
+
const made = db2 ? await db2.collection("organizations").findOne({
|
|
24620
|
+
invitedFrom: { $in: [String(invite._id), invite._id] },
|
|
24621
|
+
createdBy: { $in: actorIds },
|
|
24622
|
+
status: { $ne: "deleted" }
|
|
24623
|
+
}) : null;
|
|
24624
|
+
if (made)
|
|
24625
|
+
name = String(made.name ?? name);
|
|
24626
|
+
const orgId = made ? String(made._id) : String(
|
|
24600
24627
|
await addOrg({
|
|
24601
24628
|
name,
|
|
24602
24629
|
type: nature,
|
|
@@ -24608,8 +24635,17 @@ function useServiceProviderInviteService() {
|
|
|
24608
24635
|
invitedFrom: String(invite._id)
|
|
24609
24636
|
})
|
|
24610
24637
|
);
|
|
24611
|
-
const roles = await seedDefaultOrgRoles(useRoleRepo().addRole, orgId, nature);
|
|
24612
24638
|
const appType = serviceAppType(nature);
|
|
24639
|
+
const owners = made && db2 ? await db2.collection("roles").find({
|
|
24640
|
+
org: { $in: [new ObjectId56(orgId), orgId] },
|
|
24641
|
+
name: "owner",
|
|
24642
|
+
status: { $ne: "deleted" }
|
|
24643
|
+
}).toArray() : [];
|
|
24644
|
+
const ownerRole = (type) => {
|
|
24645
|
+
const r = owners.find((x) => x.type === type);
|
|
24646
|
+
return r ? { id: String(r._id), type, name: "owner" } : null;
|
|
24647
|
+
};
|
|
24648
|
+
const roles = ownerRole("organization") && ownerRole(appType) ? [ownerRole("organization"), ownerRole(appType)] : await seedDefaultOrgRoles(useRoleRepo().addRole, orgId, nature);
|
|
24613
24649
|
const orgOwner = roles.find((r) => r.type === "organization" && r.name === "owner");
|
|
24614
24650
|
const appOwner = roles.find((r) => r.type === appType && r.name === "owner");
|
|
24615
24651
|
if (!orgOwner || !appOwner) {
|
|
@@ -24632,8 +24668,18 @@ function useServiceProviderInviteService() {
|
|
|
24632
24668
|
siteName: "",
|
|
24633
24669
|
onboardingRequired: false
|
|
24634
24670
|
};
|
|
24635
|
-
|
|
24636
|
-
|
|
24671
|
+
const hasOwnerRow = async (type) => !!made && !!await db2?.collection("members").findOne({
|
|
24672
|
+
user: { $in: actorIds },
|
|
24673
|
+
org: { $in: [new ObjectId56(orgId), orgId] },
|
|
24674
|
+
type,
|
|
24675
|
+
status: "active"
|
|
24676
|
+
});
|
|
24677
|
+
if (!await hasOwnerRow("organization")) {
|
|
24678
|
+
await addMember({ ...base, role: orgOwner.id, type: "organization" });
|
|
24679
|
+
}
|
|
24680
|
+
if (!await hasOwnerRow(appType)) {
|
|
24681
|
+
await addMember({ ...base, role: appOwner.id, type: appType });
|
|
24682
|
+
}
|
|
24637
24683
|
await useUserRepo().updateUserFieldById({ _id: actor.id, field: "defaultOrg", value: orgId }).catch(() => void 0);
|
|
24638
24684
|
logger36.log({
|
|
24639
24685
|
level: "info",
|
|
@@ -24689,10 +24735,60 @@ function useServiceProviderInviteService() {
|
|
|
24689
24735
|
const last = [...history].reverse().find((e) => ["wrong-join-repaired", "wrong-join-undone"].includes(e?.action));
|
|
24690
24736
|
return last?.action === "wrong-join-repaired" ? last : null;
|
|
24691
24737
|
}
|
|
24738
|
+
async function leftoverRoles(invite, senderOrg, user) {
|
|
24739
|
+
const db2 = useAtlas38.getDb();
|
|
24740
|
+
if (!db2 || !user?._id || !ObjectId56.isValid(senderOrg))
|
|
24741
|
+
return [];
|
|
24742
|
+
const since = new Date(String(invite.createdAt ?? 0)).getTime();
|
|
24743
|
+
const rows = await db2.collection("members").find({
|
|
24744
|
+
user: { $in: [user._id, String(user._id)] },
|
|
24745
|
+
org: { $in: [new ObjectId56(senderOrg), senderOrg] }
|
|
24746
|
+
}).project({ role: 1 }).toArray();
|
|
24747
|
+
const roleIds = [
|
|
24748
|
+
...new Set(
|
|
24749
|
+
rows.map((m) => m.role?.toString?.() ?? "").filter((r) => ObjectId56.isValid(r))
|
|
24750
|
+
)
|
|
24751
|
+
].map((r) => new ObjectId56(r));
|
|
24752
|
+
if (!roleIds.length)
|
|
24753
|
+
return [];
|
|
24754
|
+
const roles = await db2.collection("roles").find({
|
|
24755
|
+
_id: { $in: roleIds },
|
|
24756
|
+
org: { $in: [new ObjectId56(senderOrg), senderOrg] },
|
|
24757
|
+
status: { $ne: "deleted" }
|
|
24758
|
+
}).project({ name: 1, permissions: 1, createdAt: 1 }).toArray();
|
|
24759
|
+
const out = [];
|
|
24760
|
+
for (const role of roles) {
|
|
24761
|
+
if (!(Array.isArray(role.permissions) && role.permissions.includes("*")))
|
|
24762
|
+
continue;
|
|
24763
|
+
if (role.createdAt && Number.isFinite(since) && new Date(role.createdAt).getTime() < since)
|
|
24764
|
+
continue;
|
|
24765
|
+
const inUse = await db2.collection("members").countDocuments({
|
|
24766
|
+
role: { $in: [role._id, role._id.toString()] },
|
|
24767
|
+
status: "active"
|
|
24768
|
+
});
|
|
24769
|
+
if (inUse)
|
|
24770
|
+
continue;
|
|
24771
|
+
out.push({ _id: role._id.toString(), name: String(role.name ?? "") });
|
|
24772
|
+
}
|
|
24773
|
+
return out;
|
|
24774
|
+
}
|
|
24775
|
+
async function setRoleStatus(ids2, status) {
|
|
24776
|
+
const db2 = useAtlas38.getDb();
|
|
24777
|
+
const valid = ids2.filter((r) => ObjectId56.isValid(r)).map((r) => new ObjectId56(r));
|
|
24778
|
+
if (!db2 || !valid.length)
|
|
24779
|
+
return;
|
|
24780
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
24781
|
+
await db2.collection("roles").updateMany(
|
|
24782
|
+
{ _id: { $in: valid } },
|
|
24783
|
+
{ $set: { status, deletedAt: status === "deleted" ? now : "", updatedAt: now } }
|
|
24784
|
+
);
|
|
24785
|
+
await useCache21("roles").delNamespace().catch(() => void 0);
|
|
24786
|
+
}
|
|
24692
24787
|
async function wrongJoinPreview({ id, userId }) {
|
|
24693
24788
|
const { invite, senderOrg, user } = await wrongJoinContext(id, userId);
|
|
24694
24789
|
const org = await getOrgById(senderOrg).catch(() => null);
|
|
24695
24790
|
const memberships = await wrongJoinRows(invite, senderOrg, user);
|
|
24791
|
+
const roles = await leftoverRoles(invite, senderOrg, user);
|
|
24696
24792
|
const repaired = lastRepair(invite);
|
|
24697
24793
|
return {
|
|
24698
24794
|
invitationId: id,
|
|
@@ -24701,6 +24797,8 @@ function useServiceProviderInviteService() {
|
|
|
24701
24797
|
senderOrgId: senderOrg,
|
|
24702
24798
|
senderOrgName: String(org?.name ?? ""),
|
|
24703
24799
|
memberships,
|
|
24800
|
+
// Left in the sender's role list by the old sign-up; Repair retires them.
|
|
24801
|
+
leftoverRoles: roles,
|
|
24704
24802
|
defaultOrgIsSender: String(user?.defaultOrg ?? "") === senderOrg,
|
|
24705
24803
|
repaired: repaired ? { at: repaired.at, by: repaired.byName, memberIds: repaired.memberIds ?? [] } : null
|
|
24706
24804
|
};
|
|
@@ -24709,15 +24807,17 @@ function useServiceProviderInviteService() {
|
|
|
24709
24807
|
const { actor, invite, senderOrg, user } = await wrongJoinContext(id, userId);
|
|
24710
24808
|
const rows = await wrongJoinRows(invite, senderOrg, user);
|
|
24711
24809
|
const defaultOrgIsSender = !!user?._id && String(user?.defaultOrg ?? "") === senderOrg;
|
|
24712
|
-
|
|
24810
|
+
const { updateMemberStatus } = useMemberRepo();
|
|
24811
|
+
for (const row of rows)
|
|
24812
|
+
await updateMemberStatus(row._id, { status: "inactive" });
|
|
24813
|
+
const roles = await leftoverRoles(invite, senderOrg, user);
|
|
24814
|
+
if (!rows.length && !defaultOrgIsSender && !roles.length) {
|
|
24713
24815
|
return {
|
|
24714
24816
|
message: "Nothing to repair: this account is not in the sender's organisation.",
|
|
24715
24817
|
changed: 0
|
|
24716
24818
|
};
|
|
24717
24819
|
}
|
|
24718
|
-
|
|
24719
|
-
for (const row of rows)
|
|
24720
|
-
await updateMemberStatus(row._id, { status: "inactive" });
|
|
24820
|
+
await setRoleStatus(roles.map((r) => r._id), "deleted");
|
|
24721
24821
|
if (defaultOrgIsSender)
|
|
24722
24822
|
await setDefaultOrg(String(user._id), "");
|
|
24723
24823
|
await _transition({
|
|
@@ -24726,6 +24826,7 @@ function useServiceProviderInviteService() {
|
|
|
24726
24826
|
event: {
|
|
24727
24827
|
...event("wrong-join-repaired", actor),
|
|
24728
24828
|
memberIds: rows.map((row) => row._id),
|
|
24829
|
+
roleIds: roles.map((r) => r._id),
|
|
24729
24830
|
...defaultOrgIsSender ? { defaultOrgWas: senderOrg } : {}
|
|
24730
24831
|
}
|
|
24731
24832
|
});
|
|
@@ -24734,8 +24835,8 @@ function useServiceProviderInviteService() {
|
|
|
24734
24835
|
message: `[sp-invite] wrong-join repaired invite=${id} memberships=${rows.length} by=${actor.id}`
|
|
24735
24836
|
});
|
|
24736
24837
|
return {
|
|
24737
|
-
message: `Repaired: ${rows.length} membership${rows.length === 1 ? "" : "s"} in the sender's organisation set inactive. Undo is available
|
|
24738
|
-
changed: rows.length
|
|
24838
|
+
message: `Repaired: ${rows.length} membership${rows.length === 1 ? "" : "s"} in the sender's organisation set inactive` + (roles.length ? `, ${roles.length} unused full-access role${roles.length === 1 ? "" : "s"} removed from its role list` : "") + ". Undo is available.",
|
|
24839
|
+
changed: rows.length + roles.length
|
|
24739
24840
|
};
|
|
24740
24841
|
}
|
|
24741
24842
|
async function undoWrongJoin({ id, userId }) {
|
|
@@ -24747,6 +24848,7 @@ function useServiceProviderInviteService() {
|
|
|
24747
24848
|
const ids2 = (repaired.memberIds ?? []).filter((m) => ObjectId56.isValid(m));
|
|
24748
24849
|
for (const m of ids2)
|
|
24749
24850
|
await updateMemberStatus(m, { status: "active" }).catch(() => 0);
|
|
24851
|
+
await setRoleStatus(repaired.roleIds ?? [], "active");
|
|
24750
24852
|
const { user } = await wrongJoinContext(id, userId);
|
|
24751
24853
|
if (repaired.defaultOrgWas && user?._id && !String(user.defaultOrg ?? "")) {
|
|
24752
24854
|
await setDefaultOrg(String(user._id), String(repaired.defaultOrgWas));
|