@shipfox/api-workspaces 5.0.0 → 7.0.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.
Files changed (65) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +54 -0
  3. package/README.md +2 -2
  4. package/dist/config.d.ts +0 -8
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +1 -50
  7. package/dist/config.js.map +1 -1
  8. package/dist/core/entities/invitation.d.ts +1 -0
  9. package/dist/core/entities/invitation.d.ts.map +1 -1
  10. package/dist/core/entities/invitation.js.map +1 -1
  11. package/dist/core/invitations.d.ts +21 -2
  12. package/dist/core/invitations.d.ts.map +1 -1
  13. package/dist/core/invitations.js +49 -12
  14. package/dist/core/invitations.js.map +1 -1
  15. package/dist/core/workspaces.d.ts +1 -1
  16. package/dist/core/workspaces.d.ts.map +1 -1
  17. package/dist/core/workspaces.js +13 -1
  18. package/dist/core/workspaces.js.map +1 -1
  19. package/dist/db/db.d.ts +34 -0
  20. package/dist/db/db.d.ts.map +1 -1
  21. package/dist/db/index.d.ts +2 -2
  22. package/dist/db/index.d.ts.map +1 -1
  23. package/dist/db/index.js +1 -1
  24. package/dist/db/index.js.map +1 -1
  25. package/dist/db/invitations.d.ts +15 -8
  26. package/dist/db/invitations.d.ts.map +1 -1
  27. package/dist/db/invitations.js +70 -22
  28. package/dist/db/invitations.js.map +1 -1
  29. package/dist/db/schema/invitations.d.ts +17 -0
  30. package/dist/db/schema/invitations.d.ts.map +1 -1
  31. package/dist/db/schema/invitations.js +4 -0
  32. package/dist/db/schema/invitations.js.map +1 -1
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +6 -2
  36. package/dist/index.js.map +1 -1
  37. package/dist/presentation/inter-module.d.ts +4 -0
  38. package/dist/presentation/inter-module.d.ts.map +1 -0
  39. package/dist/presentation/inter-module.js +74 -0
  40. package/dist/presentation/inter-module.js.map +1 -0
  41. package/dist/presentation/subscribers/on-invitation-send-requested.js +1 -1
  42. package/dist/presentation/subscribers/on-invitation-send-requested.js.map +1 -1
  43. package/dist/tsconfig.test.tsbuildinfo +1 -1
  44. package/drizzle/0004_tired_talon.sql +1 -0
  45. package/drizzle/meta/0004_snapshot.json +448 -0
  46. package/drizzle/meta/_journal.json +7 -0
  47. package/package.json +18 -10
  48. package/src/config.ts +1 -44
  49. package/src/core/entities/invitation.ts +1 -0
  50. package/src/core/invitations.test.ts +35 -2
  51. package/src/core/invitations.ts +72 -17
  52. package/src/core/workspaces.test.ts +28 -0
  53. package/src/core/workspaces.ts +19 -2
  54. package/src/db/index.ts +1 -6
  55. package/src/db/invitations.test.ts +237 -15
  56. package/src/db/invitations.ts +78 -50
  57. package/src/db/schema/invitations.ts +2 -0
  58. package/src/db/workspaces.test.ts +3 -2
  59. package/src/index.test.ts +10 -1
  60. package/src/index.ts +8 -1
  61. package/src/presentation/inter-module.ts +94 -0
  62. package/src/presentation/routes/invitations/create.test.ts +19 -1
  63. package/src/presentation/subscribers/invitation-send-requested.test.ts +1 -1
  64. package/src/presentation/subscribers/on-invitation-send-requested.ts +1 -1
  65. package/tsconfig.build.tsbuildinfo +1 -1
