@fonderie/workspaces 5.2.3 → 5.3.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.
@@ -26,6 +26,8 @@ const MESSAGE_KEYS: { readonly workspaceInvitation: "workspace-invitation"; }
26
26
 
27
27
  const EVENT_KEYS: { readonly personalWorkspaceCreated: "fonderie.workspace.personal.created"; }
28
28
 
29
+ const DEFAULT_TEMPLATES: { "workspace-invitation": { subject: string; html: string; text: string; }; }
30
+
29
31
  type WorkspaceType = 'ORGANIZATION' | 'PERSONAL' | 'TEAM' | 'COMMUNITY' | 'VENDOR';
30
32
 
31
33
  type InvitationStatus = 'PENDING' | 'ACCEPTED' | 'REJECTED' | 'CANCELLED';
package/dist/index.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ DEFAULT_TEMPLATES: () => DEFAULT_TEMPLATES,
23
24
  EVENT_KEYS: () => EVENT_KEYS,
24
25
  MESSAGE_KEYS: () => MESSAGE_KEYS,
25
26
  WorkspacesModule: () => WorkspacesModule,
@@ -209,13 +210,21 @@ async function getUserRoles(userId, workspaceId, store) {
209
210
  );
210
211
  }
211
212
  async function addRoleToMember(userId, workspaceId, roleId, store) {
212
- await store.query(
213
+ const rows = await store.query(
213
214
  `INSERT INTO fonderie_role_user_workspaces (user_id, workspace_id, role_id, confirmed)
214
- VALUES ($1, $2, $3, true)
215
+ SELECT $1, $2, $3, true
216
+ WHERE EXISTS (
217
+ SELECT 1 FROM fonderie_roles r
218
+ WHERE r.id = $3
219
+ AND r.workspace_id = $2
220
+ AND r.is_system = false
221
+ )
215
222
  ON CONFLICT (user_id, workspace_id, role_id) DO UPDATE
216
- SET confirmed = true, removed = false, suspended = false`,
223
+ SET confirmed = true, removed = false, suspended = false
224
+ RETURNING user_id`,
217
225
  [userId, workspaceId, roleId]
218
226
  );
227
+ return rows.length > 0;
219
228
  }
220
229
  async function removeRoleFromMember(userId, workspaceId, roleId, store) {
221
230
  const remaining = await store.query(
@@ -547,10 +556,11 @@ async function findSystemRole(name2, store) {
547
556
  );
548
557
  return row ?? null;
549
558
  }
550
- async function getRoleById(id, store) {
559
+ async function getRoleById(id, workspaceId, store) {
551
560
  const [row] = await store.query(
552
- `SELECT ${SELECT_ROLE} FROM fonderie_roles WHERE id = $1`,
553
- [id]
561
+ `SELECT ${SELECT_ROLE} FROM fonderie_roles
562
+ WHERE id = $1 AND (workspace_id = $2 OR is_system = true)`,
563
+ [id, workspaceId]
554
564
  );
555
565
  return row ?? null;
556
566
  }
@@ -563,9 +573,9 @@ async function listWorkspaceRoles(workspaceId, store) {
563
573
  [workspaceId]
564
574
  );
565
575
  }
566
- async function updateRole(id, opts, store) {
576
+ async function updateRole(id, workspaceId, opts, store) {
567
577
  const sets = [];
568
- const params = [id];
578
+ const params = [id, workspaceId];
569
579
  if (opts.name !== void 0) {
570
580
  params.push(opts.name);
571
581
  sets.push(`name = $${params.length}`);
@@ -578,11 +588,11 @@ async function updateRole(id, opts, store) {
578
588
  params.push(opts.active);
579
589
  sets.push(`active = $${params.length}`);
580
590
  }
581
- if (sets.length === 0) return getRoleById(id, store);
591
+ if (sets.length === 0) return getRoleById(id, workspaceId, store);
582
592
  const [row] = await store.query(
583
593
  `UPDATE fonderie_roles
584
594
  SET ${sets.join(", ")}
585
- WHERE id = $1 AND is_system = false
595
+ WHERE id = $1 AND workspace_id = $2 AND is_system = false
586
596
  RETURNING ${SELECT_ROLE}`,
587
597
  params
588
598
  );
@@ -648,14 +658,14 @@ var RoleModel = class {
648
658
  findSystem(name2) {
649
659
  return findSystemRole(name2, this.store);
650
660
  }
651
- findById(id) {
652
- return getRoleById(id, this.store);
661
+ findById(id, workspaceId) {
662
+ return getRoleById(id, workspaceId, this.store);
653
663
  }
654
664
  list(workspaceId) {
655
665
  return listWorkspaceRoles(workspaceId, this.store);
656
666
  }
657
- update(id, opts) {
658
- return updateRole(id, opts, this.store);
667
+ update(id, workspaceId, opts) {
668
+ return updateRole(id, workspaceId, opts, this.store);
659
669
  }
660
670
  delete(id, workspaceId) {
661
671
  return deleteRole(id, workspaceId, this.store);
@@ -927,7 +937,14 @@ function memberController(store) {
927
937
  return (0, import_core3.setApiResponse)(import_core3.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "userId is required");
928
938
  if (!roleId)
929
939
  return (0, import_core3.setApiResponse)(import_core3.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
930
- await members.addRole(userId, ctx.workspace.id, roleId);
940
+ const assigned = await members.addRole(userId, ctx.workspace.id, roleId);
941
+ if (!assigned) {
942
+ return (0, import_core3.setApiResponse)(
943
+ import_core3.HTTP.UNPROCESSABLE,
944
+ "INVALID_ROLE",
945
+ "Role is not assignable in this workspace."
946
+ );
947
+ }
931
948
  return (0, import_core3.setApiResponse)(import_core3.HTTP.OK, "ROLE_ASSIGNED", "Role assigned successfully.");
932
949
  },
933
950
  async removeRole(ctx) {
@@ -1000,7 +1017,7 @@ function roleController(store) {
1000
1017
  if (!roleId) {
1001
1018
  return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
1002
1019
  }
1003
- const role = await roles.findById(roleId);
1020
+ const role = await roles.findById(roleId, ctx.workspace.id);
1004
1021
  if (!role) {
1005
1022
  return (0, import_core4.setApiResponse)(import_core4.HTTP.NOT_FOUND, "NOT_FOUND", "Role not found");
1006
1023
  }
@@ -1031,7 +1048,7 @@ function roleController(store) {
1031
1048
  if (typeof body?.["active"] === "boolean") {
1032
1049
  opts.active = body["active"];
1033
1050
  }
1034
- const role = await roles.update(roleId, opts);
1051
+ const role = await roles.update(roleId, ctx.workspace.id, opts);
1035
1052
  if (!role) {
1036
1053
  return (0, import_core4.setApiResponse)(import_core4.HTTP.NOT_FOUND, "NOT_FOUND", "Role not found or is a system role");
1037
1054
  }
@@ -1060,7 +1077,7 @@ function roleController(store) {
1060
1077
  if (!roleId) {
1061
1078
  return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
1062
1079
  }
1063
- const role = await roles.findById(roleId);
1080
+ const role = await roles.findById(roleId, ctx.workspace.id);
1064
1081
  if (!role) {
1065
1082
  return (0, import_core4.setApiResponse)(import_core4.HTTP.NOT_FOUND, "NOT_FOUND", "Role not found");
1066
1083
  }
@@ -1079,6 +1096,10 @@ function roleController(store) {
1079
1096
  if (!roleId) {
1080
1097
  return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "roleId is required");
1081
1098
  }
1099
+ const target = await roles.findById(roleId, ctx.workspace.id);
1100
+ if (!target || target.isSystem) {
1101
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.NOT_FOUND, "NOT_FOUND", "Role not found");
1102
+ }
1082
1103
  const perms = body?.["permissions"];
1083
1104
  if (!Array.isArray(perms)) {
1084
1105
  return (0, import_core4.setApiResponse)(
@@ -1453,6 +1474,27 @@ var WorkspacesModule = class {
1453
1474
  }
1454
1475
  };
1455
1476
 
1477
+ // src/templates.ts
1478
+ var DEFAULT_TEMPLATES = {
1479
+ // Payload carries { token, pin }; the code path is PIN-based, so the copy
1480
+ // uses {{pin}} only — token is intentionally not surfaced.
1481
+ [MESSAGE_KEYS.workspaceInvitation]: {
1482
+ subject: "You've been invited to a workspace",
1483
+ html: `<h1>You&rsquo;ve been invited</h1>
1484
+ <p>You&rsquo;ve been invited to join a workspace. Use this code to accept the invitation:</p>
1485
+ <p><span class="pin-code">{{pin}}</span></p>
1486
+ <p class="muted">Enter this code on the invitation screen to join the team.</p>`,
1487
+ text: `You've been invited
1488
+
1489
+ You've been invited to join a workspace. Use this code to accept the invitation: {{pin}}
1490
+
1491
+ Enter this code on the invitation screen to join the team.`
1492
+ }
1493
+ };
1494
+ var SAMPLE_PAYLOADS = {
1495
+ [MESSAGE_KEYS.workspaceInvitation]: { token: "inv_abc123", pin: "123456" }
1496
+ };
1497
+
1456
1498
  // src/middlewares/require-workspace.ts
1457
1499
  var import_core6 = require("@fonderie/core");
1458
1500
  var requireWorkspace = async (ctx, next) => {
@@ -1561,6 +1603,7 @@ function workspaceExportContributor(store) {
1561
1603
  }
1562
1604
  // Annotate the CommonJS export names for ESM import in node:
1563
1605
  0 && (module.exports = {
1606
+ DEFAULT_TEMPLATES,
1564
1607
  EVENT_KEYS,
1565
1608
  MESSAGE_KEYS,
1566
1609
  WorkspacesModule,