@7365admin1/core 3.53.0 → 3.53.2

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @iservice365/core
2
2
 
3
+ ## 3.53.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 972c701: Scope file deletion to the uploader's tenant. New uploads are stamped with the caller's user id server-side; a stamped file can only be deleted by its uploader, someone sharing an organisation with them, or a super admin. Files with no stamp (everything uploaded before this change, and the anonymous resident sign-up upload) behave exactly as before — no backfill, no migration.
8
+
9
+ ## 3.53.1
10
+
11
+ ### Patch Changes
12
+
13
+ - bb5fb95: Scope the roles list to a site. `GET /api/roles` now accepts an optional `site`, and `role.repo.getRoles` filters on it: a role created on a site is listed only on that site, while roles with no site remain the organisation's own and stay visible on every site. `site` is part of the cache key. No stored document changes.
14
+
3
15
  ## 3.53.0
4
16
 
5
17
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -306,13 +306,14 @@ declare function useRoleRepo(): {
306
306
  getRoleByUserId: (value: string | ObjectId) => Promise<TRole | null>;
307
307
  getRoleById: (_id: string | ObjectId) => Promise<TRole | null>;
308
308
  getRoleByName: (name: string, type?: string) => Promise<TRole | null>;
309
- getRoles: ({ search, page, limit, sort, type, org, }: {
309
+ getRoles: ({ search, page, limit, sort, type, org, site, }: {
310
310
  search?: string | undefined;
311
311
  page?: number | undefined;
312
312
  limit?: number | undefined;
313
313
  sort?: any;
314
314
  type?: string | undefined;
315
315
  org: string | ObjectId;
316
+ site?: string | ObjectId | undefined;
316
317
  }) => Promise<{}>;
317
318
  getActiveByTypeOrg: (type: string, org: string | ObjectId) => Promise<TRole[]>;
318
319
  updateRole: (_id: string | ObjectId, value: TMiniRole, session?: ClientSession) => Promise<ObjectId>;
@@ -643,6 +644,13 @@ type TFile = {
643
644
  size?: number;
644
645
  status?: string;
645
646
  createdAt: string;
647
+ /**
648
+ * The signed-in user who uploaded this file, stamped server-side from the
649
+ * session. OPTIONAL on purpose: every row written before this shipped has no
650
+ * stamp, and the delete gate leaves those exactly as they were. See
651
+ * `utils/file-owner.util.ts`.
652
+ */
653
+ createdBy?: string;
646
654
  };
647
655
  declare class MFile implements TFile {
648
656
  _id?: ObjectId;
@@ -651,6 +659,7 @@ declare class MFile implements TFile {
651
659
  size?: number;
652
660
  status?: string;
653
661
  createdAt: string;
662
+ createdBy?: string;
654
663
  constructor(value: TFile);
655
664
  }
656
665
 
@@ -664,7 +673,7 @@ declare function useFileRepo(): {
664
673
  };
665
674
 
666
675
  declare function useFileService(): {
667
- createFile: (value: Express.Multer.File, folder?: string) => Promise<string>;
676
+ createFile: (value: Express.Multer.File, folder?: string, createdBy?: string) => Promise<string>;
668
677
  deleteFile: (id: string) => Promise<string>;
669
678
  deleteDraft: () => void;
670
679
  };
package/dist/index.js CHANGED
@@ -16609,7 +16609,8 @@ function useRoleRepo() {
16609
16609
  limit = 10,
16610
16610
  sort = {},
16611
16611
  type = "",
16612
- org = ""
16612
+ org = "",
16613
+ site = ""
16613
16614
  }) {
16614
16615
  page = page > 0 ? page - 1 : 0;
16615
16616
  if (org && typeof org === "string" && org.length === 24) {
@@ -16619,8 +16620,19 @@ function useRoleRepo() {
16619
16620
  throw new import_node_server_utils36.BadRequestError("Invalid organization ID format.");
16620
16621
  }
16621
16622
  }
16623
+ if (site && typeof site === "string" && site.length === 24) {
16624
+ try {
16625
+ site = new import_mongodb33.ObjectId(site);
16626
+ } catch (error) {
16627
+ throw new import_node_server_utils36.BadRequestError("Invalid site ID format.");
16628
+ }
16629
+ }
16622
16630
  const query2 = { status: "active", org };
16623
16631
  const cacheOptions = { status: "active", org };
16632
+ if (site) {
16633
+ query2.$or = [{ site }, { site: "" }, { site: null }];
16634
+ cacheOptions.site = site.toString();
16635
+ }
16624
16636
  sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
16625
16637
  cacheOptions.sort = JSON.stringify(sort);
16626
16638
  if (search) {
@@ -16824,6 +16836,8 @@ var MFile = class {
16824
16836
  this.size = value.size ?? 0;
16825
16837
  this.status = value.status ?? "draft";
16826
16838
  this.createdAt = value.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
16839
+ if (value.createdBy)
16840
+ this.createdBy = value.createdBy.toString();
16827
16841
  }
16828
16842
  };
16829
16843
 
@@ -19168,7 +19182,8 @@ function useRoleController() {
19168
19182
  page: import_joi19.default.number().integer().min(1).allow("", null).default(1),
19169
19183
  limit: import_joi19.default.number().integer().min(1).max(100).allow("", null).default(10),
19170
19184
  type: import_joi19.default.string().optional().allow("", null),
19171
- org: import_joi19.default.string().hex().optional().allow("", null)
19185
+ org: import_joi19.default.string().hex().optional().allow("", null),
19186
+ site: import_joi19.default.string().hex().optional().allow("", null)
19172
19187
  });
19173
19188
  const query2 = { ...req.query };
19174
19189
  const { error } = validation.validate(query2);
@@ -19182,11 +19197,12 @@ function useRoleController() {
19182
19197
  const limit = parseInt(req.query.limit ?? "10");
19183
19198
  const type = req.query.type ?? "";
19184
19199
  const org = req.query.org ?? "";
19200
+ const site = req.query.site ?? "";
19185
19201
  try {
19186
19202
  if (org) {
19187
19203
  await requireOrgReach(req, org);
19188
19204
  }
19189
- const data = await _getRoles({ search, page, limit, type, org });
19205
+ const data = await _getRoles({ search, page, limit, type, org, site });
19190
19206
  res.json(data);
19191
19207
  return;
19192
19208
  } catch (error2) {
@@ -22518,13 +22534,14 @@ function useFileService() {
22518
22534
  bucket: SPACES_BUCKET,
22519
22535
  forcePathStyle: true
22520
22536
  });
22521
- async function createFile(value, folder = "default") {
22537
+ async function createFile(value, folder = "default", createdBy) {
22522
22538
  const session = import_node_server_utils51.useAtlas.getClient()?.startSession();
22523
22539
  session?.startTransaction();
22524
22540
  const file = {
22525
22541
  name: value.originalname,
22526
22542
  size: value.size ?? (value.buffer ? value.buffer.length : 0),
22527
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
22543
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
22544
+ ...createdBy ? { createdBy } : {}
22528
22545
  };
22529
22546
  try {
22530
22547
  const id = await _createFile(file, session);
@@ -27610,6 +27627,49 @@ function useVerificationController() {
27610
27627
  // src/controllers/file.controller.ts
27611
27628
  var import_node_server_utils59 = require("@7365admin1/node-server-utils");
27612
27629
  var import_joi27 = __toESM(require("joi"));
27630
+
27631
+ // src/utils/file-owner.util.ts
27632
+ function fileOwnerId(file) {
27633
+ return file?.createdBy?.toString?.() ?? "";
27634
+ }
27635
+ function mayDeleteFile(params) {
27636
+ if (!params.ownerId)
27637
+ return true;
27638
+ if (!params.callerId)
27639
+ return false;
27640
+ if (params.callerId === params.ownerId)
27641
+ return true;
27642
+ if (params.callerIsSuperAdmin)
27643
+ return true;
27644
+ const ownerOrgs = new Set((params.ownerOrgIds ?? []).map(String));
27645
+ return (params.callerOrgIds ?? []).some((org) => ownerOrgs.has(String(org)));
27646
+ }
27647
+
27648
+ // src/controllers/file.controller.ts
27649
+ async function requireFileDelete(req, file) {
27650
+ const ownerId = fileOwnerId(file);
27651
+ if (!ownerId)
27652
+ return;
27653
+ const caller = callerId(req);
27654
+ if (!caller)
27655
+ throw new import_node_server_utils59.UnauthorizedError("Not authorized.");
27656
+ if (caller === ownerId)
27657
+ return;
27658
+ const [me, owner] = await Promise.all([
27659
+ resolveInviteActor(caller),
27660
+ resolveInviteActor(ownerId)
27661
+ ]);
27662
+ if (mayDeleteFile({
27663
+ ownerId,
27664
+ callerId: caller,
27665
+ callerIsSuperAdmin: me.isSuperAdmin,
27666
+ callerOrgIds: me.orgIds,
27667
+ ownerOrgIds: owner.orgIds
27668
+ })) {
27669
+ return;
27670
+ }
27671
+ throw new import_node_server_utils59.UnauthorizedError("Not authorized.");
27672
+ }
27613
27673
  function useFileController() {
27614
27674
  const { createFile, deleteFile: _deleteFile } = useFileService();
27615
27675
  const { getFileById: _getFileById } = useFileRepo();
@@ -27619,7 +27679,7 @@ function useFileController() {
27619
27679
  return;
27620
27680
  }
27621
27681
  try {
27622
- const id = await createFile(req.file);
27682
+ const id = await createFile(req.file, "default", callerId(req) || void 0);
27623
27683
  res.status(201).json({ message: "Successfully uploaded file", id });
27624
27684
  return;
27625
27685
  } catch (error) {
@@ -27638,6 +27698,7 @@ function useFileController() {
27638
27698
  return;
27639
27699
  }
27640
27700
  try {
27701
+ await requireFileDelete(req, await _getFileById(_id));
27641
27702
  const message = await _deleteFile(_id);
27642
27703
  res.json({ message });
27643
27704
  return;