@@ -32,16 +32,23 @@ export declare function listOpenInvitationsByWorkspace(params: {
32
32
  export declare function revokeInvitation(params: {
33
33
  invitationId: string;
34
34
  }): Promise<void>;
35
- export interface AcceptInvitationParams {
36
- invitationId: string;
37
- acceptedByUserId: string;
38
- acceptedByUserName?: string | null | undefined;
39
- }
40
- export interface AcceptInvitationResult {
35
+ export type ReconcileInvitationAcceptanceResult = {
36
+ status: 'accepted';
41
37
  invitation: Invitation;
42
38
  membership: Membership;
43
39
  alreadyMember: boolean;
44
- }
45
- export declare function acceptInvitation(params: AcceptInvitationParams): Promise<AcceptInvitationResult | undefined>;
40
+ } | {
41
+ status: 'already_accepted';
42
+ invitation: Invitation;
43
+ membership: Membership;
44
+ } | {
45
+ status: 'invalid' | 'expired' | 'revoked' | 'consumed_by_another_user' | 'email_mismatch';
46
+ };
47
+ export declare function reconcileInvitationAcceptance(params: {
48
+ invitationId: string;
49
+ acceptedByUserId: string;
50
+ email: string;
51
+ acceptedByUserName?: string | null | undefined;
52
+ }): Promise<ReconcileInvitationAcceptanceResult>;
46
53
  export {};
47
54
  //# sourceMappingURL=invitations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"invitations.d.ts","sourceRoot":"","sources":["../../src/db/invitations.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAC7D,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAa7D,UAAU,0BAA0B;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,MAAM,sBAAsB,GAAG,0BAA0B,GAC7D,CACI;IACE,SAAS,EAAE;QACT,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB,GACD;IAAC,SAAS,CAAC,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAC,CACvC,CAAC;AAEJ,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CA4D1F;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAUlC;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAAC,EAAE,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAM9F;AAED,wBAAsB,8BAA8B,CAAC,MAAM,EAAE;IAC3D,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAaxB;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAAC,YAAY,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpF;AAED,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAChD;AAED,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,UAAU,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAyE7C"}
1
+ {"version":3,"file":"invitations.d.ts","sourceRoot":"","sources":["../../src/db/invitations.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAC7D,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAa7D,UAAU,0BAA0B;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,MAAM,sBAAsB,GAAG,0BAA0B,GAC7D,CACI;IACE,SAAS,EAAE;QACT,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB,GACD;IAAC,SAAS,CAAC,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAC,CACvC,CAAC;AAEJ,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CAuE1F;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAUlC;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAAC,EAAE,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAM9F;AAED,wBAAsB,8BAA8B,CAAC,MAAM,EAAE;IAC3D,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAcxB;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAAC,YAAY,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAKpF;AAED,MAAM,MAAM,mCAAmC,GAC3C;IAAC,MAAM,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,UAAU,CAAC;IAAC,aAAa,EAAE,OAAO,CAAA;CAAC,GAC5F;IAAC,MAAM,EAAE,kBAAkB,CAAC;IAAC,UAAU,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAC,GAC5E;IAAC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,0BAA0B,GAAG,gBAAgB,CAAA;CAAC,CAAC;AAEhG,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAChD,GAAG,OAAO,CAAC,mCAAmC,CAAC,CAwF/C"}
@@ -1,4 +1,4 @@
1
- import { WORKSPACES_INVITATION_SEND_REQUESTED } from '@shipfox/api-workspaces-dto';
1
+ import { WORKSPACES_INVITATION_SEND_REQUESTED, WORKSPACES_MEMBER_INVITED, WORKSPACES_MEMBER_JOINED } from '@shipfox/api-workspaces-dto';
2
2
  import { writeOutboxEvent } from '@shipfox/node-outbox';
3
3
  import { and, eq, gt, isNull, lt, sql } from 'drizzle-orm';
4
4
  import { OpenInvitationExistsError } from '#core/errors.js';
@@ -10,8 +10,8 @@ import { memberships, toMembership } from './schema/memberships.js';
10
10
  import { workspacesOutbox } from './schema/outbox.js';
11
11
  export async function createInvitation(params) {
12
12
  const result = await db().transaction(async (tx)=>{
13
- await tx.delete(invitations).where(and(eq(invitations.workspaceId, params.workspaceId), eq(invitations.email, params.email), isNull(invitations.acceptedAt), lt(invitations.expiresAt, sql`now()`)));
14
- const open = await tx.select().from(invitations).where(and(eq(invitations.workspaceId, params.workspaceId), eq(invitations.email, params.email), isNull(invitations.acceptedAt))).limit(1);
13
+ await tx.delete(invitations).where(and(eq(invitations.workspaceId, params.workspaceId), eq(invitations.email, params.email), isNull(invitations.acceptedAt), isNull(invitations.revokedAt), lt(invitations.expiresAt, sql`now()`)));
14
+ const open = await tx.select().from(invitations).where(and(eq(invitations.workspaceId, params.workspaceId), eq(invitations.email, params.email), isNull(invitations.acceptedAt), isNull(invitations.revokedAt))).limit(1);
15
15
  if (open.length > 0) {
16
16
  throw new OpenInvitationExistsError(params.email);
17
17
  }
@@ -25,6 +25,15 @@ export async function createInvitation(params) {
25
25
  }).returning();
26
26
  const row = rows[0];
27
27
  if (!row) throw new Error('Insert returned no rows');
28
+ await writeOutboxEvent(tx, workspacesOutbox, {
29
+ type: WORKSPACES_MEMBER_INVITED,
30
+ payload: {
31
+ workspaceId: params.workspaceId,
32
+ invitedEmail: params.email,
33
+ inviterUserId: params.invitedByUserId,
34
+ role: 'admin'
35
+ }
36
+ });
28
37
  if (params.sendEmail) {
29
38
  await writeOutboxEvent(tx, workspacesOutbox, {
30
39
  type: WORKSPACES_INVITATION_SEND_REQUESTED,
@@ -55,34 +64,72 @@ export async function findInvitationById(params) {
55
64
  return toInvitation(row);
56
65
  }
57
66
  export async function listOpenInvitationsByWorkspace(params) {
58
- const rows = await db().select().from(invitations).where(and(eq(invitations.workspaceId, params.workspaceId), isNull(invitations.acceptedAt), gt(invitations.expiresAt, sql`now()`)));
67
+ const rows = await db().select().from(invitations).where(and(eq(invitations.workspaceId, params.workspaceId), isNull(invitations.acceptedAt), isNull(invitations.revokedAt), gt(invitations.expiresAt, sql`now()`)));
59
68
  return rows.map(toInvitation);
60
69
  }
61
70
  export async function revokeInvitation(params) {
62
- await db().delete(invitations).where(eq(invitations.id, params.invitationId));
71
+ await db().update(invitations).set({
72
+ revokedAt: sql`now()`,
73
+ updatedAt: sql`now()`
74
+ }).where(and(eq(invitations.id, params.invitationId), isNull(invitations.acceptedAt)));
63
75
  }
64
- export async function acceptInvitation(params) {
76
+ export async function reconcileInvitationAcceptance(params) {
65
77
  const result = await db().transaction(async (tx)=>{
66
- const inv = await tx.select().from(invitations).where(and(eq(invitations.id, params.invitationId), isNull(invitations.acceptedAt), gt(invitations.expiresAt, sql`now()`))).limit(1);
67
- const invRow = inv[0];
68
- if (!invRow) return undefined;
69
- const existing = await tx.select().from(memberships).where(and(eq(memberships.userId, params.acceptedByUserId), eq(memberships.workspaceId, invRow.workspaceId))).limit(1);
70
- let membership;
71
- let alreadyMember = false;
72
- const existingRow = existing[0];
73
- if (existingRow) {
74
- alreadyMember = true;
75
- membership = toMembership(existingRow);
76
- } else {
78
+ const findMembership = async (userId, workspaceId)=>{
79
+ const rows = await tx.select().from(memberships).where(and(eq(memberships.userId, userId), eq(memberships.workspaceId, workspaceId))).limit(1);
80
+ const row = rows[0];
81
+ return row ? toMembership(row) : undefined;
82
+ };
83
+ const rows = await tx.select().from(invitations).where(eq(invitations.id, params.invitationId)).limit(1).for('update');
84
+ const row = rows[0];
85
+ if (!row) return {
86
+ status: 'invalid'
87
+ };
88
+ const invitation = toInvitation(row);
89
+ if (invitation.acceptedAt !== null) {
90
+ if (invitation.acceptedByUserId !== params.acceptedByUserId) {
91
+ return {
92
+ status: 'consumed_by_another_user'
93
+ };
94
+ }
95
+ const membership = await findMembership(params.acceptedByUserId, invitation.workspaceId);
96
+ if (!membership) throw new Error('Accepted invitation has no membership');
97
+ return {
98
+ status: 'already_accepted',
99
+ invitation,
100
+ membership
101
+ };
102
+ }
103
+ if (invitation.revokedAt !== null) return {
104
+ status: 'revoked'
105
+ };
106
+ if (invitation.expiresAt.getTime() <= Date.now()) return {
107
+ status: 'expired'
108
+ };
109
+ if (invitation.email !== params.email) return {
110
+ status: 'email_mismatch'
111
+ };
112
+ const existingMembership = await findMembership(params.acceptedByUserId, invitation.workspaceId);
113
+ let membership = existingMembership;
114
+ if (!membership) {
77
115
  const created = await tx.insert(memberships).values(membershipValues({
78
116
  userId: params.acceptedByUserId,
79
- userEmail: invRow.email,
117
+ userEmail: invitation.email,
80
118
  userName: params.acceptedByUserName ?? null,
81
- workspaceId: invRow.workspaceId
119
+ workspaceId: invitation.workspaceId
82
120
  })).returning();
83
121
  const createdRow = created[0];
84
122
  if (!createdRow) throw new Error('Insert returned no rows');
85
123
  membership = toMembership(createdRow);
124
+ await writeOutboxEvent(tx, workspacesOutbox, {
125
+ type: WORKSPACES_MEMBER_JOINED,
126
+ payload: {
127
+ workspaceId: invitation.workspaceId,
128
+ userId: params.acceptedByUserId,
129
+ email: invitation.email,
130
+ viaInvitation: true
131
+ }
132
+ });
86
133
  }
87
134
  const updated = await tx.update(invitations).set({
88
135
  acceptedAt: sql`now()`,
@@ -92,13 +139,14 @@ export async function acceptInvitation(params) {
92
139
  const updatedRow = updated[0];
93
140
  if (!updatedRow) throw new Error('Update returned no rows');
94
141
  return {
142
+ status: 'accepted',
95
143
  invitation: toInvitation(updatedRow),
96
144
  membership,
97
- alreadyMember
145
+ alreadyMember: existingMembership !== undefined
98
146
  };
99
147
  });
100
- if (result && !result.alreadyMember) recordWorkspaceMembershipChanged('added');
101
- if (result) {
148
+ if (result.status === 'accepted') {
149
+ if (!result.alreadyMember) recordWorkspaceMembershipChanged('added');
102
150
  recordWorkspaceInvitationAccepted(result.alreadyMember ? 'already_member' : 'added');
103
151
  }
104
152
  return result;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/db/invitations.ts"],"sourcesContent":["import {\n WORKSPACES_INVITATION_SEND_REQUESTED,\n type WorkspacesEventMap,\n} from '@shipfox/api-workspaces-dto';\nimport {writeOutboxEvent} from '@shipfox/node-outbox';\nimport {and, eq, gt, isNull, lt, sql} from 'drizzle-orm';\nimport type {Invitation} from '#core/entities/invitation.js';\nimport type {Membership} from '#core/entities/membership.js';\nimport {OpenInvitationExistsError} from '#core/errors.js';\nimport {\n recordWorkspaceInvitationAccepted,\n recordWorkspaceInvitationCreated,\n recordWorkspaceMembershipChanged,\n} from '#metrics/instance.js';\nimport {db} from './db.js';\nimport {membershipValues} from './memberships.js';\nimport {invitations, toInvitation} from './schema/invitations.js';\nimport {memberships, toMembership} from './schema/memberships.js';\nimport {workspacesOutbox} from './schema/outbox.js';\n\ninterface CreateInvitationBaseParams {\n workspaceId: string;\n email: string;\n hashedToken: string;\n expiresAt: Date;\n invitedByUserId: string;\n invitedByDisplay?: string | null;\n}\n\nexport type CreateInvitationParams = CreateInvitationBaseParams &\n (\n | {\n sendEmail: {\n workspaceName: string;\n inviterName: string;\n inviteLink: string;\n };\n skipEmail?: never;\n }\n | {sendEmail?: never; skipEmail: true}\n );\n\nexport async function createInvitation(params: CreateInvitationParams): Promise<Invitation> {\n const result = await db().transaction(async (tx) => {\n await tx\n .delete(invitations)\n .where(\n and(\n eq(invitations.workspaceId, params.workspaceId),\n eq(invitations.email, params.email),\n isNull(invitations.acceptedAt),\n lt(invitations.expiresAt, sql`now()`),\n ),\n );\n\n const open = await tx\n .select()\n .from(invitations)\n .where(\n and(\n eq(invitations.workspaceId, params.workspaceId),\n eq(invitations.email, params.email),\n isNull(invitations.acceptedAt),\n ),\n )\n .limit(1);\n\n if (open.length > 0) {\n throw new OpenInvitationExistsError(params.email);\n }\n\n const rows = await tx\n .insert(invitations)\n .values({\n workspaceId: params.workspaceId,\n email: params.email,\n hashedToken: params.hashedToken,\n expiresAt: params.expiresAt,\n invitedByUserId: params.invitedByUserId,\n invitedByDisplay: params.invitedByDisplay ?? null,\n })\n .returning();\n\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n if (params.sendEmail) {\n await writeOutboxEvent<WorkspacesEventMap>(tx, workspacesOutbox, {\n type: WORKSPACES_INVITATION_SEND_REQUESTED,\n payload: {\n email: params.email,\n ...params.sendEmail,\n },\n });\n }\n return {\n invitation: toInvitation(row),\n emailRequested: params.sendEmail ? ('requested' as const) : ('skipped' as const),\n };\n });\n\n recordWorkspaceInvitationCreated(result.emailRequested);\n return result.invitation;\n}\n\nexport async function findInvitationByToken(params: {\n hashedToken: string;\n}): Promise<Invitation | undefined> {\n const rows = await db()\n .select()\n .from(invitations)\n .where(eq(invitations.hashedToken, params.hashedToken))\n .limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toInvitation(row);\n}\n\nexport async function findInvitationById(params: {id: string}): Promise<Invitation | undefined> {\n const rows = await db().select().from(invitations).where(eq(invitations.id, params.id)).limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toInvitation(row);\n}\n\nexport async function listOpenInvitationsByWorkspace(params: {\n workspaceId: string;\n}): Promise<Invitation[]> {\n const rows = await db()\n .select()\n .from(invitations)\n .where(\n and(\n eq(invitations.workspaceId, params.workspaceId),\n isNull(invitations.acceptedAt),\n gt(invitations.expiresAt, sql`now()`),\n ),\n );\n\n return rows.map(toInvitation);\n}\n\nexport async function revokeInvitation(params: {invitationId: string}): Promise<void> {\n await db().delete(invitations).where(eq(invitations.id, params.invitationId));\n}\n\nexport interface AcceptInvitationParams {\n invitationId: string;\n acceptedByUserId: string;\n acceptedByUserName?: string | null | undefined;\n}\n\nexport interface AcceptInvitationResult {\n invitation: Invitation;\n membership: Membership;\n alreadyMember: boolean;\n}\n\nexport async function acceptInvitation(\n params: AcceptInvitationParams,\n): Promise<AcceptInvitationResult | undefined> {\n const result = await db().transaction(async (tx) => {\n const inv = await tx\n .select()\n .from(invitations)\n .where(\n and(\n eq(invitations.id, params.invitationId),\n isNull(invitations.acceptedAt),\n gt(invitations.expiresAt, sql`now()`),\n ),\n )\n .limit(1);\n\n const invRow = inv[0];\n if (!invRow) return undefined;\n\n const existing = await tx\n .select()\n .from(memberships)\n .where(\n and(\n eq(memberships.userId, params.acceptedByUserId),\n eq(memberships.workspaceId, invRow.workspaceId),\n ),\n )\n .limit(1);\n\n let membership: Membership;\n let alreadyMember = false;\n const existingRow = existing[0];\n\n if (existingRow) {\n alreadyMember = true;\n membership = toMembership(existingRow);\n } else {\n const created = await tx\n .insert(memberships)\n .values(\n membershipValues({\n userId: params.acceptedByUserId,\n userEmail: invRow.email,\n userName: params.acceptedByUserName ?? null,\n workspaceId: invRow.workspaceId,\n }),\n )\n .returning();\n const createdRow = created[0];\n if (!createdRow) throw new Error('Insert returned no rows');\n membership = toMembership(createdRow);\n }\n\n const updated = await tx\n .update(invitations)\n .set({\n acceptedAt: sql`now()`,\n acceptedByUserId: params.acceptedByUserId,\n updatedAt: sql`now()`,\n })\n .where(eq(invitations.id, params.invitationId))\n .returning();\n\n const updatedRow = updated[0];\n if (!updatedRow) throw new Error('Update returned no rows');\n\n return {invitation: toInvitation(updatedRow), membership, alreadyMember};\n });\n\n if (result && !result.alreadyMember) recordWorkspaceMembershipChanged('added');\n if (result) {\n recordWorkspaceInvitationAccepted(result.alreadyMember ? 'already_member' : 'added');\n }\n return result;\n}\n"],"names":["WORKSPACES_INVITATION_SEND_REQUESTED","writeOutboxEvent","and","eq","gt","isNull","lt","sql","OpenInvitationExistsError","recordWorkspaceInvitationAccepted","recordWorkspaceInvitationCreated","recordWorkspaceMembershipChanged","db","membershipValues","invitations","toInvitation","memberships","toMembership","workspacesOutbox","createInvitation","params","result","transaction","tx","delete","where","workspaceId","email","acceptedAt","expiresAt","open","select","from","limit","length","rows","insert","values","hashedToken","invitedByUserId","invitedByDisplay","returning","row","Error","sendEmail","type","payload","invitation","emailRequested","findInvitationByToken","undefined","findInvitationById","id","listOpenInvitationsByWorkspace","map","revokeInvitation","invitationId","acceptInvitation","inv","invRow","existing","userId","acceptedByUserId","membership","alreadyMember","existingRow","created","userEmail","userName","acceptedByUserName","createdRow","updated","update","set","updatedAt","updatedRow"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/db/invitations.ts"],"sourcesContent":["import {\n WORKSPACES_INVITATION_SEND_REQUESTED,\n WORKSPACES_MEMBER_INVITED,\n WORKSPACES_MEMBER_JOINED,\n type WorkspacesEventMap,\n} from '@shipfox/api-workspaces-dto';\nimport {writeOutboxEvent} from '@shipfox/node-outbox';\nimport {and, eq, gt, isNull, lt, sql} from 'drizzle-orm';\nimport type {Invitation} from '#core/entities/invitation.js';\nimport type {Membership} from '#core/entities/membership.js';\nimport {OpenInvitationExistsError} from '#core/errors.js';\nimport {\n recordWorkspaceInvitationAccepted,\n recordWorkspaceInvitationCreated,\n recordWorkspaceMembershipChanged,\n} from '#metrics/instance.js';\nimport {db} from './db.js';\nimport {membershipValues} from './memberships.js';\nimport {invitations, toInvitation} from './schema/invitations.js';\nimport {memberships, toMembership} from './schema/memberships.js';\nimport {workspacesOutbox} from './schema/outbox.js';\n\ninterface CreateInvitationBaseParams {\n workspaceId: string;\n email: string;\n hashedToken: string;\n expiresAt: Date;\n invitedByUserId: string;\n invitedByDisplay?: string | null;\n}\n\nexport type CreateInvitationParams = CreateInvitationBaseParams &\n (\n | {\n sendEmail: {\n workspaceName: string;\n inviterName: string;\n inviteLink: string;\n };\n skipEmail?: never;\n }\n | {sendEmail?: never; skipEmail: true}\n );\n\nexport async function createInvitation(params: CreateInvitationParams): Promise<Invitation> {\n const result = await db().transaction(async (tx) => {\n await tx\n .delete(invitations)\n .where(\n and(\n eq(invitations.workspaceId, params.workspaceId),\n eq(invitations.email, params.email),\n isNull(invitations.acceptedAt),\n isNull(invitations.revokedAt),\n lt(invitations.expiresAt, sql`now()`),\n ),\n );\n\n const open = await tx\n .select()\n .from(invitations)\n .where(\n and(\n eq(invitations.workspaceId, params.workspaceId),\n eq(invitations.email, params.email),\n isNull(invitations.acceptedAt),\n isNull(invitations.revokedAt),\n ),\n )\n .limit(1);\n\n if (open.length > 0) {\n throw new OpenInvitationExistsError(params.email);\n }\n\n const rows = await tx\n .insert(invitations)\n .values({\n workspaceId: params.workspaceId,\n email: params.email,\n hashedToken: params.hashedToken,\n expiresAt: params.expiresAt,\n invitedByUserId: params.invitedByUserId,\n invitedByDisplay: params.invitedByDisplay ?? null,\n })\n .returning();\n\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n await writeOutboxEvent<WorkspacesEventMap>(tx, workspacesOutbox, {\n type: WORKSPACES_MEMBER_INVITED,\n payload: {\n workspaceId: params.workspaceId,\n invitedEmail: params.email,\n inviterUserId: params.invitedByUserId,\n role: 'admin',\n },\n });\n if (params.sendEmail) {\n await writeOutboxEvent<WorkspacesEventMap>(tx, workspacesOutbox, {\n type: WORKSPACES_INVITATION_SEND_REQUESTED,\n payload: {\n email: params.email,\n ...params.sendEmail,\n },\n });\n }\n return {\n invitation: toInvitation(row),\n emailRequested: params.sendEmail ? ('requested' as const) : ('skipped' as const),\n };\n });\n\n recordWorkspaceInvitationCreated(result.emailRequested);\n return result.invitation;\n}\n\nexport async function findInvitationByToken(params: {\n hashedToken: string;\n}): Promise<Invitation | undefined> {\n const rows = await db()\n .select()\n .from(invitations)\n .where(eq(invitations.hashedToken, params.hashedToken))\n .limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toInvitation(row);\n}\n\nexport async function findInvitationById(params: {id: string}): Promise<Invitation | undefined> {\n const rows = await db().select().from(invitations).where(eq(invitations.id, params.id)).limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toInvitation(row);\n}\n\nexport async function listOpenInvitationsByWorkspace(params: {\n workspaceId: string;\n}): Promise<Invitation[]> {\n const rows = await db()\n .select()\n .from(invitations)\n .where(\n and(\n eq(invitations.workspaceId, params.workspaceId),\n isNull(invitations.acceptedAt),\n isNull(invitations.revokedAt),\n gt(invitations.expiresAt, sql`now()`),\n ),\n );\n\n return rows.map(toInvitation);\n}\n\nexport async function revokeInvitation(params: {invitationId: string}): Promise<void> {\n await db()\n .update(invitations)\n .set({revokedAt: sql`now()`, updatedAt: sql`now()`})\n .where(and(eq(invitations.id, params.invitationId), isNull(invitations.acceptedAt)));\n}\n\nexport type ReconcileInvitationAcceptanceResult =\n | {status: 'accepted'; invitation: Invitation; membership: Membership; alreadyMember: boolean}\n | {status: 'already_accepted'; invitation: Invitation; membership: Membership}\n | {status: 'invalid' | 'expired' | 'revoked' | 'consumed_by_another_user' | 'email_mismatch'};\n\nexport async function reconcileInvitationAcceptance(params: {\n invitationId: string;\n acceptedByUserId: string;\n email: string;\n acceptedByUserName?: string | null | undefined;\n}): Promise<ReconcileInvitationAcceptanceResult> {\n const result = await db().transaction(async (tx) => {\n const findMembership = async (userId: string, workspaceId: string) => {\n const rows = await tx\n .select()\n .from(memberships)\n .where(and(eq(memberships.userId, userId), eq(memberships.workspaceId, workspaceId)))\n .limit(1);\n const row = rows[0];\n return row ? toMembership(row) : undefined;\n };\n const rows = await tx\n .select()\n .from(invitations)\n .where(eq(invitations.id, params.invitationId))\n .limit(1)\n .for('update');\n const row = rows[0];\n if (!row) return {status: 'invalid'} as const;\n\n const invitation = toInvitation(row);\n if (invitation.acceptedAt !== null) {\n if (invitation.acceptedByUserId !== params.acceptedByUserId) {\n return {status: 'consumed_by_another_user'} as const;\n }\n const membership = await findMembership(params.acceptedByUserId, invitation.workspaceId);\n if (!membership) throw new Error('Accepted invitation has no membership');\n return {status: 'already_accepted', invitation, membership} as const;\n }\n if (invitation.revokedAt !== null) return {status: 'revoked'} as const;\n if (invitation.expiresAt.getTime() <= Date.now()) return {status: 'expired'} as const;\n if (invitation.email !== params.email) return {status: 'email_mismatch'} as const;\n\n const existingMembership = await findMembership(\n params.acceptedByUserId,\n invitation.workspaceId,\n );\n let membership = existingMembership;\n if (!membership) {\n const created = await tx\n .insert(memberships)\n .values(\n membershipValues({\n userId: params.acceptedByUserId,\n userEmail: invitation.email,\n userName: params.acceptedByUserName ?? null,\n workspaceId: invitation.workspaceId,\n }),\n )\n .returning();\n const createdRow = created[0];\n if (!createdRow) throw new Error('Insert returned no rows');\n membership = toMembership(createdRow);\n await writeOutboxEvent<WorkspacesEventMap>(tx, workspacesOutbox, {\n type: WORKSPACES_MEMBER_JOINED,\n payload: {\n workspaceId: invitation.workspaceId,\n userId: params.acceptedByUserId,\n email: invitation.email,\n viaInvitation: true,\n },\n });\n }\n\n const updated = await tx\n .update(invitations)\n .set({\n acceptedAt: sql`now()`,\n acceptedByUserId: params.acceptedByUserId,\n updatedAt: sql`now()`,\n })\n .where(eq(invitations.id, params.invitationId))\n .returning();\n const updatedRow = updated[0];\n if (!updatedRow) throw new Error('Update returned no rows');\n return {\n status: 'accepted',\n invitation: toInvitation(updatedRow),\n membership,\n alreadyMember: existingMembership !== undefined,\n } as const;\n });\n\n if (result.status === 'accepted') {\n if (!result.alreadyMember) recordWorkspaceMembershipChanged('added');\n recordWorkspaceInvitationAccepted(result.alreadyMember ? 'already_member' : 'added');\n }\n return result;\n}\n"],"names":["WORKSPACES_INVITATION_SEND_REQUESTED","WORKSPACES_MEMBER_INVITED","WORKSPACES_MEMBER_JOINED","writeOutboxEvent","and","eq","gt","isNull","lt","sql","OpenInvitationExistsError","recordWorkspaceInvitationAccepted","recordWorkspaceInvitationCreated","recordWorkspaceMembershipChanged","db","membershipValues","invitations","toInvitation","memberships","toMembership","workspacesOutbox","createInvitation","params","result","transaction","tx","delete","where","workspaceId","email","acceptedAt","revokedAt","expiresAt","open","select","from","limit","length","rows","insert","values","hashedToken","invitedByUserId","invitedByDisplay","returning","row","Error","type","payload","invitedEmail","inviterUserId","role","sendEmail","invitation","emailRequested","findInvitationByToken","undefined","findInvitationById","id","listOpenInvitationsByWorkspace","map","revokeInvitation","update","set","updatedAt","invitationId","reconcileInvitationAcceptance","findMembership","userId","for","status","acceptedByUserId","membership","getTime","Date","now","existingMembership","created","userEmail","userName","acceptedByUserName","createdRow","viaInvitation","updated","updatedRow","alreadyMember"],"mappings":"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"}
@@ -88,6 +88,23 @@ export declare const invitations: import("drizzle-orm/pg-core").PgTableWithColum
88
88
  identity: undefined;
89
89
  generated: undefined;
90
90
  }, {}, {}>;
91
+ revokedAt: import("drizzle-orm/pg-core").PgColumn<{
92
+ name: "revoked_at";
93
+ tableName: "invitations";
94
+ dataType: "date";
95
+ columnType: "PgTimestamp";
96
+ data: Date;
97
+ driverParam: string;
98
+ notNull: false;
99
+ hasDefault: false;
100
+ isPrimaryKey: false;
101
+ isAutoincrement: false;
102
+ hasRuntimeDefault: false;
103
+ enumValues: undefined;
104
+ baseColumn: never;
105
+ identity: undefined;
106
+ generated: undefined;
107
+ }, {}, {}>;
91
108
  acceptedAt: import("drizzle-orm/pg-core").PgColumn<{
92
109
  name: "accepted_at";
93
110
  tableName: "invitations";
@@ -1 +1 @@
1
- {"version":3,"file":"invitations.d.ts","sourceRoot":"","sources":["../../../src/db/schema/invitations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAI7D,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqBvB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,OAAO,WAAW,CAAC,YAAY,CAAC;AAC3D,MAAM,MAAM,kBAAkB,GAAG,OAAO,WAAW,CAAC,YAAY,CAAC;AAEjE,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,UAAU,CAc1D"}
1
+ {"version":3,"file":"invitations.d.ts","sourceRoot":"","sources":["../../../src/db/schema/invitations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAI7D,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsBvB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,OAAO,WAAW,CAAC,YAAY,CAAC;AAC3D,MAAM,MAAM,kBAAkB,GAAG,OAAO,WAAW,CAAC,YAAY,CAAC;AAEjE,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,UAAU,CAe1D"}
@@ -12,6 +12,9 @@ export const invitations = pgTable('invitations', {
12
12
  expiresAt: timestamp('expires_at', {
13
13
  withTimezone: true
14
14
  }).notNull(),
15
+ revokedAt: timestamp('revoked_at', {
16
+ withTimezone: true
17
+ }),
15
18
  acceptedAt: timestamp('accepted_at', {
16
19
  withTimezone: true
17
20
  }),
@@ -35,6 +38,7 @@ export function toInvitation(row) {
35
38
  email: row.email,
36
39
  hashedToken: row.hashedToken,
37
40
  expiresAt: row.expiresAt,
41
+ revokedAt: row.revokedAt,
38
42
  acceptedAt: row.acceptedAt,
39
43
  acceptedByUserId: row.acceptedByUserId,
40
44
  invitedByUserId: row.invitedByUserId,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/db/schema/invitations.ts"],"sourcesContent":["import {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {index, text, timestamp, uniqueIndex, uuid} from 'drizzle-orm/pg-core';\nimport type {Invitation} from '#core/entities/invitation.js';\nimport {pgTable} from './common.js';\nimport {workspaces} from './workspaces.js';\n\nexport const invitations = pgTable(\n 'invitations',\n {\n id: uuidv7PrimaryKey(),\n workspaceId: uuid('workspace_id')\n .notNull()\n .references(() => workspaces.id, {onDelete: 'cascade'}),\n email: text('email').notNull(),\n hashedToken: text('hashed_token').notNull(),\n expiresAt: timestamp('expires_at', {withTimezone: true}).notNull(),\n acceptedAt: timestamp('accepted_at', {withTimezone: true}),\n acceptedByUserId: uuid('accepted_by_user_id'),\n invitedByUserId: uuid('invited_by_user_id').notNull(),\n invitedByDisplay: text('invited_by_display'),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),\n },\n (table) => [\n uniqueIndex('workspaces_invitations_hashed_token_unique').on(table.hashedToken),\n index('workspaces_invitations_workspace_email_idx').on(table.workspaceId, table.email),\n ],\n);\n\nexport type InvitationDb = typeof invitations.$inferSelect;\nexport type InvitationCreateDb = typeof invitations.$inferInsert;\n\nexport function toInvitation(row: InvitationDb): Invitation {\n return {\n id: row.id,\n workspaceId: row.workspaceId,\n email: row.email,\n hashedToken: row.hashedToken,\n expiresAt: row.expiresAt,\n acceptedAt: row.acceptedAt,\n acceptedByUserId: row.acceptedByUserId,\n invitedByUserId: row.invitedByUserId,\n invitedByDisplay: row.invitedByDisplay,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n"],"names":["uuidv7PrimaryKey","index","text","timestamp","uniqueIndex","uuid","pgTable","workspaces","invitations","id","workspaceId","notNull","references","onDelete","email","hashedToken","expiresAt","withTimezone","acceptedAt","acceptedByUserId","invitedByUserId","invitedByDisplay","createdAt","defaultNow","updatedAt","table","on","toInvitation","row"],"mappings":"AAAA,SAAQA,gBAAgB,QAAO,wBAAwB;AACvD,SAAQC,KAAK,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,IAAI,QAAO,sBAAsB;AAE9E,SAAQC,OAAO,QAAO,cAAc;AACpC,SAAQC,UAAU,QAAO,kBAAkB;AAE3C,OAAO,MAAMC,cAAcF,QACzB,eACA;IACEG,IAAIT;IACJU,aAAaL,KAAK,gBACfM,OAAO,GACPC,UAAU,CAAC,IAAML,WAAWE,EAAE,EAAE;QAACI,UAAU;IAAS;IACvDC,OAAOZ,KAAK,SAASS,OAAO;IAC5BI,aAAab,KAAK,gBAAgBS,OAAO;IACzCK,WAAWb,UAAU,cAAc;QAACc,cAAc;IAAI,GAAGN,OAAO;IAChEO,YAAYf,UAAU,eAAe;QAACc,cAAc;IAAI;IACxDE,kBAAkBd,KAAK;IACvBe,iBAAiBf,KAAK,sBAAsBM,OAAO;IACnDU,kBAAkBnB,KAAK;IACvBoB,WAAWnB,UAAU,cAAc;QAACc,cAAc;IAAI,GAAGN,OAAO,GAAGY,UAAU;IAC7EC,WAAWrB,UAAU,cAAc;QAACc,cAAc;IAAI,GAAGN,OAAO,GAAGY,UAAU;AAC/E,GACA,CAACE,QAAU;QACTrB,YAAY,8CAA8CsB,EAAE,CAACD,MAAMV,WAAW;QAC9Ed,MAAM,8CAA8CyB,EAAE,CAACD,MAAMf,WAAW,EAAEe,MAAMX,KAAK;KACtF,EACD;AAKF,OAAO,SAASa,aAAaC,GAAiB;IAC5C,OAAO;QACLnB,IAAImB,IAAInB,EAAE;QACVC,aAAakB,IAAIlB,WAAW;QAC5BI,OAAOc,IAAId,KAAK;QAChBC,aAAaa,IAAIb,WAAW;QAC5BC,WAAWY,IAAIZ,SAAS;QACxBE,YAAYU,IAAIV,UAAU;QAC1BC,kBAAkBS,IAAIT,gBAAgB;QACtCC,iBAAiBQ,IAAIR,eAAe;QACpCC,kBAAkBO,IAAIP,gBAAgB;QACtCC,WAAWM,IAAIN,SAAS;QACxBE,WAAWI,IAAIJ,SAAS;IAC1B;AACF"}
1
+ {"version":3,"sources":["../../../src/db/schema/invitations.ts"],"sourcesContent":["import {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {index, text, timestamp, uniqueIndex, uuid} from 'drizzle-orm/pg-core';\nimport type {Invitation} from '#core/entities/invitation.js';\nimport {pgTable} from './common.js';\nimport {workspaces} from './workspaces.js';\n\nexport const invitations = pgTable(\n 'invitations',\n {\n id: uuidv7PrimaryKey(),\n workspaceId: uuid('workspace_id')\n .notNull()\n .references(() => workspaces.id, {onDelete: 'cascade'}),\n email: text('email').notNull(),\n hashedToken: text('hashed_token').notNull(),\n expiresAt: timestamp('expires_at', {withTimezone: true}).notNull(),\n revokedAt: timestamp('revoked_at', {withTimezone: true}),\n acceptedAt: timestamp('accepted_at', {withTimezone: true}),\n acceptedByUserId: uuid('accepted_by_user_id'),\n invitedByUserId: uuid('invited_by_user_id').notNull(),\n invitedByDisplay: text('invited_by_display'),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),\n },\n (table) => [\n uniqueIndex('workspaces_invitations_hashed_token_unique').on(table.hashedToken),\n index('workspaces_invitations_workspace_email_idx').on(table.workspaceId, table.email),\n ],\n);\n\nexport type InvitationDb = typeof invitations.$inferSelect;\nexport type InvitationCreateDb = typeof invitations.$inferInsert;\n\nexport function toInvitation(row: InvitationDb): Invitation {\n return {\n id: row.id,\n workspaceId: row.workspaceId,\n email: row.email,\n hashedToken: row.hashedToken,\n expiresAt: row.expiresAt,\n revokedAt: row.revokedAt,\n acceptedAt: row.acceptedAt,\n acceptedByUserId: row.acceptedByUserId,\n invitedByUserId: row.invitedByUserId,\n invitedByDisplay: row.invitedByDisplay,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n"],"names":["uuidv7PrimaryKey","index","text","timestamp","uniqueIndex","uuid","pgTable","workspaces","invitations","id","workspaceId","notNull","references","onDelete","email","hashedToken","expiresAt","withTimezone","revokedAt","acceptedAt","acceptedByUserId","invitedByUserId","invitedByDisplay","createdAt","defaultNow","updatedAt","table","on","toInvitation","row"],"mappings":"AAAA,SAAQA,gBAAgB,QAAO,wBAAwB;AACvD,SAAQC,KAAK,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,IAAI,QAAO,sBAAsB;AAE9E,SAAQC,OAAO,QAAO,cAAc;AACpC,SAAQC,UAAU,QAAO,kBAAkB;AAE3C,OAAO,MAAMC,cAAcF,QACzB,eACA;IACEG,IAAIT;IACJU,aAAaL,KAAK,gBACfM,OAAO,GACPC,UAAU,CAAC,IAAML,WAAWE,EAAE,EAAE;QAACI,UAAU;IAAS;IACvDC,OAAOZ,KAAK,SAASS,OAAO;IAC5BI,aAAab,KAAK,gBAAgBS,OAAO;IACzCK,WAAWb,UAAU,cAAc;QAACc,cAAc;IAAI,GAAGN,OAAO;IAChEO,WAAWf,UAAU,cAAc;QAACc,cAAc;IAAI;IACtDE,YAAYhB,UAAU,eAAe;QAACc,cAAc;IAAI;IACxDG,kBAAkBf,KAAK;IACvBgB,iBAAiBhB,KAAK,sBAAsBM,OAAO;IACnDW,kBAAkBpB,KAAK;IACvBqB,WAAWpB,UAAU,cAAc;QAACc,cAAc;IAAI,GAAGN,OAAO,GAAGa,UAAU;IAC7EC,WAAWtB,UAAU,cAAc;QAACc,cAAc;IAAI,GAAGN,OAAO,GAAGa,UAAU;AAC/E,GACA,CAACE,QAAU;QACTtB,YAAY,8CAA8CuB,EAAE,CAACD,MAAMX,WAAW;QAC9Ed,MAAM,8CAA8C0B,EAAE,CAACD,MAAMhB,WAAW,EAAEgB,MAAMZ,KAAK;KACtF,EACD;AAKF,OAAO,SAASc,aAAaC,GAAiB;IAC5C,OAAO;QACLpB,IAAIoB,IAAIpB,EAAE;QACVC,aAAamB,IAAInB,WAAW;QAC5BI,OAAOe,IAAIf,KAAK;QAChBC,aAAac,IAAId,WAAW;QAC5BC,WAAWa,IAAIb,SAAS;QACxBE,WAAWW,IAAIX,SAAS;QACxBC,YAAYU,IAAIV,UAAU;QAC1BC,kBAAkBS,IAAIT,gBAAgB;QACtCC,iBAAiBQ,IAAIR,eAAe;QACpCC,kBAAkBO,IAAIP,gBAAgB;QACtCC,WAAWM,IAAIN,SAAS;QACxBE,WAAWI,IAAIJ,SAAS;IAC1B;AACF"}
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export type { Invitation } from '#core/entities/invitation.js';
3
3
  export type { Membership } from '#core/entities/membership.js';
4
4
  export type { Workspace, WorkspaceStatus } from '#core/entities/workspace.js';
5
5
  export { InvitationEmailMismatchError, TokenAlreadyUsedError, TokenExpiredError, TokenInvalidError, WorkspaceNotFoundError, } from '#core/errors.js';
6
- export { acceptWorkspaceInvitation, peekInvitationByRawToken } from '#core/invitations.js';
6
+ export { acceptWorkspaceInvitation, peekInvitationByRawToken, reconcileWorkspaceInvitationAcceptance, type WorkspaceInvitationReconciliation, } from '#core/invitations.js';
7
7
  export { type EnsureMembershipParams, ensureMembership } from '#core/memberships.js';
8
8
  export { getWorkspace, requireWorkspaceMembership } from '#core/workspaces.js';
9
9
  export { db, migrationsPath } from '#db/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AAQxD,YAAY,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAC7D,YAAY,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAC7D,YAAY,EAAC,SAAS,EAAE,eAAe,EAAC,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EACL,4BAA4B,EAC5B,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAC,yBAAyB,EAAE,wBAAwB,EAAC,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAC,KAAK,sBAAsB,EAAE,gBAAgB,EAAC,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAC,YAAY,EAAE,0BAA0B,EAAC,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAC,EAAE,EAAE,cAAc,EAAC,MAAM,cAAc,CAAC;AAChD,OAAO,EAAC,qBAAqB,EAAC,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAC,gBAAgB,IAAI,MAAM,EAAC,MAAM,+BAA+B,CAAC;AAIzE,eAAO,MAAM,gBAAgB,EAAE,aAU9B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AASxD,YAAY,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAC7D,YAAY,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAC7D,YAAY,EAAC,SAAS,EAAE,eAAe,EAAC,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EACL,4BAA4B,EAC5B,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,sCAAsC,EACtC,KAAK,iCAAiC,GACvC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAC,KAAK,sBAAsB,EAAE,gBAAgB,EAAC,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAC,YAAY,EAAE,0BAA0B,EAAC,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAC,EAAE,EAAE,cAAc,EAAC,MAAM,cAAc,CAAC;AAChD,OAAO,EAAC,qBAAqB,EAAC,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAC,gBAAgB,IAAI,MAAM,EAAC,MAAM,+BAA+B,CAAC;AAIzE,eAAO,MAAM,gBAAgB,EAAE,aAW9B,CAAC"}
package/dist/index.js CHANGED
@@ -3,10 +3,11 @@ import { subscriberFactory } from '@shipfox/node-module';
3
3
  import { db, migrationsPath, workspacesOutbox } from '#db/index.js';
4
4
  import { registerWorkspacesServiceMetrics } from '#metrics/index.js';
5
5
  import { workspacesE2eRoutes } from '#presentation/e2eRoutes/index.js';
6
+ import { createWorkspacesInterModulePresentation } from '#presentation/inter-module.js';
6
7
  import { workspacesRoutes } from '#presentation/routes/index.js';
7
8
  import { onInvitationSendRequested } from '#presentation/subscribers/index.js';
8
9
  export { InvitationEmailMismatchError, TokenAlreadyUsedError, TokenExpiredError, TokenInvalidError, WorkspaceNotFoundError } from '#core/errors.js';
9
- export { acceptWorkspaceInvitation, peekInvitationByRawToken } from '#core/invitations.js';
10
+ export { acceptWorkspaceInvitation, peekInvitationByRawToken, reconcileWorkspaceInvitationAcceptance } from '#core/invitations.js';
10
11
  export { ensureMembership } from '#core/memberships.js';
11
12
  export { getWorkspace, requireWorkspaceMembership } from '#core/workspaces.js';
12
13
  export { db, migrationsPath } from '#db/index.js';
@@ -34,7 +35,10 @@ export const workspacesModule = {
34
35
  subscribers: [
35
36
  subscriber(WORKSPACES_INVITATION_SEND_REQUESTED, onInvitationSendRequested)
36
37
  ],
37
- metrics: registerWorkspacesServiceMetrics
38
+ metrics: registerWorkspacesServiceMetrics,
39
+ interModulePresentations: [
40
+ createWorkspacesInterModulePresentation()
41
+ ]
38
42
  };
39
43
 
40
44
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n WORKSPACES_INVITATION_SEND_REQUESTED,\n type WorkspacesEventMap,\n workspacesEventSchemas,\n} from '@shipfox/api-workspaces-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {db, migrationsPath, workspacesOutbox} from '#db/index.js';\nimport {registerWorkspacesServiceMetrics} from '#metrics/index.js';\nimport {workspacesE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {workspacesRoutes} from '#presentation/routes/index.js';\nimport {onInvitationSendRequested} from '#presentation/subscribers/index.js';\n\nexport type {Invitation} from '#core/entities/invitation.js';\nexport type {Membership} from '#core/entities/membership.js';\nexport type {Workspace, WorkspaceStatus} from '#core/entities/workspace.js';\nexport {\n InvitationEmailMismatchError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nexport {acceptWorkspaceInvitation, peekInvitationByRawToken} from '#core/invitations.js';\nexport {type EnsureMembershipParams, ensureMembership} from '#core/memberships.js';\nexport {getWorkspace, requireWorkspaceMembership} from '#core/workspaces.js';\nexport {db, migrationsPath} from '#db/index.js';\nexport {listMembershipsByUser} from '#db/memberships.js';\nexport {workspacesRoutes as routes} from '#presentation/routes/index.js';\n\nconst subscriber = subscriberFactory<WorkspacesEventMap>();\n\nexport const workspacesModule: ShipfoxModule = {\n name: 'workspaces',\n database: {db, migrationsPath},\n routes: workspacesRoutes,\n e2eRoutes: [workspacesE2eRoutes],\n publishers: [\n {name: 'workspaces', table: workspacesOutbox, db, eventSchemas: workspacesEventSchemas},\n ],\n subscribers: [subscriber(WORKSPACES_INVITATION_SEND_REQUESTED, onInvitationSendRequested)],\n metrics: registerWorkspacesServiceMetrics,\n};\n"],"names":["WORKSPACES_INVITATION_SEND_REQUESTED","workspacesEventSchemas","subscriberFactory","db","migrationsPath","workspacesOutbox","registerWorkspacesServiceMetrics","workspacesE2eRoutes","workspacesRoutes","onInvitationSendRequested","InvitationEmailMismatchError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","WorkspaceNotFoundError","acceptWorkspaceInvitation","peekInvitationByRawToken","ensureMembership","getWorkspace","requireWorkspaceMembership","listMembershipsByUser","routes","subscriber","workspacesModule","name","database","e2eRoutes","publishers","table","eventSchemas","subscribers","metrics"],"mappings":"AAAA,SACEA,oCAAoC,EAEpCC,sBAAsB,QACjB,8BAA8B;AAErC,SAAQC,iBAAiB,QAAO,uBAAuB;AACvD,SAAQC,EAAE,EAAEC,cAAc,EAAEC,gBAAgB,QAAO,eAAe;AAClE,SAAQC,gCAAgC,QAAO,oBAAoB;AACnE,SAAQC,mBAAmB,QAAO,mCAAmC;AACrE,SAAQC,gBAAgB,QAAO,gCAAgC;AAC/D,SAAQC,yBAAyB,QAAO,qCAAqC;AAK7E,SACEC,4BAA4B,EAC5BC,qBAAqB,EACrBC,iBAAiB,EACjBC,iBAAiB,EACjBC,sBAAsB,QACjB,kBAAkB;AACzB,SAAQC,yBAAyB,EAAEC,wBAAwB,QAAO,uBAAuB;AACzF,SAAqCC,gBAAgB,QAAO,uBAAuB;AACnF,SAAQC,YAAY,EAAEC,0BAA0B,QAAO,sBAAsB;AAC7E,SAAQhB,EAAE,EAAEC,cAAc,QAAO,eAAe;AAChD,SAAQgB,qBAAqB,QAAO,qBAAqB;AACzD,SAAQZ,oBAAoBa,MAAM,QAAO,gCAAgC;AAEzE,MAAMC,aAAapB;AAEnB,OAAO,MAAMqB,mBAAkC;IAC7CC,MAAM;IACNC,UAAU;QAACtB;QAAIC;IAAc;IAC7BiB,QAAQb;IACRkB,WAAW;QAACnB;KAAoB;IAChCoB,YAAY;QACV;YAACH,MAAM;YAAcI,OAAOvB;YAAkBF;YAAI0B,cAAc5B;QAAsB;KACvF;IACD6B,aAAa;QAACR,WAAWtB,sCAAsCS;KAA2B;IAC1FsB,SAASzB;AACX,EAAE"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n WORKSPACES_INVITATION_SEND_REQUESTED,\n type WorkspacesEventMap,\n workspacesEventSchemas,\n} from '@shipfox/api-workspaces-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {db, migrationsPath, workspacesOutbox} from '#db/index.js';\nimport {registerWorkspacesServiceMetrics} from '#metrics/index.js';\nimport {workspacesE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {createWorkspacesInterModulePresentation} from '#presentation/inter-module.js';\nimport {workspacesRoutes} from '#presentation/routes/index.js';\nimport {onInvitationSendRequested} from '#presentation/subscribers/index.js';\n\nexport type {Invitation} from '#core/entities/invitation.js';\nexport type {Membership} from '#core/entities/membership.js';\nexport type {Workspace, WorkspaceStatus} from '#core/entities/workspace.js';\nexport {\n InvitationEmailMismatchError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nexport {\n acceptWorkspaceInvitation,\n peekInvitationByRawToken,\n reconcileWorkspaceInvitationAcceptance,\n type WorkspaceInvitationReconciliation,\n} from '#core/invitations.js';\nexport {type EnsureMembershipParams, ensureMembership} from '#core/memberships.js';\nexport {getWorkspace, requireWorkspaceMembership} from '#core/workspaces.js';\nexport {db, migrationsPath} from '#db/index.js';\nexport {listMembershipsByUser} from '#db/memberships.js';\nexport {workspacesRoutes as routes} from '#presentation/routes/index.js';\n\nconst subscriber = subscriberFactory<WorkspacesEventMap>();\n\nexport const workspacesModule: ShipfoxModule = {\n name: 'workspaces',\n database: {db, migrationsPath},\n routes: workspacesRoutes,\n e2eRoutes: [workspacesE2eRoutes],\n publishers: [\n {name: 'workspaces', table: workspacesOutbox, db, eventSchemas: workspacesEventSchemas},\n ],\n subscribers: [subscriber(WORKSPACES_INVITATION_SEND_REQUESTED, onInvitationSendRequested)],\n metrics: registerWorkspacesServiceMetrics,\n interModulePresentations: [createWorkspacesInterModulePresentation()],\n};\n"],"names":["WORKSPACES_INVITATION_SEND_REQUESTED","workspacesEventSchemas","subscriberFactory","db","migrationsPath","workspacesOutbox","registerWorkspacesServiceMetrics","workspacesE2eRoutes","createWorkspacesInterModulePresentation","workspacesRoutes","onInvitationSendRequested","InvitationEmailMismatchError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","WorkspaceNotFoundError","acceptWorkspaceInvitation","peekInvitationByRawToken","reconcileWorkspaceInvitationAcceptance","ensureMembership","getWorkspace","requireWorkspaceMembership","listMembershipsByUser","routes","subscriber","workspacesModule","name","database","e2eRoutes","publishers","table","eventSchemas","subscribers","metrics","interModulePresentations"],"mappings":"AAAA,SACEA,oCAAoC,EAEpCC,sBAAsB,QACjB,8BAA8B;AAErC,SAAQC,iBAAiB,QAAO,uBAAuB;AACvD,SAAQC,EAAE,EAAEC,cAAc,EAAEC,gBAAgB,QAAO,eAAe;AAClE,SAAQC,gCAAgC,QAAO,oBAAoB;AACnE,SAAQC,mBAAmB,QAAO,mCAAmC;AACrE,SAAQC,uCAAuC,QAAO,gCAAgC;AACtF,SAAQC,gBAAgB,QAAO,gCAAgC;AAC/D,SAAQC,yBAAyB,QAAO,qCAAqC;AAK7E,SACEC,4BAA4B,EAC5BC,qBAAqB,EACrBC,iBAAiB,EACjBC,iBAAiB,EACjBC,sBAAsB,QACjB,kBAAkB;AACzB,SACEC,yBAAyB,EACzBC,wBAAwB,EACxBC,sCAAsC,QAEjC,uBAAuB;AAC9B,SAAqCC,gBAAgB,QAAO,uBAAuB;AACnF,SAAQC,YAAY,EAAEC,0BAA0B,QAAO,sBAAsB;AAC7E,SAAQlB,EAAE,EAAEC,cAAc,QAAO,eAAe;AAChD,SAAQkB,qBAAqB,QAAO,qBAAqB;AACzD,SAAQb,oBAAoBc,MAAM,QAAO,gCAAgC;AAEzE,MAAMC,aAAatB;AAEnB,OAAO,MAAMuB,mBAAkC;IAC7CC,MAAM;IACNC,UAAU;QAACxB;QAAIC;IAAc;IAC7BmB,QAAQd;IACRmB,WAAW;QAACrB;KAAoB;IAChCsB,YAAY;QACV;YAACH,MAAM;YAAcI,OAAOzB;YAAkBF;YAAI4B,cAAc9B;QAAsB;KACvF;IACD+B,aAAa;QAACR,WAAWxB,sCAAsCU;KAA2B;IAC1FuB,SAAS3B;IACT4B,0BAA0B;QAAC1B;KAA0C;AACvE,EAAE"}
@@ -0,0 +1,4 @@
1
+ import { workspacesInterModuleContract } from '@shipfox/api-workspaces-dto/inter-module';
2
+ import { type InterModulePresentation } from '@shipfox/inter-module';
3
+ export declare function createWorkspacesInterModulePresentation(): InterModulePresentation<typeof workspacesInterModuleContract>;
4
+ //# sourceMappingURL=inter-module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inter-module.d.ts","sourceRoot":"","sources":["../../src/presentation/inter-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,6BAA6B,EAAC,MAAM,0CAA0C,CAAC;AACvF,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,uBAAuB,CAAC;AAc/B,wBAAgB,uCAAuC,IAAI,uBAAuB,CAChF,OAAO,6BAA6B,CACrC,CAwDA"}
@@ -0,0 +1,74 @@
1
+ import { workspacesInterModuleContract } from '@shipfox/api-workspaces-dto/inter-module';
2
+ import { createInterModuleKnownError, defineInterModulePresentation } from '@shipfox/inter-module';
3
+ import { InvitationEmailMismatchError, MembershipRequiredError, TokenAlreadyUsedError, TokenExpiredError, TokenInvalidError, WorkspaceInactiveError, WorkspaceNotFoundError } from '#core/errors.js';
4
+ import { acceptWorkspaceInvitation, peekInvitationByRawToken } from '#core/invitations.js';
5
+ import { requireWorkspaceMembership } from '#core/workspaces.js';
6
+ import { listMembershipsByUser } from '#db/memberships.js';
7
+ export function createWorkspacesInterModulePresentation() {
8
+ return defineInterModulePresentation(workspacesInterModuleContract, {
9
+ listMembershipsForTokenClaims: async ({ userId })=>({
10
+ memberships: (await listMembershipsByUser({
11
+ userId
12
+ })).map(({ workspaceId })=>({
13
+ workspaceId,
14
+ role: 'admin'
15
+ }))
16
+ }),
17
+ preflightInvitationAcceptance: async (input)=>{
18
+ try {
19
+ const invitation = await peekInvitationByRawToken({
20
+ token: input.token
21
+ });
22
+ if (!invitation) throw new TokenInvalidError('Invitation token is invalid');
23
+ if (invitation.acceptedAt !== null) throw new TokenAlreadyUsedError();
24
+ if (invitation.expiresAt.getTime() <= Date.now()) throw new TokenExpiredError();
25
+ if (invitation.email !== input.email) throw new InvitationEmailMismatchError();
26
+ return {};
27
+ } catch (error) {
28
+ throw toInvitationKnownError('preflightInvitationAcceptance', error);
29
+ }
30
+ },
31
+ acceptInvitation: async (input)=>{
32
+ try {
33
+ const result = await acceptWorkspaceInvitation(input);
34
+ return {
35
+ membership: {
36
+ id: result.membership.id,
37
+ userId: result.membership.userId,
38
+ workspaceId: result.membership.workspaceId
39
+ }
40
+ };
41
+ } catch (error) {
42
+ throw toInvitationKnownError('acceptInvitation', error);
43
+ }
44
+ },
45
+ requireActiveMembership: async (input)=>{
46
+ try {
47
+ await requireWorkspaceMembership(input);
48
+ return {};
49
+ } catch (error) {
50
+ const method = workspacesInterModuleContract.methods.requireActiveMembership;
51
+ if (error instanceof MembershipRequiredError) throw createInterModuleKnownError(method, 'membership-required', {
52
+ workspaceId: input.workspaceId
53
+ });
54
+ if (error instanceof WorkspaceNotFoundError) throw createInterModuleKnownError(method, 'workspace-not-found', {
55
+ workspaceId: input.workspaceId
56
+ });
57
+ if (error instanceof WorkspaceInactiveError) throw createInterModuleKnownError(method, 'workspace-inactive', {
58
+ workspaceId: input.workspaceId
59
+ });
60
+ throw error;
61
+ }
62
+ }
63
+ });
64
+ }
65
+ function toInvitationKnownError(methodName, error) {
66
+ const method = workspacesInterModuleContract.methods[methodName];
67
+ if (error instanceof TokenInvalidError) return createInterModuleKnownError(method, 'invitation-token-invalid', {});
68
+ if (error instanceof TokenAlreadyUsedError) return createInterModuleKnownError(method, 'invitation-token-used', {});
69
+ if (error instanceof TokenExpiredError) return createInterModuleKnownError(method, 'invitation-token-expired', {});
70
+ if (error instanceof InvitationEmailMismatchError) return createInterModuleKnownError(method, 'invitation-email-mismatch', {});
71
+ return error;
72
+ }
73
+
74
+ //# sourceMappingURL=inter-module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/presentation/inter-module.ts"],"sourcesContent":["import {workspacesInterModuleContract} from '@shipfox/api-workspaces-dto/inter-module';\nimport {\n createInterModuleKnownError,\n defineInterModulePresentation,\n type InterModulePresentation,\n} from '@shipfox/inter-module';\nimport {\n InvitationEmailMismatchError,\n MembershipRequiredError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n WorkspaceInactiveError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nimport {acceptWorkspaceInvitation, peekInvitationByRawToken} from '#core/invitations.js';\nimport {requireWorkspaceMembership} from '#core/workspaces.js';\nimport {listMembershipsByUser} from '#db/memberships.js';\n\nexport function createWorkspacesInterModulePresentation(): InterModulePresentation<\n typeof workspacesInterModuleContract\n> {\n return defineInterModulePresentation(workspacesInterModuleContract, {\n listMembershipsForTokenClaims: async ({userId}) => ({\n memberships: (await listMembershipsByUser({userId})).map(({workspaceId}) => ({\n workspaceId,\n role: 'admin' as const,\n })),\n }),\n preflightInvitationAcceptance: async (input) => {\n try {\n const invitation = await peekInvitationByRawToken({token: input.token});\n if (!invitation) throw new TokenInvalidError('Invitation token is invalid');\n if (invitation.acceptedAt !== null) throw new TokenAlreadyUsedError();\n if (invitation.expiresAt.getTime() <= Date.now()) throw new TokenExpiredError();\n if (invitation.email !== input.email) throw new InvitationEmailMismatchError();\n return {};\n } catch (error) {\n throw toInvitationKnownError('preflightInvitationAcceptance', error);\n }\n },\n acceptInvitation: async (input) => {\n try {\n const result = await acceptWorkspaceInvitation(input);\n return {\n membership: {\n id: result.membership.id,\n userId: result.membership.userId,\n workspaceId: result.membership.workspaceId,\n },\n };\n } catch (error) {\n throw toInvitationKnownError('acceptInvitation', error);\n }\n },\n requireActiveMembership: async (input) => {\n try {\n await requireWorkspaceMembership(input);\n return {};\n } catch (error) {\n const method = workspacesInterModuleContract.methods.requireActiveMembership;\n if (error instanceof MembershipRequiredError)\n throw createInterModuleKnownError(method, 'membership-required', {\n workspaceId: input.workspaceId,\n });\n if (error instanceof WorkspaceNotFoundError)\n throw createInterModuleKnownError(method, 'workspace-not-found', {\n workspaceId: input.workspaceId,\n });\n if (error instanceof WorkspaceInactiveError)\n throw createInterModuleKnownError(method, 'workspace-inactive', {\n workspaceId: input.workspaceId,\n });\n throw error;\n }\n },\n });\n}\n\nfunction toInvitationKnownError(\n methodName: 'preflightInvitationAcceptance' | 'acceptInvitation',\n error: unknown,\n): unknown {\n const method = workspacesInterModuleContract.methods[methodName];\n if (error instanceof TokenInvalidError)\n return createInterModuleKnownError(method, 'invitation-token-invalid', {});\n if (error instanceof TokenAlreadyUsedError)\n return createInterModuleKnownError(method, 'invitation-token-used', {});\n if (error instanceof TokenExpiredError)\n return createInterModuleKnownError(method, 'invitation-token-expired', {});\n if (error instanceof InvitationEmailMismatchError)\n return createInterModuleKnownError(method, 'invitation-email-mismatch', {});\n return error;\n}\n"],"names":["workspacesInterModuleContract","createInterModuleKnownError","defineInterModulePresentation","InvitationEmailMismatchError","MembershipRequiredError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","WorkspaceInactiveError","WorkspaceNotFoundError","acceptWorkspaceInvitation","peekInvitationByRawToken","requireWorkspaceMembership","listMembershipsByUser","createWorkspacesInterModulePresentation","listMembershipsForTokenClaims","userId","memberships","map","workspaceId","role","preflightInvitationAcceptance","input","invitation","token","acceptedAt","expiresAt","getTime","Date","now","email","error","toInvitationKnownError","acceptInvitation","result","membership","id","requireActiveMembership","method","methods","methodName"],"mappings":"AAAA,SAAQA,6BAA6B,QAAO,2CAA2C;AACvF,SACEC,2BAA2B,EAC3BC,6BAA6B,QAExB,wBAAwB;AAC/B,SACEC,4BAA4B,EAC5BC,uBAAuB,EACvBC,qBAAqB,EACrBC,iBAAiB,EACjBC,iBAAiB,EACjBC,sBAAsB,EACtBC,sBAAsB,QACjB,kBAAkB;AACzB,SAAQC,yBAAyB,EAAEC,wBAAwB,QAAO,uBAAuB;AACzF,SAAQC,0BAA0B,QAAO,sBAAsB;AAC/D,SAAQC,qBAAqB,QAAO,qBAAqB;AAEzD,OAAO,SAASC;IAGd,OAAOZ,8BAA8BF,+BAA+B;QAClEe,+BAA+B,OAAO,EAACC,MAAM,EAAC,GAAM,CAAA;gBAClDC,aAAa,AAAC,CAAA,MAAMJ,sBAAsB;oBAACG;gBAAM,EAAC,EAAGE,GAAG,CAAC,CAAC,EAACC,WAAW,EAAC,GAAM,CAAA;wBAC3EA;wBACAC,MAAM;oBACR,CAAA;YACF,CAAA;QACAC,+BAA+B,OAAOC;YACpC,IAAI;gBACF,MAAMC,aAAa,MAAMZ,yBAAyB;oBAACa,OAAOF,MAAME,KAAK;gBAAA;gBACrE,IAAI,CAACD,YAAY,MAAM,IAAIhB,kBAAkB;gBAC7C,IAAIgB,WAAWE,UAAU,KAAK,MAAM,MAAM,IAAIpB;gBAC9C,IAAIkB,WAAWG,SAAS,CAACC,OAAO,MAAMC,KAAKC,GAAG,IAAI,MAAM,IAAIvB;gBAC5D,IAAIiB,WAAWO,KAAK,KAAKR,MAAMQ,KAAK,EAAE,MAAM,IAAI3B;gBAChD,OAAO,CAAC;YACV,EAAE,OAAO4B,OAAO;gBACd,MAAMC,uBAAuB,iCAAiCD;YAChE;QACF;QACAE,kBAAkB,OAAOX;YACvB,IAAI;gBACF,MAAMY,SAAS,MAAMxB,0BAA0BY;gBAC/C,OAAO;oBACLa,YAAY;wBACVC,IAAIF,OAAOC,UAAU,CAACC,EAAE;wBACxBpB,QAAQkB,OAAOC,UAAU,CAACnB,MAAM;wBAChCG,aAAae,OAAOC,UAAU,CAAChB,WAAW;oBAC5C;gBACF;YACF,EAAE,OAAOY,OAAO;gBACd,MAAMC,uBAAuB,oBAAoBD;YACnD;QACF;QACAM,yBAAyB,OAAOf;YAC9B,IAAI;gBACF,MAAMV,2BAA2BU;gBACjC,OAAO,CAAC;YACV,EAAE,OAAOS,OAAO;gBACd,MAAMO,SAAStC,8BAA8BuC,OAAO,CAACF,uBAAuB;gBAC5E,IAAIN,iBAAiB3B,yBACnB,MAAMH,4BAA4BqC,QAAQ,uBAAuB;oBAC/DnB,aAAaG,MAAMH,WAAW;gBAChC;gBACF,IAAIY,iBAAiBtB,wBACnB,MAAMR,4BAA4BqC,QAAQ,uBAAuB;oBAC/DnB,aAAaG,MAAMH,WAAW;gBAChC;gBACF,IAAIY,iBAAiBvB,wBACnB,MAAMP,4BAA4BqC,QAAQ,sBAAsB;oBAC9DnB,aAAaG,MAAMH,WAAW;gBAChC;gBACF,MAAMY;YACR;QACF;IACF;AACF;AAEA,SAASC,uBACPQ,UAAgE,EAChET,KAAc;IAEd,MAAMO,SAAStC,8BAA8BuC,OAAO,CAACC,WAAW;IAChE,IAAIT,iBAAiBxB,mBACnB,OAAON,4BAA4BqC,QAAQ,4BAA4B,CAAC;IAC1E,IAAIP,iBAAiB1B,uBACnB,OAAOJ,4BAA4BqC,QAAQ,yBAAyB,CAAC;IACvE,IAAIP,iBAAiBzB,mBACnB,OAAOL,4BAA4BqC,QAAQ,4BAA4B,CAAC;IAC1E,IAAIP,iBAAiB5B,8BACnB,OAAOF,4BAA4BqC,QAAQ,6BAA6B,CAAC;IAC3E,OAAOP;AACT"}
@@ -1,5 +1,5 @@
1
1
  import { renderEmail } from '@shipfox/node-email';
2
- import { mailer } from '#config.js';
2
+ import { mailer } from '@shipfox/node-mailer';
3
3
  export async function onInvitationSendRequested(payload) {
4
4
  const email = await renderEmail('workspace-invitation', {
5
5
  email: payload.email,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presentation/subscribers/on-invitation-send-requested.ts"],"sourcesContent":["import type {WorkspacesInvitationSendRequestedEvent} from '@shipfox/api-workspaces-dto';\nimport {renderEmail} from '@shipfox/node-email';\nimport {mailer} from '#config.js';\n\nexport async function onInvitationSendRequested(\n payload: WorkspacesInvitationSendRequestedEvent,\n): Promise<void> {\n const email = await renderEmail('workspace-invitation', {\n email: payload.email,\n workspaceName: payload.workspaceName,\n inviterName: payload.inviterName,\n inviteLink: payload.inviteLink,\n });\n await mailer.send({to: payload.email, ...email});\n}\n"],"names":["renderEmail","mailer","onInvitationSendRequested","payload","email","workspaceName","inviterName","inviteLink","send","to"],"mappings":"AACA,SAAQA,WAAW,QAAO,sBAAsB;AAChD,SAAQC,MAAM,QAAO,aAAa;AAElC,OAAO,eAAeC,0BACpBC,OAA+C;IAE/C,MAAMC,QAAQ,MAAMJ,YAAY,wBAAwB;QACtDI,OAAOD,QAAQC,KAAK;QACpBC,eAAeF,QAAQE,aAAa;QACpCC,aAAaH,QAAQG,WAAW;QAChCC,YAAYJ,QAAQI,UAAU;IAChC;IACA,MAAMN,OAAOO,IAAI,CAAC;QAACC,IAAIN,QAAQC,KAAK;QAAE,GAAGA,KAAK;IAAA;AAChD"}
1
+ {"version":3,"sources":["../../../src/presentation/subscribers/on-invitation-send-requested.ts"],"sourcesContent":["import type {WorkspacesInvitationSendRequestedEvent} from '@shipfox/api-workspaces-dto';\nimport {renderEmail} from '@shipfox/node-email';\nimport {mailer} from '@shipfox/node-mailer';\n\nexport async function onInvitationSendRequested(\n payload: WorkspacesInvitationSendRequestedEvent,\n): Promise<void> {\n const email = await renderEmail('workspace-invitation', {\n email: payload.email,\n workspaceName: payload.workspaceName,\n inviterName: payload.inviterName,\n inviteLink: payload.inviteLink,\n });\n await mailer.send({to: payload.email, ...email});\n}\n"],"names":["renderEmail","mailer","onInvitationSendRequested","payload","email","workspaceName","inviterName","inviteLink","send","to"],"mappings":"AACA,SAAQA,WAAW,QAAO,sBAAsB;AAChD,SAAQC,MAAM,QAAO,uBAAuB;AAE5C,OAAO,eAAeC,0BACpBC,OAA+C;IAE/C,MAAMC,QAAQ,MAAMJ,YAAY,wBAAwB;QACtDI,OAAOD,QAAQC,KAAK;QACpBC,eAAeF,QAAQE,aAAa;QACpCC,aAAaH,QAAQG,WAAW;QAChCC,YAAYJ,QAAQI,UAAU;IAChC;IACA,MAAMN,OAAOO,IAAI,CAAC;QAACC,IAAIN,QAAQC,KAAK;QAAE,GAAGA,KAAK;IAAA;AAChD"}