@7365admin1/core 3.134.0 → 3.135.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/dist/index.mjs CHANGED
@@ -4286,7 +4286,7 @@ function useMemberRepo() {
4286
4286
  throw error;
4287
4287
  }
4288
4288
  }
4289
- async function updateRoleById(_id, role) {
4289
+ async function updateRoleById(_id, role, createdBy) {
4290
4290
  try {
4291
4291
  _id = new ObjectId14(_id);
4292
4292
  } catch (error) {
@@ -4302,6 +4302,9 @@ function useMemberRepo() {
4302
4302
  role,
4303
4303
  updatedAt: /* @__PURE__ */ new Date()
4304
4304
  };
4305
+ if (createdBy && ObjectId14.isValid(String(createdBy))) {
4306
+ updateValue.createdBy = new ObjectId14(String(createdBy));
4307
+ }
4305
4308
  const res = await collection.updateOne({ _id }, { $set: updateValue });
4306
4309
  if (res.modifiedCount === 0) {
4307
4310
  throw new InternalServerError6("Unable to update feedback status.");
@@ -8833,11 +8836,27 @@ async function siteInvitedPins(db2, userId, memberships) {
8833
8836
  const keys = /* @__PURE__ */ new Set();
8834
8837
  if (!pinned.length || !ObjectId26.isValid(userId))
8835
8838
  return keys;
8839
+ const both3 = (ids2) => [...new Set(ids2.map(String))].flatMap((v) => [v, new ObjectId26(v)]);
8840
+ const wanted = new Set(pinned.map((m) => pinKey(m.org, m.siteId)));
8841
+ const placed = await db2.collection("members").find(
8842
+ {
8843
+ user: new ObjectId26(userId),
8844
+ status: { $ne: "deleted" },
8845
+ org: { $in: both3(pinned.map((m) => m.org)) },
8846
+ siteId: { $in: both3(pinned.map((m) => m.siteId)) },
8847
+ createdBy: { $type: "objectId", $ne: new ObjectId26(userId) }
8848
+ },
8849
+ { projection: { org: 1, siteId: 1 } }
8850
+ ).toArray();
8851
+ for (const row of placed) {
8852
+ const key = pinKey(row.org, row.siteId);
8853
+ if (wanted.has(key))
8854
+ keys.add(key);
8855
+ }
8836
8856
  const user = await db2.collection("users").findOne({ _id: new ObjectId26(userId) }, { projection: { email: 1 } });
8837
8857
  const email = String(user?.email ?? "").trim();
8838
8858
  if (!email)
8839
8859
  return keys;
8840
- const both3 = (ids2) => [...new Set(ids2.map(String))].flatMap((v) => [v, new ObjectId26(v)]);
8841
8860
  const invites = await db2.collection("verifications").find(
8842
8861
  {
8843
8862
  email,
@@ -8853,7 +8872,6 @@ async function siteInvitedPins(db2, userId, memberships) {
8853
8872
  collation: { locale: "en", strength: 2 }
8854
8873
  }
8855
8874
  ).toArray();
8856
- const wanted = new Set(pinned.map((m) => pinKey(m.org, m.siteId)));
8857
8875
  for (const invite of invites) {
8858
8876
  const key = pinKey(invite.metadata?.org, invite.metadata?.siteId);
8859
8877
  if (wanted.has(key))
@@ -24813,7 +24831,8 @@ function useRoleRepo() {
24813
24831
  sort = {},
24814
24832
  type = "",
24815
24833
  org = "",
24816
- site = ""
24834
+ site = "",
24835
+ scope = ""
24817
24836
  }) {
24818
24837
  page2 = page2 > 0 ? page2 - 1 : 0;
24819
24838
  if (org && typeof org === "string" && org.length === 24) {
@@ -24835,6 +24854,9 @@ function useRoleRepo() {
24835
24854
  if (site) {
24836
24855
  query2.$or = [{ site }, { site: "" }, { site: null }];
24837
24856
  cacheOptions.site = site.toString();
24857
+ } else if (scope === "organisation") {
24858
+ query2.site = { $in: ["", null] };
24859
+ cacheOptions.scope = scope;
24838
24860
  }
24839
24861
  sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
24840
24862
  cacheOptions.sort = JSON.stringify(sort);
@@ -27290,7 +27312,9 @@ function useRoleController() {
27290
27312
  limit: Joi28.number().integer().min(1).max(100).allow("", null).default(10),
27291
27313
  type: Joi28.string().optional().allow("", null),
27292
27314
  org: Joi28.string().hex().optional().allow("", null),
27293
- site: Joi28.string().hex().optional().allow("", null)
27315
+ site: Joi28.string().hex().optional().allow("", null),
27316
+ // "organisation": only the roles with no site (Organization app, service roles made once)
27317
+ scope: Joi28.string().valid("organisation").optional().allow("", null)
27294
27318
  });
27295
27319
  const query2 = { ...req.query };
27296
27320
  const { error } = validation.validate(query2);
@@ -27305,18 +27329,19 @@ function useRoleController() {
27305
27329
  const type = req.query.type ?? "";
27306
27330
  const org = req.query.org ?? "";
27307
27331
  const site = req.query.site ?? "";
27332
+ const scope = req.query.scope ?? "";
27308
27333
  try {
27309
27334
  if (org) {
27310
27335
  await requireOrgReach(req, org);
27311
27336
  }
27312
- let data = await _getRoles({ search, page: page2, limit, type, org, site });
27313
- const listIsWholeOrg = org && !search && !site && page2 === 1;
27337
+ let data = await _getRoles({ search, page: page2, limit, type, org, site, scope });
27338
+ const listIsWholeOrg = org && !search && !site && !scope && page2 === 1;
27314
27339
  const listed = data?.items;
27315
27340
  if (listIsWholeOrg && listed?.length === 0) {
27316
27341
  const seeded = await healDefaultRoles(org).catch(() => 0);
27317
27342
  if (seeded > 0) {
27318
27343
  try {
27319
- data = await _getRoles({ search, page: page2, limit, type, org, site });
27344
+ data = await _getRoles({ search, page: page2, limit, type, org, site, scope });
27320
27345
  } catch (error2) {
27321
27346
  logger42.log({ level: "error", message: error2.message });
27322
27347
  }
@@ -27768,7 +27793,8 @@ function useMemberService() {
27768
27793
  }
27769
27794
  }
27770
27795
  }
27771
- await _updateRoleById(id, role);
27796
+ const placedBy = member.siteId && !newRole?.site && callerId5 && ObjectId60.isValid(callerId5) && String(member.user ?? "") !== callerId5 ? callerId5 : void 0;
27797
+ await _updateRoleById(id, role, placedBy);
27772
27798
  return { message: "Member role updated successfully." };
27773
27799
  }
27774
27800
  async function updateSiteById(id, siteId, siteName) {
@@ -27816,6 +27842,7 @@ function useMemberService() {
27816
27842
  import { BadRequestError as BadRequestError56, UnauthorizedError as UnauthorizedError7, useAtlas as useAtlas44 } from "@7365admin1/node-server-utils";
27817
27843
  import { ObjectId as ObjectId61 } from "mongodb";
27818
27844
  var EXCLUDED_TYPES = ["resident", PLATFORM_STAFF_MEMBER_TYPE];
27845
+ var ORG_ROLE_EXCLUDED = [...EXCLUDED_TYPES, "organization"];
27819
27846
  var both = (id) => [id, new ObjectId61(id)];
27820
27847
  function useMemberSiteAccessService() {
27821
27848
  const { add, updateMemberStatus, updateRoleById } = useMemberRepo();
@@ -27855,10 +27882,13 @@ function useMemberSiteAccessService() {
27855
27882
  const roles = await db2().collection("roles").find({
27856
27883
  $or: [
27857
27884
  { _id: { $in: roleIds.map((r) => new ObjectId61(r)) } },
27858
- { org: { $in: both(org) }, site: { $in: [...sites.keys()].flatMap(both) }, status: "active" }
27885
+ { org: { $in: both(org) }, site: { $in: [...sites.keys()].flatMap(both) }, status: "active" },
27886
+ // organisation-level service roles (no site; never the org-console type)
27887
+ { org: { $in: both(org) }, site: { $in: ["", null] }, status: "active", type: { $nin: ORG_ROLE_EXCLUDED } }
27859
27888
  ]
27860
27889
  }).project({ name: 1, type: 1, site: 1, org: 1, status: 1 }).toArray();
27861
27890
  const byId = new Map(roles.map((r) => [String(r._id), r]));
27891
+ const orgRoles = roles.filter((r) => !String(r.site ?? "") && String(r.org ?? "") === org && r.status === "active" && !ORG_ROLE_EXCLUDED.includes(r.type)).map((r) => ({ _id: String(r._id), name: r.name ?? "", type: r.type ?? "", scope: "organisation" })).sort((a, b) => a.name.localeCompare(b.name));
27862
27892
  const view = (m) => ({
27863
27893
  _id: String(m._id),
27864
27894
  role: String(m.role ?? ""),
@@ -27872,7 +27902,7 @@ function useMemberSiteAccessService() {
27872
27902
  ...s,
27873
27903
  membership: here[0] ?? null,
27874
27904
  memberships: here,
27875
- roles: roles.filter((r) => String(r.site ?? "") === s.site && String(r.org ?? "") === org && r.status === "active" && !EXCLUDED_TYPES.includes(r.type)).map((r) => ({ _id: String(r._id), name: r.name ?? "", type: r.type ?? "" }))
27905
+ roles: roles.filter((r) => String(r.site ?? "") === s.site && String(r.org ?? "") === org && r.status === "active" && !EXCLUDED_TYPES.includes(r.type)).map((r) => ({ _id: String(r._id), name: r.name ?? "", type: r.type ?? "", scope: "site" })).concat(orgRoles)
27876
27906
  };
27877
27907
  }).sort((a, b) => a.name.localeCompare(b.name));
27878
27908
  const orgWide = members.filter((m) => !String(m.siteId ?? "")).map(view);
@@ -27898,7 +27928,7 @@ function useMemberSiteAccessService() {
27898
27928
  throw new BadRequestError56("A site is listed twice.");
27899
27929
  seen2.add(choice.site);
27900
27930
  if (!row.roles.some((r) => r._id === choice.role)) {
27901
- throw new BadRequestError56(`Choose a role created for ${row.name || "that site"}.`);
27931
+ throw new BadRequestError56(`Choose a role created for ${row.name || "that site"}, or one of the organisation's service roles.`);
27902
27932
  }
27903
27933
  }
27904
27934
  const userDoc = await db2().collection("users").findOne({ _id: new ObjectId61(user) }, { projection: { email: 1, name: 1 } });
@@ -27912,10 +27942,11 @@ function useMemberSiteAccessService() {
27912
27942
  const choice = sites.find((c) => c.site === row.site);
27913
27943
  const role = choice ? row.roles.find((r) => r._id === choice.role) : null;
27914
27944
  const keep = role ? row.memberships.find((m) => m.type === role.type) : void 0;
27915
- if (role && keep && keep.role !== role._id) {
27945
+ if (role && keep && (keep.role !== role._id || role.scope === "organisation")) {
27916
27946
  reroles.push(async () => {
27917
- await updateRoleById(keep._id, role._id);
27918
- applied.changed.push(row.site);
27947
+ await updateRoleById(keep._id, role._id, actor);
27948
+ if (keep.role !== role._id)
27949
+ applied.changed.push(row.site);
27919
27950
  });
27920
27951
  } else if (role && !keep) {
27921
27952
  adds.push(async () => {
@@ -27924,7 +27955,7 @@ function useMemberSiteAccessService() {
27924
27955
  { projection: { _id: 1 } }
27925
27956
  );
27926
27957
  if (old) {
27927
- await updateRoleById(old._id, role._id);
27958
+ await updateRoleById(old._id, role._id, actor);
27928
27959
  await updateMemberStatus(old._id, { status: suspended ? "suspended" : "active" });
27929
27960
  } else {
27930
27961
  await add({
@@ -27938,6 +27969,8 @@ function useMemberSiteAccessService() {
27938
27969
  siteId: row.site,
27939
27970
  siteName: row.name,
27940
27971
  dateInvited: (/* @__PURE__ */ new Date()).toISOString(),
27972
+ // "placed at this site by the editor": confines an organisation role to it (siteInvitedPins)
27973
+ createdBy: actor,
27941
27974
  ...suspended ? { status: "suspended" } : {}
27942
27975
  });
27943
27976
  }