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