@shipfox/api-workspaces 10.2.0 → 11.0.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.
Files changed (47) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +14 -0
  3. package/dist/core/invitations.d.ts.map +1 -1
  4. package/dist/core/invitations.js +6 -3
  5. package/dist/core/invitations.js.map +1 -1
  6. package/dist/core/workspaces.d.ts +1 -0
  7. package/dist/core/workspaces.d.ts.map +1 -1
  8. package/dist/core/workspaces.js +5 -3
  9. package/dist/core/workspaces.js.map +1 -1
  10. package/dist/presentation/inter-module.d.ts.map +1 -1
  11. package/dist/presentation/inter-module.js +3 -2
  12. package/dist/presentation/inter-module.js.map +1 -1
  13. package/dist/presentation/routes/invitations/create.d.ts.map +1 -1
  14. package/dist/presentation/routes/invitations/create.js +5 -1
  15. package/dist/presentation/routes/invitations/create.js.map +1 -1
  16. package/dist/presentation/routes/invitations/list.d.ts.map +1 -1
  17. package/dist/presentation/routes/invitations/list.js +5 -1
  18. package/dist/presentation/routes/invitations/list.js.map +1 -1
  19. package/dist/presentation/routes/invitations/revoke.d.ts.map +1 -1
  20. package/dist/presentation/routes/invitations/revoke.js +5 -1
  21. package/dist/presentation/routes/invitations/revoke.js.map +1 -1
  22. package/dist/presentation/routes/members/list.d.ts.map +1 -1
  23. package/dist/presentation/routes/members/list.js +5 -1
  24. package/dist/presentation/routes/members/list.js.map +1 -1
  25. package/dist/presentation/routes/members/remove.d.ts.map +1 -1
  26. package/dist/presentation/routes/members/remove.js +5 -1
  27. package/dist/presentation/routes/members/remove.js.map +1 -1
  28. package/dist/tsconfig.test.tsbuildinfo +1 -1
  29. package/package.json +4 -4
  30. package/src/core/invitations.test.ts +8 -8
  31. package/src/core/invitations.ts +3 -0
  32. package/src/core/workspaces.test.ts +22 -3
  33. package/src/core/workspaces.ts +4 -1
  34. package/src/presentation/inter-module.test.ts +51 -0
  35. package/src/presentation/inter-module.ts +7 -4
  36. package/src/presentation/routes/invitations/create.test.ts +15 -0
  37. package/src/presentation/routes/invitations/create.ts +2 -1
  38. package/src/presentation/routes/invitations/list.test.ts +30 -0
  39. package/src/presentation/routes/invitations/list.ts +2 -1
  40. package/src/presentation/routes/invitations/revoke.test.ts +14 -0
  41. package/src/presentation/routes/invitations/revoke.ts +2 -1
  42. package/src/presentation/routes/members/list.test.ts +29 -1
  43. package/src/presentation/routes/members/list.ts +2 -1
  44. package/src/presentation/routes/members/remove.test.ts +14 -0
  45. package/src/presentation/routes/members/remove.ts +2 -1
  46. package/test/routes.ts +8 -3
  47. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 52 files with swc (363.1ms)
2
+ Successfully compiled: 52 files with swc (485.39ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @shipfox/api-workspaces
2
2
 
3
+ ## 11.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 25158c8: Carry workspace lifecycle status in JWT membership claims and enforce suspended or inactive access at the stateless workspace gate while keeping access-token verification stateless.
8
+
9
+ `getAuthenticatedSessionContext()` now reads refresh-session metadata from verified access-token claims without checking active refresh-session state; revoking a refresh session does not invalidate an already-issued access token.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [25158c8]
14
+ - @shipfox/api-auth-context@11.0.0
15
+ - @shipfox/api-workspaces-dto@11.0.0
16
+
3
17
  ## 10.2.0
4
18
 
5
19
  ### Minor Changes
@@ -1 +1 @@
1
- {"version":3,"file":"invitations.d.ts","sourceRoot":"","sources":["../../src/core/invitations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,2BAA2B,CAAC;AAarE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAC;AAsBzD,wBAAsB,yBAAyB,CAAC,MAAM,EAAE;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,oBAAoB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;CAC5D,GAAG,OAAO,CAAC,UAAU,CAAC,CAwBtB;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAElC;AAED,MAAM,MAAM,uBAAuB,GAC/B;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,IAAI,CAAC;CACjB,GACD;IAAC,MAAM,EAAE,SAAS,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAC,GAC3D;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAC,GAC/C;IAAC,MAAM,EAAE,SAAS,CAAA;CAAC,CAAC;AAExB,MAAM,WAAW,2BAA2B;IAC1C,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,UAAU,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAoCjG;AAED,wBAAsB,yBAAyB,CAAC,MAAM,EAAE;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAClC,GAAG,OAAO,CAAC,2BAA2B,CAAC,CA0BvC;AAED,MAAM,MAAM,iCAAiC,GACzC;IACE,MAAM,EAAE,UAAU,GAAG,kBAAkB,CAAC;IACxC,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,UAAU,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;CACxB,GACD;IACE,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,0BAA0B,GAAG,gBAAgB,CAAC;CAC3F,CAAC;AAEN,wBAAsB,sCAAsC,CAAC,MAAM,EAAE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAClC,GAAG,OAAO,CAAC,iCAAiC,CAAC,CA2B7C;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;CAC5D,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAQxB;AAED,wBAAsB,yBAAyB,CAAC,MAAM,EAAE;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IAC3D,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBhB"}
1
+ {"version":3,"file":"invitations.d.ts","sourceRoot":"","sources":["../../src/core/invitations.ts"],"names":[],"mappings":"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"}
@@ -18,7 +18,8 @@ export async function createWorkspaceInvitation(params) {
18
18
  const { workspace } = await requireWorkspaceMembership({
19
19
  workspaceId: params.workspaceId,
20
20
  userId: params.invitedByUserId,
21
- memberships: params.invitedByMemberships
21
+ memberships: params.invitedByMemberships,
22
+ enforceWorkspaceStatus: false
22
23
  });
23
24
  const rawToken = generateOpaqueToken('invitation');
24
25
  const invitation = await createInvitation({
@@ -144,7 +145,8 @@ export async function listWorkspaceInvitations(params) {
144
145
  await requireWorkspaceMembership({
145
146
  workspaceId: params.workspaceId,
146
147
  userId: params.requesterUserId,
147
- memberships: params.requesterMemberships
148
+ memberships: params.requesterMemberships,
149
+ enforceWorkspaceStatus: false
148
150
  });
149
151
  return listOpenInvitationsByWorkspace({
150
152
  workspaceId: params.workspaceId
@@ -154,7 +156,8 @@ export async function revokeWorkspaceInvitation(params) {
154
156
  await requireWorkspaceMembership({
155
157
  workspaceId: params.workspaceId,
156
158
  userId: params.requesterUserId,
157
- memberships: params.requesterMemberships
159
+ memberships: params.requesterMemberships,
160
+ enforceWorkspaceStatus: false
158
161
  });
159
162
  const invitation = await findInvitationById({
160
163
  id: params.invitationId
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/invitations.ts"],"sourcesContent":["import type {UserContextMembership} from '@shipfox/api-auth-context';\nimport {emailSchema} from '@shipfox/api-common-dto';\nimport {generateOpaqueToken, hashOpaqueToken} from '@shipfox/node-tokens';\nimport {config} from '#config.js';\nimport {\n createInvitation,\n findInvitationById,\n findInvitationByToken,\n listOpenInvitationsByWorkspace,\n reconcileInvitationAcceptance,\n revokeInvitation,\n} from '#db/invitations.js';\nimport {getWorkspaceById} from '#db/workspaces.js';\nimport type {Invitation} from './entities/invitation.js';\nimport type {Membership} from './entities/membership.js';\nimport {\n InvitationEmailMismatchError,\n InvitationNotFoundError,\n InvitationWorkspaceMismatchError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n} from './errors.js';\nimport {requireWorkspaceMembership} from './workspaces.js';\n\nconst INVITATION_TTL_DAYS = 7;\n\nfunction daysFromNow(days: number): Date {\n return new Date(Date.now() + days * 24 * 60 * 60 * 1000);\n}\n\nfunction nonBlankOrFallback(value: string | null | undefined, fallback: string): string {\n const trimmed = value?.trim();\n return trimmed ? trimmed : fallback;\n}\n\nexport async function createWorkspaceInvitation(params: {\n workspaceId: string;\n email: string;\n invitedByUserId: string;\n invitedByDisplay?: string | null;\n invitedByMemberships: ReadonlyArray<UserContextMembership>;\n}): Promise<Invitation> {\n const email = emailSchema.parse(params.email);\n const {workspace} = await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.invitedByUserId,\n memberships: params.invitedByMemberships,\n });\n\n const rawToken = generateOpaqueToken('invitation');\n const invitation = await createInvitation({\n workspaceId: params.workspaceId,\n email,\n hashedToken: hashOpaqueToken(rawToken),\n expiresAt: daysFromNow(INVITATION_TTL_DAYS),\n invitedByUserId: params.invitedByUserId,\n invitedByDisplay: params.invitedByDisplay ?? null,\n sendEmail: {\n workspaceName: nonBlankOrFallback(workspace.name, 'your workspace'),\n inviterName: nonBlankOrFallback(params.invitedByDisplay, 'A teammate'),\n inviteLink: `${config.CLIENT_BASE_URL}/invitations/accept?token=${rawToken}`,\n },\n });\n\n return invitation;\n}\n\nexport async function peekInvitationByRawToken(params: {\n token: string;\n}): Promise<Invitation | undefined> {\n return await findInvitationByToken({hashedToken: hashOpaqueToken(params.token)});\n}\n\nexport type PreviewInvitationResult =\n | {\n status: 'pending';\n workspaceId: string;\n workspaceName: string;\n email: string;\n invitedByDisplay: string | null;\n expiresAt: Date;\n }\n | {status: 'expired'; workspaceName: string; expiresAt: Date}\n | {status: 'already_used'; workspaceName: string}\n | {status: 'invalid'};\n\nexport interface AcceptedWorkspaceInvitation {\n invitation: Invitation;\n membership: Membership;\n alreadyMember: boolean;\n}\n\nexport async function previewInvitation(params: {token: string}): Promise<PreviewInvitationResult> {\n const invitation = await peekInvitationByRawToken({token: params.token});\n if (!invitation) {\n return {status: 'invalid'};\n }\n\n const workspace = await getWorkspaceById(invitation.workspaceId);\n if (!workspace) {\n // Workspace cascade-deleted — the invitation row is orphaned. Treat as invalid.\n return {status: 'invalid'};\n }\n\n if (invitation.revokedAt !== null) {\n return {status: 'invalid'};\n }\n\n if (invitation.acceptedAt !== null) {\n return {status: 'already_used', workspaceName: workspace.name};\n }\n\n if (invitation.expiresAt.getTime() <= Date.now()) {\n return {\n status: 'expired',\n workspaceName: workspace.name,\n expiresAt: invitation.expiresAt,\n };\n }\n\n return {\n status: 'pending',\n workspaceId: workspace.id,\n workspaceName: workspace.name,\n email: invitation.email,\n invitedByDisplay: invitation.invitedByDisplay,\n expiresAt: invitation.expiresAt,\n };\n}\n\nexport async function acceptWorkspaceInvitation(params: {\n token: string;\n userId: string;\n email: string;\n name?: string | null | undefined;\n}): Promise<AcceptedWorkspaceInvitation> {\n const result = await reconcileWorkspaceInvitationAcceptance({\n token: params.token,\n email: params.email,\n userId: params.userId,\n name: params.name,\n });\n if (result.status === 'accepted') {\n return {\n invitation: result.invitation,\n membership: result.membership,\n alreadyMember: result.alreadyMember,\n };\n }\n if (result.status === 'already_accepted' || result.status === 'consumed_by_another_user') {\n throw new TokenAlreadyUsedError();\n }\n if (result.status === 'expired') {\n throw new TokenExpiredError();\n }\n if (result.status === 'email_mismatch') {\n throw new InvitationEmailMismatchError();\n }\n throw new TokenInvalidError(\n result.status === 'revoked' ? 'Invitation token has been revoked' : 'Invitation is invalid',\n );\n}\n\nexport type WorkspaceInvitationReconciliation =\n | {\n status: 'accepted' | 'already_accepted';\n invitation: Invitation;\n membership: Membership;\n alreadyMember: boolean;\n }\n | {\n status: 'invalid' | 'expired' | 'revoked' | 'consumed_by_another_user' | 'email_mismatch';\n };\n\nexport async function reconcileWorkspaceInvitationAcceptance(params: {\n token: string;\n userId: string;\n email: string;\n name?: string | null | undefined;\n}): Promise<WorkspaceInvitationReconciliation> {\n const invitation = await peekInvitationByRawToken({token: params.token});\n if (!invitation) return {status: 'invalid'};\n\n const result = await reconcileInvitationAcceptance({\n invitationId: invitation.id,\n acceptedByUserId: params.userId,\n email: emailSchema.parse(params.email),\n acceptedByUserName: params.name,\n });\n if (result.status === 'accepted') {\n return {\n status: result.status,\n invitation: result.invitation,\n membership: result.membership,\n alreadyMember: result.alreadyMember,\n };\n }\n if (result.status === 'already_accepted') {\n return {\n status: result.status,\n invitation: result.invitation,\n membership: result.membership,\n alreadyMember: true,\n };\n }\n return result;\n}\n\nexport async function listWorkspaceInvitations(params: {\n workspaceId: string;\n requesterUserId: string;\n requesterMemberships: ReadonlyArray<UserContextMembership>;\n}): Promise<Invitation[]> {\n await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.requesterUserId,\n memberships: params.requesterMemberships,\n });\n\n return listOpenInvitationsByWorkspace({workspaceId: params.workspaceId});\n}\n\nexport async function revokeWorkspaceInvitation(params: {\n workspaceId: string;\n requesterUserId: string;\n requesterMemberships: ReadonlyArray<UserContextMembership>;\n invitationId: string;\n}): Promise<void> {\n await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.requesterUserId,\n memberships: params.requesterMemberships,\n });\n\n const invitation = await findInvitationById({id: params.invitationId});\n if (!invitation) {\n throw new InvitationNotFoundError(params.invitationId);\n }\n\n if (invitation.workspaceId !== params.workspaceId) {\n throw new InvitationWorkspaceMismatchError();\n }\n\n await revokeInvitation({invitationId: params.invitationId});\n}\n"],"names":["emailSchema","generateOpaqueToken","hashOpaqueToken","config","createInvitation","findInvitationById","findInvitationByToken","listOpenInvitationsByWorkspace","reconcileInvitationAcceptance","revokeInvitation","getWorkspaceById","InvitationEmailMismatchError","InvitationNotFoundError","InvitationWorkspaceMismatchError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","requireWorkspaceMembership","INVITATION_TTL_DAYS","daysFromNow","days","Date","now","nonBlankOrFallback","value","fallback","trimmed","trim","createWorkspaceInvitation","params","email","parse","workspace","workspaceId","userId","invitedByUserId","memberships","invitedByMemberships","rawToken","invitation","hashedToken","expiresAt","invitedByDisplay","sendEmail","workspaceName","name","inviterName","inviteLink","CLIENT_BASE_URL","peekInvitationByRawToken","token","previewInvitation","status","revokedAt","acceptedAt","getTime","id","acceptWorkspaceInvitation","result","reconcileWorkspaceInvitationAcceptance","membership","alreadyMember","invitationId","acceptedByUserId","acceptedByUserName","listWorkspaceInvitations","requesterUserId","requesterMemberships","revokeWorkspaceInvitation"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/core/invitations.ts"],"sourcesContent":["import type {UserContextMembership} from '@shipfox/api-auth-context';\nimport {emailSchema} from '@shipfox/api-common-dto';\nimport {generateOpaqueToken, hashOpaqueToken} from '@shipfox/node-tokens';\nimport {config} from '#config.js';\nimport {\n createInvitation,\n findInvitationById,\n findInvitationByToken,\n listOpenInvitationsByWorkspace,\n reconcileInvitationAcceptance,\n revokeInvitation,\n} from '#db/invitations.js';\nimport {getWorkspaceById} from '#db/workspaces.js';\nimport type {Invitation} from './entities/invitation.js';\nimport type {Membership} from './entities/membership.js';\nimport {\n InvitationEmailMismatchError,\n InvitationNotFoundError,\n InvitationWorkspaceMismatchError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n} from './errors.js';\nimport {requireWorkspaceMembership} from './workspaces.js';\n\nconst INVITATION_TTL_DAYS = 7;\n\nfunction daysFromNow(days: number): Date {\n return new Date(Date.now() + days * 24 * 60 * 60 * 1000);\n}\n\nfunction nonBlankOrFallback(value: string | null | undefined, fallback: string): string {\n const trimmed = value?.trim();\n return trimmed ? trimmed : fallback;\n}\n\nexport async function createWorkspaceInvitation(params: {\n workspaceId: string;\n email: string;\n invitedByUserId: string;\n invitedByDisplay?: string | null;\n invitedByMemberships: ReadonlyArray<UserContextMembership>;\n}): Promise<Invitation> {\n const email = emailSchema.parse(params.email);\n const {workspace} = await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.invitedByUserId,\n memberships: params.invitedByMemberships,\n enforceWorkspaceStatus: false,\n });\n\n const rawToken = generateOpaqueToken('invitation');\n const invitation = await createInvitation({\n workspaceId: params.workspaceId,\n email,\n hashedToken: hashOpaqueToken(rawToken),\n expiresAt: daysFromNow(INVITATION_TTL_DAYS),\n invitedByUserId: params.invitedByUserId,\n invitedByDisplay: params.invitedByDisplay ?? null,\n sendEmail: {\n workspaceName: nonBlankOrFallback(workspace.name, 'your workspace'),\n inviterName: nonBlankOrFallback(params.invitedByDisplay, 'A teammate'),\n inviteLink: `${config.CLIENT_BASE_URL}/invitations/accept?token=${rawToken}`,\n },\n });\n\n return invitation;\n}\n\nexport async function peekInvitationByRawToken(params: {\n token: string;\n}): Promise<Invitation | undefined> {\n return await findInvitationByToken({hashedToken: hashOpaqueToken(params.token)});\n}\n\nexport type PreviewInvitationResult =\n | {\n status: 'pending';\n workspaceId: string;\n workspaceName: string;\n email: string;\n invitedByDisplay: string | null;\n expiresAt: Date;\n }\n | {status: 'expired'; workspaceName: string; expiresAt: Date}\n | {status: 'already_used'; workspaceName: string}\n | {status: 'invalid'};\n\nexport interface AcceptedWorkspaceInvitation {\n invitation: Invitation;\n membership: Membership;\n alreadyMember: boolean;\n}\n\nexport async function previewInvitation(params: {token: string}): Promise<PreviewInvitationResult> {\n const invitation = await peekInvitationByRawToken({token: params.token});\n if (!invitation) {\n return {status: 'invalid'};\n }\n\n const workspace = await getWorkspaceById(invitation.workspaceId);\n if (!workspace) {\n // Workspace cascade-deleted — the invitation row is orphaned. Treat as invalid.\n return {status: 'invalid'};\n }\n\n if (invitation.revokedAt !== null) {\n return {status: 'invalid'};\n }\n\n if (invitation.acceptedAt !== null) {\n return {status: 'already_used', workspaceName: workspace.name};\n }\n\n if (invitation.expiresAt.getTime() <= Date.now()) {\n return {\n status: 'expired',\n workspaceName: workspace.name,\n expiresAt: invitation.expiresAt,\n };\n }\n\n return {\n status: 'pending',\n workspaceId: workspace.id,\n workspaceName: workspace.name,\n email: invitation.email,\n invitedByDisplay: invitation.invitedByDisplay,\n expiresAt: invitation.expiresAt,\n };\n}\n\nexport async function acceptWorkspaceInvitation(params: {\n token: string;\n userId: string;\n email: string;\n name?: string | null | undefined;\n}): Promise<AcceptedWorkspaceInvitation> {\n const result = await reconcileWorkspaceInvitationAcceptance({\n token: params.token,\n email: params.email,\n userId: params.userId,\n name: params.name,\n });\n if (result.status === 'accepted') {\n return {\n invitation: result.invitation,\n membership: result.membership,\n alreadyMember: result.alreadyMember,\n };\n }\n if (result.status === 'already_accepted' || result.status === 'consumed_by_another_user') {\n throw new TokenAlreadyUsedError();\n }\n if (result.status === 'expired') {\n throw new TokenExpiredError();\n }\n if (result.status === 'email_mismatch') {\n throw new InvitationEmailMismatchError();\n }\n throw new TokenInvalidError(\n result.status === 'revoked' ? 'Invitation token has been revoked' : 'Invitation is invalid',\n );\n}\n\nexport type WorkspaceInvitationReconciliation =\n | {\n status: 'accepted' | 'already_accepted';\n invitation: Invitation;\n membership: Membership;\n alreadyMember: boolean;\n }\n | {\n status: 'invalid' | 'expired' | 'revoked' | 'consumed_by_another_user' | 'email_mismatch';\n };\n\nexport async function reconcileWorkspaceInvitationAcceptance(params: {\n token: string;\n userId: string;\n email: string;\n name?: string | null | undefined;\n}): Promise<WorkspaceInvitationReconciliation> {\n const invitation = await peekInvitationByRawToken({token: params.token});\n if (!invitation) return {status: 'invalid'};\n\n const result = await reconcileInvitationAcceptance({\n invitationId: invitation.id,\n acceptedByUserId: params.userId,\n email: emailSchema.parse(params.email),\n acceptedByUserName: params.name,\n });\n if (result.status === 'accepted') {\n return {\n status: result.status,\n invitation: result.invitation,\n membership: result.membership,\n alreadyMember: result.alreadyMember,\n };\n }\n if (result.status === 'already_accepted') {\n return {\n status: result.status,\n invitation: result.invitation,\n membership: result.membership,\n alreadyMember: true,\n };\n }\n return result;\n}\n\nexport async function listWorkspaceInvitations(params: {\n workspaceId: string;\n requesterUserId: string;\n requesterMemberships: ReadonlyArray<UserContextMembership>;\n}): Promise<Invitation[]> {\n await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.requesterUserId,\n memberships: params.requesterMemberships,\n enforceWorkspaceStatus: false,\n });\n\n return listOpenInvitationsByWorkspace({workspaceId: params.workspaceId});\n}\n\nexport async function revokeWorkspaceInvitation(params: {\n workspaceId: string;\n requesterUserId: string;\n requesterMemberships: ReadonlyArray<UserContextMembership>;\n invitationId: string;\n}): Promise<void> {\n await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.requesterUserId,\n memberships: params.requesterMemberships,\n enforceWorkspaceStatus: false,\n });\n\n const invitation = await findInvitationById({id: params.invitationId});\n if (!invitation) {\n throw new InvitationNotFoundError(params.invitationId);\n }\n\n if (invitation.workspaceId !== params.workspaceId) {\n throw new InvitationWorkspaceMismatchError();\n }\n\n await revokeInvitation({invitationId: params.invitationId});\n}\n"],"names":["emailSchema","generateOpaqueToken","hashOpaqueToken","config","createInvitation","findInvitationById","findInvitationByToken","listOpenInvitationsByWorkspace","reconcileInvitationAcceptance","revokeInvitation","getWorkspaceById","InvitationEmailMismatchError","InvitationNotFoundError","InvitationWorkspaceMismatchError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","requireWorkspaceMembership","INVITATION_TTL_DAYS","daysFromNow","days","Date","now","nonBlankOrFallback","value","fallback","trimmed","trim","createWorkspaceInvitation","params","email","parse","workspace","workspaceId","userId","invitedByUserId","memberships","invitedByMemberships","enforceWorkspaceStatus","rawToken","invitation","hashedToken","expiresAt","invitedByDisplay","sendEmail","workspaceName","name","inviterName","inviteLink","CLIENT_BASE_URL","peekInvitationByRawToken","token","previewInvitation","status","revokedAt","acceptedAt","getTime","id","acceptWorkspaceInvitation","result","reconcileWorkspaceInvitationAcceptance","membership","alreadyMember","invitationId","acceptedByUserId","acceptedByUserName","listWorkspaceInvitations","requesterUserId","requesterMemberships","revokeWorkspaceInvitation"],"mappings":"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"}
@@ -6,6 +6,7 @@ export interface RequireWorkspaceMembershipParams {
6
6
  workspaceId: string;
7
7
  userId: string;
8
8
  memberships: ReadonlyArray<UserContextMembership>;
9
+ enforceWorkspaceStatus?: boolean;
9
10
  }
10
11
  export interface RequireWorkspaceMembershipResult {
11
12
  workspace: Workspace;
@@ -1 +1 @@
1
- {"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/core/workspaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAEL,KAAK,aAAa,EAEnB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAE7B,MAAM,oBAAoB,CAAC;AAK5B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yBAAyB,CAAC;AASvD,MAAM,WAAW,gCAAgC;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,gCAAgC;IAC/C,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,gCAAgC,GACvC,OAAO,CAAC,gCAAgC,CAAC,CAe3C;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACtC,GAAG,OAAO,CAAC,SAAS,CAAC,CA0BrB;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAOpF;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAG/F;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAG/B;AAED,wBAAsB,4BAA4B,CAAC,MAAM,EAAE;IACzD,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAErC;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;CAC5D,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAQhC;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IAC3D,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBhB"}
1
+ {"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/core/workspaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAEL,KAAK,aAAa,EAEnB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAE7B,MAAM,oBAAoB,CAAC;AAK5B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yBAAyB,CAAC;AASvD,MAAM,WAAW,gCAAgC;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IAClD,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,gCAAgC;IAC/C,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,gCAAgC,GACvC,OAAO,CAAC,gCAAgC,CAAC,CAe3C;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACtC,GAAG,OAAO,CAAC,SAAS,CAAC,CA0BrB;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAOpF;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAG/F;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAG/B;AAED,wBAAsB,4BAA4B,CAAC,MAAM,EAAE;IACzD,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAErC;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;CAC5D,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAShC;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IAC3D,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBhB"}
@@ -16,7 +16,7 @@ export async function requireWorkspaceMembership(params) {
16
16
  if (!workspace) {
17
17
  throw new WorkspaceNotFoundError(params.workspaceId);
18
18
  }
19
- if (workspace.status !== 'active') {
19
+ if (params.enforceWorkspaceStatus !== false && workspace.status !== 'active') {
20
20
  throw new WorkspaceInactiveError(params.workspaceId);
21
21
  }
22
22
  return {
@@ -75,7 +75,8 @@ export async function listWorkspaceMembers(params) {
75
75
  await requireWorkspaceMembership({
76
76
  workspaceId: params.workspaceId,
77
77
  userId: params.requesterUserId,
78
- memberships: params.requesterMemberships
78
+ memberships: params.requesterMemberships,
79
+ enforceWorkspaceStatus: false
79
80
  });
80
81
  return listMembershipsByWorkspace({
81
82
  workspaceId: params.workspaceId
@@ -85,7 +86,8 @@ export async function removeWorkspaceMember(params) {
85
86
  await requireWorkspaceMembership({
86
87
  workspaceId: params.workspaceId,
87
88
  userId: params.requesterUserId,
88
- memberships: params.requesterMemberships
89
+ memberships: params.requesterMemberships,
90
+ enforceWorkspaceStatus: false
89
91
  });
90
92
  if (params.userId === params.requesterUserId) {
91
93
  throw new SelfRemovalNotAllowedError();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/workspaces.ts"],"sourcesContent":["import type {UserContextMembership} from '@shipfox/api-auth-context';\nimport {\n WORKSPACES_WORKSPACE_CREATED,\n type WorkspaceRole,\n type WorkspacesEventMap,\n} from '@shipfox/api-workspaces-dto';\nimport {writeOutboxEvent} from '@shipfox/node-outbox';\nimport {db} from '#db/db.js';\nimport {\n findMembership,\n listMembershipsByUser,\n listMembershipsByWorkspace,\n type MembershipWithUser,\n type MembershipWithWorkspace,\n removeMembership,\n} from '#db/memberships.js';\nimport {memberships} from '#db/schema/memberships.js';\nimport {workspacesOutbox} from '#db/schema/outbox.js';\nimport {toWorkspace, workspaces} from '#db/schema/workspaces.js';\nimport {getWorkspaceById} from '#db/workspaces.js';\nimport type {Workspace} from './entities/workspace.js';\nimport {\n MembershipNotFoundError,\n MembershipRequiredError,\n SelfRemovalNotAllowedError,\n WorkspaceInactiveError,\n WorkspaceNotFoundError,\n} from './errors.js';\n\nexport interface RequireWorkspaceMembershipParams {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<UserContextMembership>;\n}\n\nexport interface RequireWorkspaceMembershipResult {\n workspace: Workspace;\n workspaceId: string;\n userId: string;\n role: WorkspaceRole;\n}\n\nexport async function requireWorkspaceMembership(\n params: RequireWorkspaceMembershipParams,\n): Promise<RequireWorkspaceMembershipResult> {\n const membership = params.memberships.find((m) => m.workspaceId === params.workspaceId);\n if (!membership) {\n throw new MembershipRequiredError(params.workspaceId);\n }\n\n const workspace = await getWorkspaceById(params.workspaceId);\n if (!workspace) {\n throw new WorkspaceNotFoundError(params.workspaceId);\n }\n if (workspace.status !== 'active') {\n throw new WorkspaceInactiveError(params.workspaceId);\n }\n\n return {workspace, workspaceId: workspace.id, userId: params.userId, role: membership.role};\n}\n\nexport async function createWorkspaceForUser(params: {\n name: string;\n userId: string;\n userEmail?: string | undefined;\n userName?: string | null | undefined;\n}): Promise<Workspace> {\n return await db().transaction(async (tx) => {\n const [workspaceRow] = await tx\n .insert(workspaces)\n .values({name: params.name, createdBy: params.userId})\n .returning();\n if (!workspaceRow) throw new Error('Insert returned no rows');\n await tx.insert(memberships).values({\n userId: params.userId,\n userEmail: params.userEmail ?? `user-${params.userId}@example.local`,\n userName: params.userName ?? null,\n workspaceId: workspaceRow.id,\n });\n const workspace = toWorkspace(workspaceRow);\n\n await writeOutboxEvent<WorkspacesEventMap>(tx, workspacesOutbox, {\n type: WORKSPACES_WORKSPACE_CREATED,\n payload: {\n workspaceId: workspace.id,\n name: workspace.name,\n creatorUserId: params.userId,\n },\n });\n\n return workspace;\n });\n}\n\nexport async function getWorkspace(params: {workspaceId: string}): Promise<Workspace> {\n const workspace = await getWorkspaceById(params.workspaceId);\n if (!workspace) {\n throw new WorkspaceNotFoundError(params.workspaceId);\n }\n\n return workspace;\n}\n\nexport async function getWorkspaceCreator(params: {workspaceId: string}): Promise<string | null> {\n const workspace = await getWorkspace(params);\n return workspace.createdBy;\n}\n\nexport async function getWorkspaceOperatingState(params: {\n workspaceId: string;\n}): Promise<Workspace['status']> {\n const workspace = await getWorkspace(params);\n return workspace.status;\n}\n\nexport async function listUserWorkspaceMemberships(params: {\n userId: string;\n}): Promise<MembershipWithWorkspace[]> {\n return await listMembershipsByUser({userId: params.userId});\n}\n\nexport async function listWorkspaceMembers(params: {\n workspaceId: string;\n requesterUserId: string;\n requesterMemberships: ReadonlyArray<UserContextMembership>;\n}): Promise<MembershipWithUser[]> {\n await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.requesterUserId,\n memberships: params.requesterMemberships,\n });\n\n return listMembershipsByWorkspace({workspaceId: params.workspaceId});\n}\n\nexport async function removeWorkspaceMember(params: {\n workspaceId: string;\n requesterUserId: string;\n requesterMemberships: ReadonlyArray<UserContextMembership>;\n userId: string;\n}): Promise<void> {\n await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.requesterUserId,\n memberships: params.requesterMemberships,\n });\n\n if (params.userId === params.requesterUserId) {\n throw new SelfRemovalNotAllowedError();\n }\n\n const target = await findMembership({userId: params.userId, workspaceId: params.workspaceId});\n if (!target) {\n throw new MembershipNotFoundError(params.userId, params.workspaceId);\n }\n\n await removeMembership({userId: params.userId, workspaceId: params.workspaceId});\n}\n"],"names":["WORKSPACES_WORKSPACE_CREATED","writeOutboxEvent","db","findMembership","listMembershipsByUser","listMembershipsByWorkspace","removeMembership","memberships","workspacesOutbox","toWorkspace","workspaces","getWorkspaceById","MembershipNotFoundError","MembershipRequiredError","SelfRemovalNotAllowedError","WorkspaceInactiveError","WorkspaceNotFoundError","requireWorkspaceMembership","params","membership","find","m","workspaceId","workspace","status","id","userId","role","createWorkspaceForUser","transaction","tx","workspaceRow","insert","values","name","createdBy","returning","Error","userEmail","userName","type","payload","creatorUserId","getWorkspace","getWorkspaceCreator","getWorkspaceOperatingState","listUserWorkspaceMemberships","listWorkspaceMembers","requesterUserId","requesterMemberships","removeWorkspaceMember","target"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/core/workspaces.ts"],"sourcesContent":["import type {UserContextMembership} from '@shipfox/api-auth-context';\nimport {\n WORKSPACES_WORKSPACE_CREATED,\n type WorkspaceRole,\n type WorkspacesEventMap,\n} from '@shipfox/api-workspaces-dto';\nimport {writeOutboxEvent} from '@shipfox/node-outbox';\nimport {db} from '#db/db.js';\nimport {\n findMembership,\n listMembershipsByUser,\n listMembershipsByWorkspace,\n type MembershipWithUser,\n type MembershipWithWorkspace,\n removeMembership,\n} from '#db/memberships.js';\nimport {memberships} from '#db/schema/memberships.js';\nimport {workspacesOutbox} from '#db/schema/outbox.js';\nimport {toWorkspace, workspaces} from '#db/schema/workspaces.js';\nimport {getWorkspaceById} from '#db/workspaces.js';\nimport type {Workspace} from './entities/workspace.js';\nimport {\n MembershipNotFoundError,\n MembershipRequiredError,\n SelfRemovalNotAllowedError,\n WorkspaceInactiveError,\n WorkspaceNotFoundError,\n} from './errors.js';\n\nexport interface RequireWorkspaceMembershipParams {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<UserContextMembership>;\n enforceWorkspaceStatus?: boolean;\n}\n\nexport interface RequireWorkspaceMembershipResult {\n workspace: Workspace;\n workspaceId: string;\n userId: string;\n role: WorkspaceRole;\n}\n\nexport async function requireWorkspaceMembership(\n params: RequireWorkspaceMembershipParams,\n): Promise<RequireWorkspaceMembershipResult> {\n const membership = params.memberships.find((m) => m.workspaceId === params.workspaceId);\n if (!membership) {\n throw new MembershipRequiredError(params.workspaceId);\n }\n\n const workspace = await getWorkspaceById(params.workspaceId);\n if (!workspace) {\n throw new WorkspaceNotFoundError(params.workspaceId);\n }\n if (params.enforceWorkspaceStatus !== false && workspace.status !== 'active') {\n throw new WorkspaceInactiveError(params.workspaceId);\n }\n\n return {workspace, workspaceId: workspace.id, userId: params.userId, role: membership.role};\n}\n\nexport async function createWorkspaceForUser(params: {\n name: string;\n userId: string;\n userEmail?: string | undefined;\n userName?: string | null | undefined;\n}): Promise<Workspace> {\n return await db().transaction(async (tx) => {\n const [workspaceRow] = await tx\n .insert(workspaces)\n .values({name: params.name, createdBy: params.userId})\n .returning();\n if (!workspaceRow) throw new Error('Insert returned no rows');\n await tx.insert(memberships).values({\n userId: params.userId,\n userEmail: params.userEmail ?? `user-${params.userId}@example.local`,\n userName: params.userName ?? null,\n workspaceId: workspaceRow.id,\n });\n const workspace = toWorkspace(workspaceRow);\n\n await writeOutboxEvent<WorkspacesEventMap>(tx, workspacesOutbox, {\n type: WORKSPACES_WORKSPACE_CREATED,\n payload: {\n workspaceId: workspace.id,\n name: workspace.name,\n creatorUserId: params.userId,\n },\n });\n\n return workspace;\n });\n}\n\nexport async function getWorkspace(params: {workspaceId: string}): Promise<Workspace> {\n const workspace = await getWorkspaceById(params.workspaceId);\n if (!workspace) {\n throw new WorkspaceNotFoundError(params.workspaceId);\n }\n\n return workspace;\n}\n\nexport async function getWorkspaceCreator(params: {workspaceId: string}): Promise<string | null> {\n const workspace = await getWorkspace(params);\n return workspace.createdBy;\n}\n\nexport async function getWorkspaceOperatingState(params: {\n workspaceId: string;\n}): Promise<Workspace['status']> {\n const workspace = await getWorkspace(params);\n return workspace.status;\n}\n\nexport async function listUserWorkspaceMemberships(params: {\n userId: string;\n}): Promise<MembershipWithWorkspace[]> {\n return await listMembershipsByUser({userId: params.userId});\n}\n\nexport async function listWorkspaceMembers(params: {\n workspaceId: string;\n requesterUserId: string;\n requesterMemberships: ReadonlyArray<UserContextMembership>;\n}): Promise<MembershipWithUser[]> {\n await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.requesterUserId,\n memberships: params.requesterMemberships,\n enforceWorkspaceStatus: false,\n });\n\n return listMembershipsByWorkspace({workspaceId: params.workspaceId});\n}\n\nexport async function removeWorkspaceMember(params: {\n workspaceId: string;\n requesterUserId: string;\n requesterMemberships: ReadonlyArray<UserContextMembership>;\n userId: string;\n}): Promise<void> {\n await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.requesterUserId,\n memberships: params.requesterMemberships,\n enforceWorkspaceStatus: false,\n });\n\n if (params.userId === params.requesterUserId) {\n throw new SelfRemovalNotAllowedError();\n }\n\n const target = await findMembership({userId: params.userId, workspaceId: params.workspaceId});\n if (!target) {\n throw new MembershipNotFoundError(params.userId, params.workspaceId);\n }\n\n await removeMembership({userId: params.userId, workspaceId: params.workspaceId});\n}\n"],"names":["WORKSPACES_WORKSPACE_CREATED","writeOutboxEvent","db","findMembership","listMembershipsByUser","listMembershipsByWorkspace","removeMembership","memberships","workspacesOutbox","toWorkspace","workspaces","getWorkspaceById","MembershipNotFoundError","MembershipRequiredError","SelfRemovalNotAllowedError","WorkspaceInactiveError","WorkspaceNotFoundError","requireWorkspaceMembership","params","membership","find","m","workspaceId","workspace","enforceWorkspaceStatus","status","id","userId","role","createWorkspaceForUser","transaction","tx","workspaceRow","insert","values","name","createdBy","returning","Error","userEmail","userName","type","payload","creatorUserId","getWorkspace","getWorkspaceCreator","getWorkspaceOperatingState","listUserWorkspaceMemberships","listWorkspaceMembers","requesterUserId","requesterMemberships","removeWorkspaceMember","target"],"mappings":"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"}
@@ -1 +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;AAkB/B,wBAAgB,uCAAuC,IAAI,uBAAuB,CAChF,OAAO,6BAA6B,CACrC,CAoFA"}
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;AAkB/B,wBAAgB,uCAAuC,IAAI,uBAAuB,CAChF,OAAO,6BAA6B,CACrC,CAuFA"}
@@ -9,9 +9,10 @@ export function createWorkspacesInterModulePresentation() {
9
9
  listMembershipsForTokenClaims: async ({ userId })=>({
10
10
  memberships: (await listMembershipsByUser({
11
11
  userId
12
- })).map(({ workspaceId })=>({
12
+ })).map(({ workspaceId, workspaceStatus })=>({
13
13
  workspaceId,
14
- role: 'admin'
14
+ role: 'admin',
15
+ workspaceStatus
15
16
  }))
16
17
  }),
17
18
  getWorkspaceCreator: async (input)=>{
@@ -1 +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 {\n getWorkspaceCreator,\n getWorkspaceOperatingState,\n requireWorkspaceMembership,\n} 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 getWorkspaceCreator: async (input) => {\n try {\n return {creatorUserId: await getWorkspaceCreator(input)};\n } catch (error) {\n if (error instanceof WorkspaceNotFoundError) {\n throw createInterModuleKnownError(\n workspacesInterModuleContract.methods.getWorkspaceCreator,\n 'workspace-not-found',\n {workspaceId: input.workspaceId},\n );\n }\n throw error;\n }\n },\n getWorkspaceOperatingState: async (input) => {\n try {\n return {status: await getWorkspaceOperatingState(input)};\n } catch (error) {\n if (error instanceof WorkspaceNotFoundError) {\n throw createInterModuleKnownError(\n workspacesInterModuleContract.methods.getWorkspaceOperatingState,\n 'workspace-not-found',\n {workspaceId: input.workspaceId},\n );\n }\n throw error;\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","getWorkspaceCreator","getWorkspaceOperatingState","requireWorkspaceMembership","listMembershipsByUser","createWorkspacesInterModulePresentation","listMembershipsForTokenClaims","userId","memberships","map","workspaceId","role","input","creatorUserId","error","methods","status","preflightInvitationAcceptance","invitation","token","acceptedAt","expiresAt","getTime","Date","now","email","toInvitationKnownError","acceptInvitation","result","membership","id","requireActiveMembership","method","methodName"],"mappings":"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"}
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 {\n getWorkspaceCreator,\n getWorkspaceOperatingState,\n requireWorkspaceMembership,\n} 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(\n ({workspaceId, workspaceStatus}) => ({\n workspaceId,\n role: 'admin' as const,\n workspaceStatus,\n }),\n ),\n }),\n getWorkspaceCreator: async (input) => {\n try {\n return {creatorUserId: await getWorkspaceCreator(input)};\n } catch (error) {\n if (error instanceof WorkspaceNotFoundError) {\n throw createInterModuleKnownError(\n workspacesInterModuleContract.methods.getWorkspaceCreator,\n 'workspace-not-found',\n {workspaceId: input.workspaceId},\n );\n }\n throw error;\n }\n },\n getWorkspaceOperatingState: async (input) => {\n try {\n return {status: await getWorkspaceOperatingState(input)};\n } catch (error) {\n if (error instanceof WorkspaceNotFoundError) {\n throw createInterModuleKnownError(\n workspacesInterModuleContract.methods.getWorkspaceOperatingState,\n 'workspace-not-found',\n {workspaceId: input.workspaceId},\n );\n }\n throw error;\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","getWorkspaceCreator","getWorkspaceOperatingState","requireWorkspaceMembership","listMembershipsByUser","createWorkspacesInterModulePresentation","listMembershipsForTokenClaims","userId","memberships","map","workspaceId","workspaceStatus","role","input","creatorUserId","error","methods","status","preflightInvitationAcceptance","invitation","token","acceptedAt","expiresAt","getTime","Date","now","email","toInvitationKnownError","acceptInvitation","result","membership","id","requireActiveMembership","method","methodName"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../../src/presentation/routes/invitations/create.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,qBAAqB,iDA8ChC,CAAC"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../../src/presentation/routes/invitations/create.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,qBAAqB,iDA+ChC,CAAC"}
@@ -1,4 +1,4 @@
1
- import { AUTH_USER, getUserContext } from '@shipfox/api-auth-context';
1
+ import { AUTH_USER, getUserContext, requireWorkspaceAccess } from '@shipfox/api-auth-context';
2
2
  import { createInvitationBodySchema, invitationDtoSchema } from '@shipfox/api-workspaces-dto';
3
3
  import { ClientError, defineRoute } from '@shipfox/node-fastify';
4
4
  import { z } from 'zod';
@@ -50,6 +50,10 @@ export const createInvitationRoute = defineRoute({
50
50
  });
51
51
  }
52
52
  const { workspaceId } = request.params;
53
+ requireWorkspaceAccess({
54
+ request,
55
+ workspaceId
56
+ });
53
57
  const { email } = request.body;
54
58
  const invitation = await createWorkspaceInvitation({
55
59
  workspaceId,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/presentation/routes/invitations/create.ts"],"sourcesContent":["import {AUTH_USER, getUserContext} from '@shipfox/api-auth-context';\nimport {createInvitationBodySchema, invitationDtoSchema} from '@shipfox/api-workspaces-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {\n MembershipRequiredError,\n OpenInvitationExistsError,\n WorkspaceInactiveError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nimport {createWorkspaceInvitation} from '#core/index.js';\nimport {toInvitationDto} from '#presentation/dto/index.js';\n\nexport const createInvitationRoute = defineRoute({\n method: 'POST',\n path: '/',\n description: 'Invite someone to join a workspace by email.',\n auth: AUTH_USER,\n schema: {\n params: z.object({workspaceId: z.string().uuid()}),\n body: createInvitationBodySchema,\n response: {\n 201: invitationDtoSchema,\n },\n },\n errorHandler: (error) => {\n if (error instanceof WorkspaceNotFoundError) {\n throw new ClientError('Workspace not found', 'not-found', {status: 404});\n }\n if (error instanceof MembershipRequiredError) {\n throw new ClientError('Not a member of this workspace', 'forbidden', {status: 403});\n }\n if (error instanceof OpenInvitationExistsError) {\n throw new ClientError(error.message, 'open-invitation-exists', {status: 409});\n }\n if (error instanceof WorkspaceInactiveError) {\n throw new ClientError('Workspace is not active', 'workspace-inactive', {status: 403});\n }\n throw error;\n },\n handler: async (request, reply) => {\n const client = getUserContext(request);\n if (!client) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n\n const {workspaceId} = request.params;\n const {email} = request.body;\n const invitation = await createWorkspaceInvitation({\n workspaceId,\n email,\n invitedByUserId: client.userId,\n invitedByDisplay: client.name ?? 'A workspace admin',\n invitedByMemberships: client.memberships,\n });\n\n reply.code(201);\n return toInvitationDto(invitation);\n },\n});\n"],"names":["AUTH_USER","getUserContext","createInvitationBodySchema","invitationDtoSchema","ClientError","defineRoute","z","MembershipRequiredError","OpenInvitationExistsError","WorkspaceInactiveError","WorkspaceNotFoundError","createWorkspaceInvitation","toInvitationDto","createInvitationRoute","method","path","description","auth","schema","params","object","workspaceId","string","uuid","body","response","errorHandler","error","status","message","handler","request","reply","client","email","invitation","invitedByUserId","userId","invitedByDisplay","name","invitedByMemberships","memberships","code"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,cAAc,QAAO,4BAA4B;AACpE,SAAQC,0BAA0B,EAAEC,mBAAmB,QAAO,8BAA8B;AAC5F,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SACEC,uBAAuB,EACvBC,yBAAyB,EACzBC,sBAAsB,EACtBC,sBAAsB,QACjB,kBAAkB;AACzB,SAAQC,yBAAyB,QAAO,iBAAiB;AACzD,SAAQC,eAAe,QAAO,6BAA6B;AAE3D,OAAO,MAAMC,wBAAwBR,YAAY;IAC/CS,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,MAAMjB;IACNkB,QAAQ;QACNC,QAAQb,EAAEc,MAAM,CAAC;YAACC,aAAaf,EAAEgB,MAAM,GAAGC,IAAI;QAAE;QAChDC,MAAMtB;QACNuB,UAAU;YACR,KAAKtB;QACP;IACF;IACAuB,cAAc,CAACC;QACb,IAAIA,iBAAiBjB,wBAAwB;YAC3C,MAAM,IAAIN,YAAY,uBAAuB,aAAa;gBAACwB,QAAQ;YAAG;QACxE;QACA,IAAID,iBAAiBpB,yBAAyB;YAC5C,MAAM,IAAIH,YAAY,kCAAkC,aAAa;gBAACwB,QAAQ;YAAG;QACnF;QACA,IAAID,iBAAiBnB,2BAA2B;YAC9C,MAAM,IAAIJ,YAAYuB,MAAME,OAAO,EAAE,0BAA0B;gBAACD,QAAQ;YAAG;QAC7E;QACA,IAAID,iBAAiBlB,wBAAwB;YAC3C,MAAM,IAAIL,YAAY,2BAA2B,sBAAsB;gBAACwB,QAAQ;YAAG;QACrF;QACA,MAAMD;IACR;IACAG,SAAS,OAAOC,SAASC;QACvB,MAAMC,SAAShC,eAAe8B;QAC9B,IAAI,CAACE,QAAQ;YACX,MAAM,IAAI7B,YAAY,2BAA2B,gBAAgB;gBAACwB,QAAQ;YAAG;QAC/E;QAEA,MAAM,EAACP,WAAW,EAAC,GAAGU,QAAQZ,MAAM;QACpC,MAAM,EAACe,KAAK,EAAC,GAAGH,QAAQP,IAAI;QAC5B,MAAMW,aAAa,MAAMxB,0BAA0B;YACjDU;YACAa;YACAE,iBAAiBH,OAAOI,MAAM;YAC9BC,kBAAkBL,OAAOM,IAAI,IAAI;YACjCC,sBAAsBP,OAAOQ,WAAW;QAC1C;QAEAT,MAAMU,IAAI,CAAC;QACX,OAAO9B,gBAAgBuB;IACzB;AACF,GAAG"}
1
+ {"version":3,"sources":["../../../../src/presentation/routes/invitations/create.ts"],"sourcesContent":["import {AUTH_USER, getUserContext, requireWorkspaceAccess} from '@shipfox/api-auth-context';\nimport {createInvitationBodySchema, invitationDtoSchema} from '@shipfox/api-workspaces-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {\n MembershipRequiredError,\n OpenInvitationExistsError,\n WorkspaceInactiveError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nimport {createWorkspaceInvitation} from '#core/index.js';\nimport {toInvitationDto} from '#presentation/dto/index.js';\n\nexport const createInvitationRoute = defineRoute({\n method: 'POST',\n path: '/',\n description: 'Invite someone to join a workspace by email.',\n auth: AUTH_USER,\n schema: {\n params: z.object({workspaceId: z.string().uuid()}),\n body: createInvitationBodySchema,\n response: {\n 201: invitationDtoSchema,\n },\n },\n errorHandler: (error) => {\n if (error instanceof WorkspaceNotFoundError) {\n throw new ClientError('Workspace not found', 'not-found', {status: 404});\n }\n if (error instanceof MembershipRequiredError) {\n throw new ClientError('Not a member of this workspace', 'forbidden', {status: 403});\n }\n if (error instanceof OpenInvitationExistsError) {\n throw new ClientError(error.message, 'open-invitation-exists', {status: 409});\n }\n if (error instanceof WorkspaceInactiveError) {\n throw new ClientError('Workspace is not active', 'workspace-inactive', {status: 403});\n }\n throw error;\n },\n handler: async (request, reply) => {\n const client = getUserContext(request);\n if (!client) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n\n const {workspaceId} = request.params;\n requireWorkspaceAccess({request, workspaceId});\n const {email} = request.body;\n const invitation = await createWorkspaceInvitation({\n workspaceId,\n email,\n invitedByUserId: client.userId,\n invitedByDisplay: client.name ?? 'A workspace admin',\n invitedByMemberships: client.memberships,\n });\n\n reply.code(201);\n return toInvitationDto(invitation);\n },\n});\n"],"names":["AUTH_USER","getUserContext","requireWorkspaceAccess","createInvitationBodySchema","invitationDtoSchema","ClientError","defineRoute","z","MembershipRequiredError","OpenInvitationExistsError","WorkspaceInactiveError","WorkspaceNotFoundError","createWorkspaceInvitation","toInvitationDto","createInvitationRoute","method","path","description","auth","schema","params","object","workspaceId","string","uuid","body","response","errorHandler","error","status","message","handler","request","reply","client","email","invitation","invitedByUserId","userId","invitedByDisplay","name","invitedByMemberships","memberships","code"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,cAAc,EAAEC,sBAAsB,QAAO,4BAA4B;AAC5F,SAAQC,0BAA0B,EAAEC,mBAAmB,QAAO,8BAA8B;AAC5F,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SACEC,uBAAuB,EACvBC,yBAAyB,EACzBC,sBAAsB,EACtBC,sBAAsB,QACjB,kBAAkB;AACzB,SAAQC,yBAAyB,QAAO,iBAAiB;AACzD,SAAQC,eAAe,QAAO,6BAA6B;AAE3D,OAAO,MAAMC,wBAAwBR,YAAY;IAC/CS,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,MAAMlB;IACNmB,QAAQ;QACNC,QAAQb,EAAEc,MAAM,CAAC;YAACC,aAAaf,EAAEgB,MAAM,GAAGC,IAAI;QAAE;QAChDC,MAAMtB;QACNuB,UAAU;YACR,KAAKtB;QACP;IACF;IACAuB,cAAc,CAACC;QACb,IAAIA,iBAAiBjB,wBAAwB;YAC3C,MAAM,IAAIN,YAAY,uBAAuB,aAAa;gBAACwB,QAAQ;YAAG;QACxE;QACA,IAAID,iBAAiBpB,yBAAyB;YAC5C,MAAM,IAAIH,YAAY,kCAAkC,aAAa;gBAACwB,QAAQ;YAAG;QACnF;QACA,IAAID,iBAAiBnB,2BAA2B;YAC9C,MAAM,IAAIJ,YAAYuB,MAAME,OAAO,EAAE,0BAA0B;gBAACD,QAAQ;YAAG;QAC7E;QACA,IAAID,iBAAiBlB,wBAAwB;YAC3C,MAAM,IAAIL,YAAY,2BAA2B,sBAAsB;gBAACwB,QAAQ;YAAG;QACrF;QACA,MAAMD;IACR;IACAG,SAAS,OAAOC,SAASC;QACvB,MAAMC,SAASjC,eAAe+B;QAC9B,IAAI,CAACE,QAAQ;YACX,MAAM,IAAI7B,YAAY,2BAA2B,gBAAgB;gBAACwB,QAAQ;YAAG;QAC/E;QAEA,MAAM,EAACP,WAAW,EAAC,GAAGU,QAAQZ,MAAM;QACpClB,uBAAuB;YAAC8B;YAASV;QAAW;QAC5C,MAAM,EAACa,KAAK,EAAC,GAAGH,QAAQP,IAAI;QAC5B,MAAMW,aAAa,MAAMxB,0BAA0B;YACjDU;YACAa;YACAE,iBAAiBH,OAAOI,MAAM;YAC9BC,kBAAkBL,OAAOM,IAAI,IAAI;YACjCC,sBAAsBP,OAAOQ,WAAW;QAC1C;QAEAT,MAAMU,IAAI,CAAC;QACX,OAAO9B,gBAAgBuB;IACzB;AACF,GAAG"}
@@ -1 +1 @@
1
- {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../../src/presentation/routes/invitations/list.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,oBAAoB,iDAwC/B,CAAC"}
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../../src/presentation/routes/invitations/list.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,oBAAoB,iDAyC/B,CAAC"}
@@ -1,4 +1,4 @@
1
- import { AUTH_USER, getUserContext } from '@shipfox/api-auth-context';
1
+ import { AUTH_USER, getUserContext, requireWorkspaceAccess } from '@shipfox/api-auth-context';
2
2
  import { listInvitationsResponseSchema } from '@shipfox/api-workspaces-dto';
3
3
  import { ClientError, defineRoute } from '@shipfox/node-fastify';
4
4
  import { z } from 'zod';
@@ -44,6 +44,10 @@ export const listInvitationsRoute = defineRoute({
44
44
  });
45
45
  }
46
46
  const { workspaceId } = request.params;
47
+ requireWorkspaceAccess({
48
+ request,
49
+ workspaceId
50
+ });
47
51
  const invitations = await listWorkspaceInvitations({
48
52
  workspaceId,
49
53
  requesterUserId: client.userId,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/presentation/routes/invitations/list.ts"],"sourcesContent":["import {AUTH_USER, getUserContext} from '@shipfox/api-auth-context';\nimport {listInvitationsResponseSchema} from '@shipfox/api-workspaces-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {\n MembershipRequiredError,\n WorkspaceInactiveError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nimport {listWorkspaceInvitations} from '#core/index.js';\nimport {toInvitationDto} from '#presentation/dto/index.js';\n\nexport const listInvitationsRoute = defineRoute({\n method: 'GET',\n path: '/',\n description: 'List pending invitations for a workspace.',\n auth: AUTH_USER,\n schema: {\n params: z.object({workspaceId: z.string().uuid()}),\n response: {\n 200: listInvitationsResponseSchema,\n },\n },\n errorHandler: (error) => {\n if (error instanceof WorkspaceNotFoundError) {\n throw new ClientError('Workspace not found', 'not-found', {status: 404});\n }\n if (error instanceof MembershipRequiredError) {\n throw new ClientError('Not a member of this workspace', 'forbidden', {status: 403});\n }\n if (error instanceof WorkspaceInactiveError) {\n throw new ClientError('Workspace is not active', 'workspace-inactive', {status: 403});\n }\n throw error;\n },\n handler: async (request) => {\n const client = getUserContext(request);\n if (!client) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n\n const {workspaceId} = request.params;\n const invitations = await listWorkspaceInvitations({\n workspaceId,\n requesterUserId: client.userId,\n requesterMemberships: client.memberships,\n });\n\n return {\n invitations: invitations.map(toInvitationDto),\n };\n },\n});\n"],"names":["AUTH_USER","getUserContext","listInvitationsResponseSchema","ClientError","defineRoute","z","MembershipRequiredError","WorkspaceInactiveError","WorkspaceNotFoundError","listWorkspaceInvitations","toInvitationDto","listInvitationsRoute","method","path","description","auth","schema","params","object","workspaceId","string","uuid","response","errorHandler","error","status","handler","request","client","invitations","requesterUserId","userId","requesterMemberships","memberships","map"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,cAAc,QAAO,4BAA4B;AACpE,SAAQC,6BAA6B,QAAO,8BAA8B;AAC1E,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SACEC,uBAAuB,EACvBC,sBAAsB,EACtBC,sBAAsB,QACjB,kBAAkB;AACzB,SAAQC,wBAAwB,QAAO,iBAAiB;AACxD,SAAQC,eAAe,QAAO,6BAA6B;AAE3D,OAAO,MAAMC,uBAAuBP,YAAY;IAC9CQ,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,MAAMf;IACNgB,QAAQ;QACNC,QAAQZ,EAAEa,MAAM,CAAC;YAACC,aAAad,EAAEe,MAAM,GAAGC,IAAI;QAAE;QAChDC,UAAU;YACR,KAAKpB;QACP;IACF;IACAqB,cAAc,CAACC;QACb,IAAIA,iBAAiBhB,wBAAwB;YAC3C,MAAM,IAAIL,YAAY,uBAAuB,aAAa;gBAACsB,QAAQ;YAAG;QACxE;QACA,IAAID,iBAAiBlB,yBAAyB;YAC5C,MAAM,IAAIH,YAAY,kCAAkC,aAAa;gBAACsB,QAAQ;YAAG;QACnF;QACA,IAAID,iBAAiBjB,wBAAwB;YAC3C,MAAM,IAAIJ,YAAY,2BAA2B,sBAAsB;gBAACsB,QAAQ;YAAG;QACrF;QACA,MAAMD;IACR;IACAE,SAAS,OAAOC;QACd,MAAMC,SAAS3B,eAAe0B;QAC9B,IAAI,CAACC,QAAQ;YACX,MAAM,IAAIzB,YAAY,2BAA2B,gBAAgB;gBAACsB,QAAQ;YAAG;QAC/E;QAEA,MAAM,EAACN,WAAW,EAAC,GAAGQ,QAAQV,MAAM;QACpC,MAAMY,cAAc,MAAMpB,yBAAyB;YACjDU;YACAW,iBAAiBF,OAAOG,MAAM;YAC9BC,sBAAsBJ,OAAOK,WAAW;QAC1C;QAEA,OAAO;YACLJ,aAAaA,YAAYK,GAAG,CAACxB;QAC/B;IACF;AACF,GAAG"}
1
+ {"version":3,"sources":["../../../../src/presentation/routes/invitations/list.ts"],"sourcesContent":["import {AUTH_USER, getUserContext, requireWorkspaceAccess} from '@shipfox/api-auth-context';\nimport {listInvitationsResponseSchema} from '@shipfox/api-workspaces-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {\n MembershipRequiredError,\n WorkspaceInactiveError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nimport {listWorkspaceInvitations} from '#core/index.js';\nimport {toInvitationDto} from '#presentation/dto/index.js';\n\nexport const listInvitationsRoute = defineRoute({\n method: 'GET',\n path: '/',\n description: 'List pending invitations for a workspace.',\n auth: AUTH_USER,\n schema: {\n params: z.object({workspaceId: z.string().uuid()}),\n response: {\n 200: listInvitationsResponseSchema,\n },\n },\n errorHandler: (error) => {\n if (error instanceof WorkspaceNotFoundError) {\n throw new ClientError('Workspace not found', 'not-found', {status: 404});\n }\n if (error instanceof MembershipRequiredError) {\n throw new ClientError('Not a member of this workspace', 'forbidden', {status: 403});\n }\n if (error instanceof WorkspaceInactiveError) {\n throw new ClientError('Workspace is not active', 'workspace-inactive', {status: 403});\n }\n throw error;\n },\n handler: async (request) => {\n const client = getUserContext(request);\n if (!client) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n\n const {workspaceId} = request.params;\n requireWorkspaceAccess({request, workspaceId});\n const invitations = await listWorkspaceInvitations({\n workspaceId,\n requesterUserId: client.userId,\n requesterMemberships: client.memberships,\n });\n\n return {\n invitations: invitations.map(toInvitationDto),\n };\n },\n});\n"],"names":["AUTH_USER","getUserContext","requireWorkspaceAccess","listInvitationsResponseSchema","ClientError","defineRoute","z","MembershipRequiredError","WorkspaceInactiveError","WorkspaceNotFoundError","listWorkspaceInvitations","toInvitationDto","listInvitationsRoute","method","path","description","auth","schema","params","object","workspaceId","string","uuid","response","errorHandler","error","status","handler","request","client","invitations","requesterUserId","userId","requesterMemberships","memberships","map"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,cAAc,EAAEC,sBAAsB,QAAO,4BAA4B;AAC5F,SAAQC,6BAA6B,QAAO,8BAA8B;AAC1E,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SACEC,uBAAuB,EACvBC,sBAAsB,EACtBC,sBAAsB,QACjB,kBAAkB;AACzB,SAAQC,wBAAwB,QAAO,iBAAiB;AACxD,SAAQC,eAAe,QAAO,6BAA6B;AAE3D,OAAO,MAAMC,uBAAuBP,YAAY;IAC9CQ,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,MAAMhB;IACNiB,QAAQ;QACNC,QAAQZ,EAAEa,MAAM,CAAC;YAACC,aAAad,EAAEe,MAAM,GAAGC,IAAI;QAAE;QAChDC,UAAU;YACR,KAAKpB;QACP;IACF;IACAqB,cAAc,CAACC;QACb,IAAIA,iBAAiBhB,wBAAwB;YAC3C,MAAM,IAAIL,YAAY,uBAAuB,aAAa;gBAACsB,QAAQ;YAAG;QACxE;QACA,IAAID,iBAAiBlB,yBAAyB;YAC5C,MAAM,IAAIH,YAAY,kCAAkC,aAAa;gBAACsB,QAAQ;YAAG;QACnF;QACA,IAAID,iBAAiBjB,wBAAwB;YAC3C,MAAM,IAAIJ,YAAY,2BAA2B,sBAAsB;gBAACsB,QAAQ;YAAG;QACrF;QACA,MAAMD;IACR;IACAE,SAAS,OAAOC;QACd,MAAMC,SAAS5B,eAAe2B;QAC9B,IAAI,CAACC,QAAQ;YACX,MAAM,IAAIzB,YAAY,2BAA2B,gBAAgB;gBAACsB,QAAQ;YAAG;QAC/E;QAEA,MAAM,EAACN,WAAW,EAAC,GAAGQ,QAAQV,MAAM;QACpChB,uBAAuB;YAAC0B;YAASR;QAAW;QAC5C,MAAMU,cAAc,MAAMpB,yBAAyB;YACjDU;YACAW,iBAAiBF,OAAOG,MAAM;YAC9BC,sBAAsBJ,OAAOK,WAAW;QAC1C;QAEA,OAAO;YACLJ,aAAaA,YAAYK,GAAG,CAACxB;QAC/B;IACF;AACF,GAAG"}
@@ -1 +1 @@
1
- {"version":3,"file":"revoke.d.ts","sourceRoot":"","sources":["../../../../src/presentation/routes/invitations/revoke.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,qBAAqB,iDAmDhC,CAAC"}
1
+ {"version":3,"file":"revoke.d.ts","sourceRoot":"","sources":["../../../../src/presentation/routes/invitations/revoke.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,qBAAqB,iDAoDhC,CAAC"}
@@ -1,4 +1,4 @@
1
- import { AUTH_USER, getUserContext } from '@shipfox/api-auth-context';
1
+ import { AUTH_USER, getUserContext, requireWorkspaceAccess } from '@shipfox/api-auth-context';
2
2
  import { ClientError, defineRoute } from '@shipfox/node-fastify';
3
3
  import { z } from 'zod';
4
4
  import { InvitationNotFoundError, InvitationWorkspaceMismatchError, MembershipRequiredError, WorkspaceInactiveError, WorkspaceNotFoundError } from '#core/errors.js';
@@ -53,6 +53,10 @@ export const revokeInvitationRoute = defineRoute({
53
53
  });
54
54
  }
55
55
  const { workspaceId, invitationId } = request.params;
56
+ requireWorkspaceAccess({
57
+ request,
58
+ workspaceId
59
+ });
56
60
  await revokeWorkspaceInvitation({
57
61
  workspaceId,
58
62
  invitationId,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/presentation/routes/invitations/revoke.ts"],"sourcesContent":["import {AUTH_USER, getUserContext} from '@shipfox/api-auth-context';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {\n InvitationNotFoundError,\n InvitationWorkspaceMismatchError,\n MembershipRequiredError,\n WorkspaceInactiveError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nimport {revokeWorkspaceInvitation} from '#core/index.js';\n\nexport const revokeInvitationRoute = defineRoute({\n method: 'DELETE',\n path: '/:invitationId',\n description: 'Cancel a pending invitation.',\n auth: AUTH_USER,\n schema: {\n params: z.object({\n workspaceId: z.string().uuid(),\n invitationId: z.string().uuid(),\n }),\n response: {\n 204: z.void(),\n },\n },\n errorHandler: (error) => {\n if (error instanceof WorkspaceNotFoundError) {\n throw new ClientError('Workspace not found', 'not-found', {status: 404});\n }\n if (error instanceof MembershipRequiredError) {\n throw new ClientError('Not a member of this workspace', 'forbidden', {status: 403});\n }\n if (error instanceof InvitationNotFoundError) {\n throw new ClientError('Invitation not found', 'not-found', {status: 404});\n }\n if (error instanceof InvitationWorkspaceMismatchError) {\n throw new ClientError('Invitation does not belong to this workspace', 'forbidden', {\n status: 403,\n });\n }\n if (error instanceof WorkspaceInactiveError) {\n throw new ClientError('Workspace is not active', 'workspace-inactive', {status: 403});\n }\n throw error;\n },\n handler: async (request, reply) => {\n const client = getUserContext(request);\n if (!client) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n\n const {workspaceId, invitationId} = request.params;\n\n await revokeWorkspaceInvitation({\n workspaceId,\n invitationId,\n requesterUserId: client.userId,\n requesterMemberships: client.memberships,\n });\n\n reply.code(204);\n },\n});\n"],"names":["AUTH_USER","getUserContext","ClientError","defineRoute","z","InvitationNotFoundError","InvitationWorkspaceMismatchError","MembershipRequiredError","WorkspaceInactiveError","WorkspaceNotFoundError","revokeWorkspaceInvitation","revokeInvitationRoute","method","path","description","auth","schema","params","object","workspaceId","string","uuid","invitationId","response","void","errorHandler","error","status","handler","request","reply","client","requesterUserId","userId","requesterMemberships","memberships","code"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,cAAc,QAAO,4BAA4B;AACpE,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SACEC,uBAAuB,EACvBC,gCAAgC,EAChCC,uBAAuB,EACvBC,sBAAsB,EACtBC,sBAAsB,QACjB,kBAAkB;AACzB,SAAQC,yBAAyB,QAAO,iBAAiB;AAEzD,OAAO,MAAMC,wBAAwBR,YAAY;IAC/CS,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,MAAMf;IACNgB,QAAQ;QACNC,QAAQb,EAAEc,MAAM,CAAC;YACfC,aAAaf,EAAEgB,MAAM,GAAGC,IAAI;YAC5BC,cAAclB,EAAEgB,MAAM,GAAGC,IAAI;QAC/B;QACAE,UAAU;YACR,KAAKnB,EAAEoB,IAAI;QACb;IACF;IACAC,cAAc,CAACC;QACb,IAAIA,iBAAiBjB,wBAAwB;YAC3C,MAAM,IAAIP,YAAY,uBAAuB,aAAa;gBAACyB,QAAQ;YAAG;QACxE;QACA,IAAID,iBAAiBnB,yBAAyB;YAC5C,MAAM,IAAIL,YAAY,kCAAkC,aAAa;gBAACyB,QAAQ;YAAG;QACnF;QACA,IAAID,iBAAiBrB,yBAAyB;YAC5C,MAAM,IAAIH,YAAY,wBAAwB,aAAa;gBAACyB,QAAQ;YAAG;QACzE;QACA,IAAID,iBAAiBpB,kCAAkC;YACrD,MAAM,IAAIJ,YAAY,gDAAgD,aAAa;gBACjFyB,QAAQ;YACV;QACF;QACA,IAAID,iBAAiBlB,wBAAwB;YAC3C,MAAM,IAAIN,YAAY,2BAA2B,sBAAsB;gBAACyB,QAAQ;YAAG;QACrF;QACA,MAAMD;IACR;IACAE,SAAS,OAAOC,SAASC;QACvB,MAAMC,SAAS9B,eAAe4B;QAC9B,IAAI,CAACE,QAAQ;YACX,MAAM,IAAI7B,YAAY,2BAA2B,gBAAgB;gBAACyB,QAAQ;YAAG;QAC/E;QAEA,MAAM,EAACR,WAAW,EAAEG,YAAY,EAAC,GAAGO,QAAQZ,MAAM;QAElD,MAAMP,0BAA0B;YAC9BS;YACAG;YACAU,iBAAiBD,OAAOE,MAAM;YAC9BC,sBAAsBH,OAAOI,WAAW;QAC1C;QAEAL,MAAMM,IAAI,CAAC;IACb;AACF,GAAG"}
1
+ {"version":3,"sources":["../../../../src/presentation/routes/invitations/revoke.ts"],"sourcesContent":["import {AUTH_USER, getUserContext, requireWorkspaceAccess} from '@shipfox/api-auth-context';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {\n InvitationNotFoundError,\n InvitationWorkspaceMismatchError,\n MembershipRequiredError,\n WorkspaceInactiveError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nimport {revokeWorkspaceInvitation} from '#core/index.js';\n\nexport const revokeInvitationRoute = defineRoute({\n method: 'DELETE',\n path: '/:invitationId',\n description: 'Cancel a pending invitation.',\n auth: AUTH_USER,\n schema: {\n params: z.object({\n workspaceId: z.string().uuid(),\n invitationId: z.string().uuid(),\n }),\n response: {\n 204: z.void(),\n },\n },\n errorHandler: (error) => {\n if (error instanceof WorkspaceNotFoundError) {\n throw new ClientError('Workspace not found', 'not-found', {status: 404});\n }\n if (error instanceof MembershipRequiredError) {\n throw new ClientError('Not a member of this workspace', 'forbidden', {status: 403});\n }\n if (error instanceof InvitationNotFoundError) {\n throw new ClientError('Invitation not found', 'not-found', {status: 404});\n }\n if (error instanceof InvitationWorkspaceMismatchError) {\n throw new ClientError('Invitation does not belong to this workspace', 'forbidden', {\n status: 403,\n });\n }\n if (error instanceof WorkspaceInactiveError) {\n throw new ClientError('Workspace is not active', 'workspace-inactive', {status: 403});\n }\n throw error;\n },\n handler: async (request, reply) => {\n const client = getUserContext(request);\n if (!client) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n\n const {workspaceId, invitationId} = request.params;\n requireWorkspaceAccess({request, workspaceId});\n\n await revokeWorkspaceInvitation({\n workspaceId,\n invitationId,\n requesterUserId: client.userId,\n requesterMemberships: client.memberships,\n });\n\n reply.code(204);\n },\n});\n"],"names":["AUTH_USER","getUserContext","requireWorkspaceAccess","ClientError","defineRoute","z","InvitationNotFoundError","InvitationWorkspaceMismatchError","MembershipRequiredError","WorkspaceInactiveError","WorkspaceNotFoundError","revokeWorkspaceInvitation","revokeInvitationRoute","method","path","description","auth","schema","params","object","workspaceId","string","uuid","invitationId","response","void","errorHandler","error","status","handler","request","reply","client","requesterUserId","userId","requesterMemberships","memberships","code"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,cAAc,EAAEC,sBAAsB,QAAO,4BAA4B;AAC5F,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SACEC,uBAAuB,EACvBC,gCAAgC,EAChCC,uBAAuB,EACvBC,sBAAsB,EACtBC,sBAAsB,QACjB,kBAAkB;AACzB,SAAQC,yBAAyB,QAAO,iBAAiB;AAEzD,OAAO,MAAMC,wBAAwBR,YAAY;IAC/CS,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,MAAMhB;IACNiB,QAAQ;QACNC,QAAQb,EAAEc,MAAM,CAAC;YACfC,aAAaf,EAAEgB,MAAM,GAAGC,IAAI;YAC5BC,cAAclB,EAAEgB,MAAM,GAAGC,IAAI;QAC/B;QACAE,UAAU;YACR,KAAKnB,EAAEoB,IAAI;QACb;IACF;IACAC,cAAc,CAACC;QACb,IAAIA,iBAAiBjB,wBAAwB;YAC3C,MAAM,IAAIP,YAAY,uBAAuB,aAAa;gBAACyB,QAAQ;YAAG;QACxE;QACA,IAAID,iBAAiBnB,yBAAyB;YAC5C,MAAM,IAAIL,YAAY,kCAAkC,aAAa;gBAACyB,QAAQ;YAAG;QACnF;QACA,IAAID,iBAAiBrB,yBAAyB;YAC5C,MAAM,IAAIH,YAAY,wBAAwB,aAAa;gBAACyB,QAAQ;YAAG;QACzE;QACA,IAAID,iBAAiBpB,kCAAkC;YACrD,MAAM,IAAIJ,YAAY,gDAAgD,aAAa;gBACjFyB,QAAQ;YACV;QACF;QACA,IAAID,iBAAiBlB,wBAAwB;YAC3C,MAAM,IAAIN,YAAY,2BAA2B,sBAAsB;gBAACyB,QAAQ;YAAG;QACrF;QACA,MAAMD;IACR;IACAE,SAAS,OAAOC,SAASC;QACvB,MAAMC,SAAS/B,eAAe6B;QAC9B,IAAI,CAACE,QAAQ;YACX,MAAM,IAAI7B,YAAY,2BAA2B,gBAAgB;gBAACyB,QAAQ;YAAG;QAC/E;QAEA,MAAM,EAACR,WAAW,EAAEG,YAAY,EAAC,GAAGO,QAAQZ,MAAM;QAClDhB,uBAAuB;YAAC4B;YAASV;QAAW;QAE5C,MAAMT,0BAA0B;YAC9BS;YACAG;YACAU,iBAAiBD,OAAOE,MAAM;YAC9BC,sBAAsBH,OAAOI,WAAW;QAC1C;QAEAL,MAAMM,IAAI,CAAC;IACb;AACF,GAAG"}
@@ -1 +1 @@
1
- {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../../src/presentation/routes/members/list.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,gBAAgB,iDAwC3B,CAAC"}
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../../src/presentation/routes/members/list.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,gBAAgB,iDAyC3B,CAAC"}
@@ -1,4 +1,4 @@
1
- import { AUTH_USER, getUserContext } from '@shipfox/api-auth-context';
1
+ import { AUTH_USER, getUserContext, requireWorkspaceAccess } from '@shipfox/api-auth-context';
2
2
  import { listMembersResponseSchema } from '@shipfox/api-workspaces-dto';
3
3
  import { ClientError, defineRoute } from '@shipfox/node-fastify';
4
4
  import { z } from 'zod';
@@ -44,6 +44,10 @@ export const listMembersRoute = defineRoute({
44
44
  });
45
45
  }
46
46
  const { workspaceId } = request.params;
47
+ requireWorkspaceAccess({
48
+ request,
49
+ workspaceId
50
+ });
47
51
  const members = await listWorkspaceMembers({
48
52
  workspaceId,
49
53
  requesterUserId: client.userId,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/presentation/routes/members/list.ts"],"sourcesContent":["import {AUTH_USER, getUserContext} from '@shipfox/api-auth-context';\nimport {listMembersResponseSchema} from '@shipfox/api-workspaces-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {\n MembershipRequiredError,\n WorkspaceInactiveError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nimport {listWorkspaceMembers} from '#core/index.js';\nimport {toMembershipWithUserDto} from '#presentation/dto/index.js';\n\nexport const listMembersRoute = defineRoute({\n method: 'GET',\n path: '/',\n description: 'List the members of a workspace.',\n auth: AUTH_USER,\n schema: {\n params: z.object({workspaceId: z.string().uuid()}),\n response: {\n 200: listMembersResponseSchema,\n },\n },\n errorHandler: (error) => {\n if (error instanceof WorkspaceNotFoundError) {\n throw new ClientError('Workspace not found', 'not-found', {status: 404});\n }\n if (error instanceof MembershipRequiredError) {\n throw new ClientError('Not a member of this workspace', 'forbidden', {status: 403});\n }\n if (error instanceof WorkspaceInactiveError) {\n throw new ClientError('Workspace is not active', 'workspace-inactive', {status: 403});\n }\n throw error;\n },\n handler: async (request) => {\n const client = getUserContext(request);\n if (!client) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n\n const {workspaceId} = request.params;\n const members = await listWorkspaceMembers({\n workspaceId,\n requesterUserId: client.userId,\n requesterMemberships: client.memberships,\n });\n\n return {\n members: members.map(toMembershipWithUserDto),\n };\n },\n});\n"],"names":["AUTH_USER","getUserContext","listMembersResponseSchema","ClientError","defineRoute","z","MembershipRequiredError","WorkspaceInactiveError","WorkspaceNotFoundError","listWorkspaceMembers","toMembershipWithUserDto","listMembersRoute","method","path","description","auth","schema","params","object","workspaceId","string","uuid","response","errorHandler","error","status","handler","request","client","members","requesterUserId","userId","requesterMemberships","memberships","map"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,cAAc,QAAO,4BAA4B;AACpE,SAAQC,yBAAyB,QAAO,8BAA8B;AACtE,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SACEC,uBAAuB,EACvBC,sBAAsB,EACtBC,sBAAsB,QACjB,kBAAkB;AACzB,SAAQC,oBAAoB,QAAO,iBAAiB;AACpD,SAAQC,uBAAuB,QAAO,6BAA6B;AAEnE,OAAO,MAAMC,mBAAmBP,YAAY;IAC1CQ,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,MAAMf;IACNgB,QAAQ;QACNC,QAAQZ,EAAEa,MAAM,CAAC;YAACC,aAAad,EAAEe,MAAM,GAAGC,IAAI;QAAE;QAChDC,UAAU;YACR,KAAKpB;QACP;IACF;IACAqB,cAAc,CAACC;QACb,IAAIA,iBAAiBhB,wBAAwB;YAC3C,MAAM,IAAIL,YAAY,uBAAuB,aAAa;gBAACsB,QAAQ;YAAG;QACxE;QACA,IAAID,iBAAiBlB,yBAAyB;YAC5C,MAAM,IAAIH,YAAY,kCAAkC,aAAa;gBAACsB,QAAQ;YAAG;QACnF;QACA,IAAID,iBAAiBjB,wBAAwB;YAC3C,MAAM,IAAIJ,YAAY,2BAA2B,sBAAsB;gBAACsB,QAAQ;YAAG;QACrF;QACA,MAAMD;IACR;IACAE,SAAS,OAAOC;QACd,MAAMC,SAAS3B,eAAe0B;QAC9B,IAAI,CAACC,QAAQ;YACX,MAAM,IAAIzB,YAAY,2BAA2B,gBAAgB;gBAACsB,QAAQ;YAAG;QAC/E;QAEA,MAAM,EAACN,WAAW,EAAC,GAAGQ,QAAQV,MAAM;QACpC,MAAMY,UAAU,MAAMpB,qBAAqB;YACzCU;YACAW,iBAAiBF,OAAOG,MAAM;YAC9BC,sBAAsBJ,OAAOK,WAAW;QAC1C;QAEA,OAAO;YACLJ,SAASA,QAAQK,GAAG,CAACxB;QACvB;IACF;AACF,GAAG"}
1
+ {"version":3,"sources":["../../../../src/presentation/routes/members/list.ts"],"sourcesContent":["import {AUTH_USER, getUserContext, requireWorkspaceAccess} from '@shipfox/api-auth-context';\nimport {listMembersResponseSchema} from '@shipfox/api-workspaces-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {\n MembershipRequiredError,\n WorkspaceInactiveError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nimport {listWorkspaceMembers} from '#core/index.js';\nimport {toMembershipWithUserDto} from '#presentation/dto/index.js';\n\nexport const listMembersRoute = defineRoute({\n method: 'GET',\n path: '/',\n description: 'List the members of a workspace.',\n auth: AUTH_USER,\n schema: {\n params: z.object({workspaceId: z.string().uuid()}),\n response: {\n 200: listMembersResponseSchema,\n },\n },\n errorHandler: (error) => {\n if (error instanceof WorkspaceNotFoundError) {\n throw new ClientError('Workspace not found', 'not-found', {status: 404});\n }\n if (error instanceof MembershipRequiredError) {\n throw new ClientError('Not a member of this workspace', 'forbidden', {status: 403});\n }\n if (error instanceof WorkspaceInactiveError) {\n throw new ClientError('Workspace is not active', 'workspace-inactive', {status: 403});\n }\n throw error;\n },\n handler: async (request) => {\n const client = getUserContext(request);\n if (!client) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n\n const {workspaceId} = request.params;\n requireWorkspaceAccess({request, workspaceId});\n const members = await listWorkspaceMembers({\n workspaceId,\n requesterUserId: client.userId,\n requesterMemberships: client.memberships,\n });\n\n return {\n members: members.map(toMembershipWithUserDto),\n };\n },\n});\n"],"names":["AUTH_USER","getUserContext","requireWorkspaceAccess","listMembersResponseSchema","ClientError","defineRoute","z","MembershipRequiredError","WorkspaceInactiveError","WorkspaceNotFoundError","listWorkspaceMembers","toMembershipWithUserDto","listMembersRoute","method","path","description","auth","schema","params","object","workspaceId","string","uuid","response","errorHandler","error","status","handler","request","client","members","requesterUserId","userId","requesterMemberships","memberships","map"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,cAAc,EAAEC,sBAAsB,QAAO,4BAA4B;AAC5F,SAAQC,yBAAyB,QAAO,8BAA8B;AACtE,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SACEC,uBAAuB,EACvBC,sBAAsB,EACtBC,sBAAsB,QACjB,kBAAkB;AACzB,SAAQC,oBAAoB,QAAO,iBAAiB;AACpD,SAAQC,uBAAuB,QAAO,6BAA6B;AAEnE,OAAO,MAAMC,mBAAmBP,YAAY;IAC1CQ,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,MAAMhB;IACNiB,QAAQ;QACNC,QAAQZ,EAAEa,MAAM,CAAC;YAACC,aAAad,EAAEe,MAAM,GAAGC,IAAI;QAAE;QAChDC,UAAU;YACR,KAAKpB;QACP;IACF;IACAqB,cAAc,CAACC;QACb,IAAIA,iBAAiBhB,wBAAwB;YAC3C,MAAM,IAAIL,YAAY,uBAAuB,aAAa;gBAACsB,QAAQ;YAAG;QACxE;QACA,IAAID,iBAAiBlB,yBAAyB;YAC5C,MAAM,IAAIH,YAAY,kCAAkC,aAAa;gBAACsB,QAAQ;YAAG;QACnF;QACA,IAAID,iBAAiBjB,wBAAwB;YAC3C,MAAM,IAAIJ,YAAY,2BAA2B,sBAAsB;gBAACsB,QAAQ;YAAG;QACrF;QACA,MAAMD;IACR;IACAE,SAAS,OAAOC;QACd,MAAMC,SAAS5B,eAAe2B;QAC9B,IAAI,CAACC,QAAQ;YACX,MAAM,IAAIzB,YAAY,2BAA2B,gBAAgB;gBAACsB,QAAQ;YAAG;QAC/E;QAEA,MAAM,EAACN,WAAW,EAAC,GAAGQ,QAAQV,MAAM;QACpChB,uBAAuB;YAAC0B;YAASR;QAAW;QAC5C,MAAMU,UAAU,MAAMpB,qBAAqB;YACzCU;YACAW,iBAAiBF,OAAOG,MAAM;YAC9BC,sBAAsBJ,OAAOK,WAAW;QAC1C;QAEA,OAAO;YACLJ,SAASA,QAAQK,GAAG,CAACxB;QACvB;IACF;AACF,GAAG"}
@@ -1 +1 @@
1
- {"version":3,"file":"remove.d.ts","sourceRoot":"","sources":["../../../../src/presentation/routes/members/remove.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,iBAAiB,iDAoD5B,CAAC"}
1
+ {"version":3,"file":"remove.d.ts","sourceRoot":"","sources":["../../../../src/presentation/routes/members/remove.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,iBAAiB,iDAqD5B,CAAC"}
@@ -1,4 +1,4 @@
1
- import { AUTH_USER, getUserContext } from '@shipfox/api-auth-context';
1
+ import { AUTH_USER, getUserContext, requireWorkspaceAccess } from '@shipfox/api-auth-context';
2
2
  import { ClientError, defineRoute } from '@shipfox/node-fastify';
3
3
  import { z } from 'zod';
4
4
  import { LastMemberError, MembershipNotFoundError, MembershipRequiredError, SelfRemovalNotAllowedError, WorkspaceInactiveError, WorkspaceNotFoundError } from '#core/errors.js';
@@ -58,6 +58,10 @@ export const removeMemberRoute = defineRoute({
58
58
  });
59
59
  }
60
60
  const { workspaceId, userId } = request.params;
61
+ requireWorkspaceAccess({
62
+ request,
63
+ workspaceId
64
+ });
61
65
  await removeWorkspaceMember({
62
66
  workspaceId,
63
67
  userId,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/presentation/routes/members/remove.ts"],"sourcesContent":["import {AUTH_USER, getUserContext} from '@shipfox/api-auth-context';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {\n LastMemberError,\n MembershipNotFoundError,\n MembershipRequiredError,\n SelfRemovalNotAllowedError,\n WorkspaceInactiveError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nimport {removeWorkspaceMember} from '#core/index.js';\n\nexport const removeMemberRoute = defineRoute({\n method: 'DELETE',\n path: '/:userId',\n description: 'Remove a member from a workspace.',\n auth: AUTH_USER,\n schema: {\n params: z.object({\n workspaceId: z.string().uuid(),\n userId: z.string().uuid(),\n }),\n response: {\n 204: z.void(),\n },\n },\n errorHandler: (error) => {\n if (error instanceof WorkspaceNotFoundError) {\n throw new ClientError('Workspace not found', 'not-found', {status: 404});\n }\n if (error instanceof MembershipRequiredError) {\n throw new ClientError('Not a member of this workspace', 'forbidden', {status: 403});\n }\n if (error instanceof MembershipNotFoundError) {\n throw new ClientError('Member not found', 'not-found', {status: 404});\n }\n if (error instanceof LastMemberError) {\n throw new ClientError(error.message, 'last-member', {status: 409});\n }\n if (error instanceof SelfRemovalNotAllowedError) {\n throw new ClientError(error.message, 'self-removal-not-allowed', {status: 409});\n }\n if (error instanceof WorkspaceInactiveError) {\n throw new ClientError('Workspace is not active', 'workspace-inactive', {status: 403});\n }\n throw error;\n },\n handler: async (request, reply) => {\n const client = getUserContext(request);\n if (!client) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n\n const {workspaceId, userId} = request.params;\n\n await removeWorkspaceMember({\n workspaceId,\n userId,\n requesterUserId: client.userId,\n requesterMemberships: client.memberships,\n });\n\n reply.code(204);\n },\n});\n"],"names":["AUTH_USER","getUserContext","ClientError","defineRoute","z","LastMemberError","MembershipNotFoundError","MembershipRequiredError","SelfRemovalNotAllowedError","WorkspaceInactiveError","WorkspaceNotFoundError","removeWorkspaceMember","removeMemberRoute","method","path","description","auth","schema","params","object","workspaceId","string","uuid","userId","response","void","errorHandler","error","status","message","handler","request","reply","client","requesterUserId","requesterMemberships","memberships","code"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,cAAc,QAAO,4BAA4B;AACpE,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SACEC,eAAe,EACfC,uBAAuB,EACvBC,uBAAuB,EACvBC,0BAA0B,EAC1BC,sBAAsB,EACtBC,sBAAsB,QACjB,kBAAkB;AACzB,SAAQC,qBAAqB,QAAO,iBAAiB;AAErD,OAAO,MAAMC,oBAAoBT,YAAY;IAC3CU,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,MAAMhB;IACNiB,QAAQ;QACNC,QAAQd,EAAEe,MAAM,CAAC;YACfC,aAAahB,EAAEiB,MAAM,GAAGC,IAAI;YAC5BC,QAAQnB,EAAEiB,MAAM,GAAGC,IAAI;QACzB;QACAE,UAAU;YACR,KAAKpB,EAAEqB,IAAI;QACb;IACF;IACAC,cAAc,CAACC;QACb,IAAIA,iBAAiBjB,wBAAwB;YAC3C,MAAM,IAAIR,YAAY,uBAAuB,aAAa;gBAAC0B,QAAQ;YAAG;QACxE;QACA,IAAID,iBAAiBpB,yBAAyB;YAC5C,MAAM,IAAIL,YAAY,kCAAkC,aAAa;gBAAC0B,QAAQ;YAAG;QACnF;QACA,IAAID,iBAAiBrB,yBAAyB;YAC5C,MAAM,IAAIJ,YAAY,oBAAoB,aAAa;gBAAC0B,QAAQ;YAAG;QACrE;QACA,IAAID,iBAAiBtB,iBAAiB;YACpC,MAAM,IAAIH,YAAYyB,MAAME,OAAO,EAAE,eAAe;gBAACD,QAAQ;YAAG;QAClE;QACA,IAAID,iBAAiBnB,4BAA4B;YAC/C,MAAM,IAAIN,YAAYyB,MAAME,OAAO,EAAE,4BAA4B;gBAACD,QAAQ;YAAG;QAC/E;QACA,IAAID,iBAAiBlB,wBAAwB;YAC3C,MAAM,IAAIP,YAAY,2BAA2B,sBAAsB;gBAAC0B,QAAQ;YAAG;QACrF;QACA,MAAMD;IACR;IACAG,SAAS,OAAOC,SAASC;QACvB,MAAMC,SAAShC,eAAe8B;QAC9B,IAAI,CAACE,QAAQ;YACX,MAAM,IAAI/B,YAAY,2BAA2B,gBAAgB;gBAAC0B,QAAQ;YAAG;QAC/E;QAEA,MAAM,EAACR,WAAW,EAAEG,MAAM,EAAC,GAAGQ,QAAQb,MAAM;QAE5C,MAAMP,sBAAsB;YAC1BS;YACAG;YACAW,iBAAiBD,OAAOV,MAAM;YAC9BY,sBAAsBF,OAAOG,WAAW;QAC1C;QAEAJ,MAAMK,IAAI,CAAC;IACb;AACF,GAAG"}
1
+ {"version":3,"sources":["../../../../src/presentation/routes/members/remove.ts"],"sourcesContent":["import {AUTH_USER, getUserContext, requireWorkspaceAccess} from '@shipfox/api-auth-context';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {\n LastMemberError,\n MembershipNotFoundError,\n MembershipRequiredError,\n SelfRemovalNotAllowedError,\n WorkspaceInactiveError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nimport {removeWorkspaceMember} from '#core/index.js';\n\nexport const removeMemberRoute = defineRoute({\n method: 'DELETE',\n path: '/:userId',\n description: 'Remove a member from a workspace.',\n auth: AUTH_USER,\n schema: {\n params: z.object({\n workspaceId: z.string().uuid(),\n userId: z.string().uuid(),\n }),\n response: {\n 204: z.void(),\n },\n },\n errorHandler: (error) => {\n if (error instanceof WorkspaceNotFoundError) {\n throw new ClientError('Workspace not found', 'not-found', {status: 404});\n }\n if (error instanceof MembershipRequiredError) {\n throw new ClientError('Not a member of this workspace', 'forbidden', {status: 403});\n }\n if (error instanceof MembershipNotFoundError) {\n throw new ClientError('Member not found', 'not-found', {status: 404});\n }\n if (error instanceof LastMemberError) {\n throw new ClientError(error.message, 'last-member', {status: 409});\n }\n if (error instanceof SelfRemovalNotAllowedError) {\n throw new ClientError(error.message, 'self-removal-not-allowed', {status: 409});\n }\n if (error instanceof WorkspaceInactiveError) {\n throw new ClientError('Workspace is not active', 'workspace-inactive', {status: 403});\n }\n throw error;\n },\n handler: async (request, reply) => {\n const client = getUserContext(request);\n if (!client) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n\n const {workspaceId, userId} = request.params;\n requireWorkspaceAccess({request, workspaceId});\n\n await removeWorkspaceMember({\n workspaceId,\n userId,\n requesterUserId: client.userId,\n requesterMemberships: client.memberships,\n });\n\n reply.code(204);\n },\n});\n"],"names":["AUTH_USER","getUserContext","requireWorkspaceAccess","ClientError","defineRoute","z","LastMemberError","MembershipNotFoundError","MembershipRequiredError","SelfRemovalNotAllowedError","WorkspaceInactiveError","WorkspaceNotFoundError","removeWorkspaceMember","removeMemberRoute","method","path","description","auth","schema","params","object","workspaceId","string","uuid","userId","response","void","errorHandler","error","status","message","handler","request","reply","client","requesterUserId","requesterMemberships","memberships","code"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,cAAc,EAAEC,sBAAsB,QAAO,4BAA4B;AAC5F,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SACEC,eAAe,EACfC,uBAAuB,EACvBC,uBAAuB,EACvBC,0BAA0B,EAC1BC,sBAAsB,EACtBC,sBAAsB,QACjB,kBAAkB;AACzB,SAAQC,qBAAqB,QAAO,iBAAiB;AAErD,OAAO,MAAMC,oBAAoBT,YAAY;IAC3CU,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,MAAMjB;IACNkB,QAAQ;QACNC,QAAQd,EAAEe,MAAM,CAAC;YACfC,aAAahB,EAAEiB,MAAM,GAAGC,IAAI;YAC5BC,QAAQnB,EAAEiB,MAAM,GAAGC,IAAI;QACzB;QACAE,UAAU;YACR,KAAKpB,EAAEqB,IAAI;QACb;IACF;IACAC,cAAc,CAACC;QACb,IAAIA,iBAAiBjB,wBAAwB;YAC3C,MAAM,IAAIR,YAAY,uBAAuB,aAAa;gBAAC0B,QAAQ;YAAG;QACxE;QACA,IAAID,iBAAiBpB,yBAAyB;YAC5C,MAAM,IAAIL,YAAY,kCAAkC,aAAa;gBAAC0B,QAAQ;YAAG;QACnF;QACA,IAAID,iBAAiBrB,yBAAyB;YAC5C,MAAM,IAAIJ,YAAY,oBAAoB,aAAa;gBAAC0B,QAAQ;YAAG;QACrE;QACA,IAAID,iBAAiBtB,iBAAiB;YACpC,MAAM,IAAIH,YAAYyB,MAAME,OAAO,EAAE,eAAe;gBAACD,QAAQ;YAAG;QAClE;QACA,IAAID,iBAAiBnB,4BAA4B;YAC/C,MAAM,IAAIN,YAAYyB,MAAME,OAAO,EAAE,4BAA4B;gBAACD,QAAQ;YAAG;QAC/E;QACA,IAAID,iBAAiBlB,wBAAwB;YAC3C,MAAM,IAAIP,YAAY,2BAA2B,sBAAsB;gBAAC0B,QAAQ;YAAG;QACrF;QACA,MAAMD;IACR;IACAG,SAAS,OAAOC,SAASC;QACvB,MAAMC,SAASjC,eAAe+B;QAC9B,IAAI,CAACE,QAAQ;YACX,MAAM,IAAI/B,YAAY,2BAA2B,gBAAgB;gBAAC0B,QAAQ;YAAG;QAC/E;QAEA,MAAM,EAACR,WAAW,EAAEG,MAAM,EAAC,GAAGQ,QAAQb,MAAM;QAC5CjB,uBAAuB;YAAC8B;YAASX;QAAW;QAE5C,MAAMT,sBAAsB;YAC1BS;YACAG;YACAW,iBAAiBD,OAAOV,MAAM;YAC9BY,sBAAsBF,OAAOG,WAAW;QAC1C;QAEAJ,MAAMK,IAAI,CAAC;IACb;AACF,GAAG"}