@shipfox/api-workspaces 10.2.0 → 12.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +48 -0
- package/README.md +27 -4
- package/dist/core/entities/workspace.d.ts +1 -0
- package/dist/core/entities/workspace.d.ts.map +1 -1
- package/dist/core/entities/workspace.js.map +1 -1
- package/dist/core/errors.d.ts +3 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +6 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/invitations.d.ts.map +1 -1
- package/dist/core/invitations.js +7 -4
- package/dist/core/invitations.js.map +1 -1
- package/dist/core/rate-limit.d.ts +39 -0
- package/dist/core/rate-limit.d.ts.map +1 -0
- package/dist/core/rate-limit.js +70 -0
- package/dist/core/rate-limit.js.map +1 -0
- package/dist/core/workspaces.d.ts +8 -0
- package/dist/core/workspaces.d.ts.map +1 -1
- package/dist/core/workspaces.js +47 -7
- package/dist/core/workspaces.js.map +1 -1
- package/dist/db/db.d.ts +320 -0
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +3 -1
- package/dist/db/db.js.map +1 -1
- package/dist/db/index.d.ts +4 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +3 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/memberships.d.ts +1 -0
- package/dist/db/memberships.d.ts.map +1 -1
- package/dist/db/memberships.js +2 -0
- package/dist/db/memberships.js.map +1 -1
- package/dist/db/rate-limits.d.ts +9 -0
- package/dist/db/rate-limits.d.ts.map +1 -0
- package/dist/db/rate-limits.js +43 -0
- package/dist/db/rate-limits.js.map +1 -0
- package/dist/db/schema/rate-limits.d.ts +145 -0
- package/dist/db/schema/rate-limits.d.ts.map +1 -0
- package/dist/db/schema/rate-limits.js +25 -0
- package/dist/db/schema/rate-limits.js.map +1 -0
- package/dist/db/schema/workspaces.d.ts +17 -0
- package/dist/db/schema/workspaces.d.ts.map +1 -1
- package/dist/db/schema/workspaces.js +5 -2
- package/dist/db/schema/workspaces.js.map +1 -1
- package/dist/db/workspaces.d.ts +11 -1
- package/dist/db/workspaces.d.ts.map +1 -1
- package/dist/db/workspaces.js +12 -3
- package/dist/db/workspaces.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics/instance.d.ts +6 -0
- package/dist/metrics/instance.d.ts.map +1 -1
- package/dist/metrics/instance.js +12 -0
- package/dist/metrics/instance.js.map +1 -1
- package/dist/presentation/dto/membership.d.ts.map +1 -1
- package/dist/presentation/dto/membership.js +1 -0
- package/dist/presentation/dto/membership.js.map +1 -1
- package/dist/presentation/dto/workspace.d.ts.map +1 -1
- package/dist/presentation/dto/workspace.js +1 -0
- package/dist/presentation/dto/workspace.js.map +1 -1
- package/dist/presentation/e2eRoutes/create-workspace.d.ts.map +1 -1
- package/dist/presentation/e2eRoutes/create-workspace.js +11 -1
- package/dist/presentation/e2eRoutes/create-workspace.js.map +1 -1
- package/dist/presentation/inter-module.d.ts.map +1 -1
- package/dist/presentation/inter-module.js +3 -2
- package/dist/presentation/inter-module.js.map +1 -1
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js +3 -1
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/presentation/routes/invitations/create.d.ts.map +1 -1
- package/dist/presentation/routes/invitations/create.js +5 -1
- package/dist/presentation/routes/invitations/create.js.map +1 -1
- package/dist/presentation/routes/invitations/list.d.ts.map +1 -1
- package/dist/presentation/routes/invitations/list.js +5 -1
- package/dist/presentation/routes/invitations/list.js.map +1 -1
- package/dist/presentation/routes/invitations/revoke.d.ts.map +1 -1
- package/dist/presentation/routes/invitations/revoke.js +5 -1
- package/dist/presentation/routes/invitations/revoke.js.map +1 -1
- package/dist/presentation/routes/members/list.d.ts.map +1 -1
- package/dist/presentation/routes/members/list.js +5 -1
- package/dist/presentation/routes/members/list.js.map +1 -1
- package/dist/presentation/routes/members/remove.d.ts.map +1 -1
- package/dist/presentation/routes/members/remove.js +5 -1
- package/dist/presentation/routes/members/remove.js.map +1 -1
- package/dist/presentation/routes/rate-limit.d.ts +3 -0
- package/dist/presentation/routes/rate-limit.d.ts.map +1 -0
- package/dist/presentation/routes/rate-limit.js +42 -0
- package/dist/presentation/routes/rate-limit.js.map +1 -0
- package/dist/presentation/routes/workspaces/create.d.ts.map +1 -1
- package/dist/presentation/routes/workspaces/create.js +11 -1
- package/dist/presentation/routes/workspaces/create.js.map +1 -1
- package/dist/presentation/routes/workspaces/index.d.ts +2 -0
- package/dist/presentation/routes/workspaces/index.d.ts.map +1 -1
- package/dist/presentation/routes/workspaces/index.js +2 -0
- package/dist/presentation/routes/workspaces/index.js.map +1 -1
- package/dist/presentation/routes/workspaces/slug-availability.d.ts +2 -0
- package/dist/presentation/routes/workspaces/slug-availability.d.ts.map +1 -0
- package/dist/presentation/routes/workspaces/slug-availability.js +31 -0
- package/dist/presentation/routes/workspaces/slug-availability.js.map +1 -0
- package/dist/presentation/routes/workspaces/update.d.ts +2 -0
- package/dist/presentation/routes/workspaces/update.d.ts.map +1 -0
- package/dist/presentation/routes/workspaces/update.js +49 -0
- package/dist/presentation/routes/workspaces/update.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0000_initial.sql +2 -0
- package/drizzle/0003_dapper_talos.sql +13 -0
- package/drizzle/meta/0000_snapshot.json +23 -1
- package/drizzle/meta/0003_snapshot.json +689 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +14 -12
- package/src/core/entities/workspace.ts +1 -0
- package/src/core/errors.ts +7 -0
- package/src/core/index.ts +3 -0
- package/src/core/invitations.test.ts +8 -8
- package/src/core/invitations.ts +4 -1
- package/src/core/rate-limit.test.ts +113 -0
- package/src/core/rate-limit.ts +119 -0
- package/src/core/workspaces.test.ts +31 -3
- package/src/core/workspaces.ts +51 -4
- package/src/db/db.ts +2 -0
- package/src/db/index.ts +10 -0
- package/src/db/memberships.ts +3 -0
- package/src/db/rate-limits.test.ts +74 -0
- package/src/db/rate-limits.ts +59 -0
- package/src/db/schema/rate-limits.ts +27 -0
- package/src/db/schema/workspaces.ts +7 -2
- package/src/db/workspaces.ts +21 -1
- package/src/index.test.ts +2 -0
- package/src/index.ts +1 -0
- package/src/metrics/instance.ts +26 -0
- package/src/presentation/dto/membership.ts +1 -0
- package/src/presentation/dto/workspace.ts +1 -0
- package/src/presentation/e2eRoutes/create-workspace.ts +9 -1
- package/src/presentation/inter-module.test.ts +69 -0
- package/src/presentation/inter-module.ts +7 -4
- package/src/presentation/routes/index.ts +8 -1
- package/src/presentation/routes/invitations/create.test.ts +15 -0
- package/src/presentation/routes/invitations/create.ts +2 -1
- package/src/presentation/routes/invitations/list.test.ts +30 -0
- package/src/presentation/routes/invitations/list.ts +2 -1
- package/src/presentation/routes/invitations/revoke.test.ts +14 -0
- package/src/presentation/routes/invitations/revoke.ts +2 -1
- package/src/presentation/routes/members/list.test.ts +29 -1
- package/src/presentation/routes/members/list.ts +2 -1
- package/src/presentation/routes/members/remove.test.ts +14 -0
- package/src/presentation/routes/members/remove.ts +2 -1
- package/src/presentation/routes/rate-limit.ts +45 -0
- package/src/presentation/routes/workspaces/create.test.ts +33 -3
- package/src/presentation/routes/workspaces/create.ts +9 -1
- package/src/presentation/routes/workspaces/index.ts +2 -0
- package/src/presentation/routes/workspaces/list.test.ts +1 -0
- package/src/presentation/routes/workspaces/slug-availability.test.ts +159 -0
- package/src/presentation/routes/workspaces/slug-availability.ts +30 -0
- package/src/presentation/routes/workspaces/update.test.ts +114 -0
- package/src/presentation/routes/workspaces/update.ts +41 -0
- package/test/env.ts +1 -0
- package/test/factories/workspace.ts +3 -1
- package/test/globalSetup.ts +1 -1
- package/test/routes.ts +26 -7
- package/tsconfig.build.tsbuildinfo +1 -1
package/dist/metrics/instance.js
CHANGED
|
@@ -12,6 +12,12 @@ const invitationCreatedCount = meter.createCounter('workspaces_invitation_create
|
|
|
12
12
|
const invitationAcceptedCount = meter.createCounter('workspaces_invitation_accepted', {
|
|
13
13
|
description: 'Workspace invitations accepted by membership outcome'
|
|
14
14
|
});
|
|
15
|
+
const workspaceRateLimitCheckCount = meter.createCounter('workspaces_rate_limit_checks', {
|
|
16
|
+
description: 'Workspace rate-limit checks by action and outcome'
|
|
17
|
+
});
|
|
18
|
+
const workspaceRateLimitPruneFailureCount = meter.createCounter('workspaces_rate_limit_prune_failures', {
|
|
19
|
+
description: 'Workspace rate-limit prune failures'
|
|
20
|
+
});
|
|
15
21
|
function recordMetric(record) {
|
|
16
22
|
try {
|
|
17
23
|
record();
|
|
@@ -37,5 +43,11 @@ export function recordWorkspaceInvitationAccepted(outcome) {
|
|
|
37
43
|
outcome
|
|
38
44
|
}));
|
|
39
45
|
}
|
|
46
|
+
export function recordWorkspaceRateLimitCheck(params) {
|
|
47
|
+
recordMetric(()=>workspaceRateLimitCheckCount.add(1, params));
|
|
48
|
+
}
|
|
49
|
+
export function recordWorkspaceRateLimitPruneFailure() {
|
|
50
|
+
recordMetric(()=>workspaceRateLimitPruneFailureCount.add(1));
|
|
51
|
+
}
|
|
40
52
|
|
|
41
53
|
//# sourceMappingURL=instance.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/metrics/instance.ts"],"sourcesContent":["import {instanceMetrics} from '@shipfox/node-opentelemetry';\n\nexport type WorkspacesInvitationEmailRequested = 'requested' | 'skipped';\nexport type WorkspacesMembershipChangeAction = 'added' | 'removed';\nexport type WorkspacesInvitationAcceptOutcome = 'added' | 'already_member';\n\nconst meter = instanceMetrics.getMeter('workspaces');\n\nconst workspaceCreatedCount = meter.createCounter<Record<string, never>>('workspaces_created', {\n description: 'Workspaces created',\n});\n\nconst membershipChangedCount = meter.createCounter<{\n action: WorkspacesMembershipChangeAction;\n}>('workspaces_membership_changed', {\n description: 'Workspace membership changes by action',\n});\n\nconst invitationCreatedCount = meter.createCounter<{\n email_requested: WorkspacesInvitationEmailRequested;\n}>('workspaces_invitation_created', {\n description: 'Workspace invitations created by whether an invitation email was requested',\n});\n\nconst invitationAcceptedCount = meter.createCounter<{\n outcome: WorkspacesInvitationAcceptOutcome;\n}>('workspaces_invitation_accepted', {\n description: 'Workspace invitations accepted by membership outcome',\n});\n\nfunction recordMetric(record: () => void): void {\n try {\n record();\n } catch {\n // Metrics must not affect workspace mutations.\n }\n}\n\nexport function recordWorkspaceCreated(): void {\n recordMetric(() => workspaceCreatedCount.add(1));\n}\n\nexport function recordWorkspaceMembershipChanged(action: WorkspacesMembershipChangeAction): void {\n recordMetric(() => membershipChangedCount.add(1, {action}));\n}\n\nexport function recordWorkspaceInvitationCreated(\n emailRequested: WorkspacesInvitationEmailRequested,\n): void {\n recordMetric(() => invitationCreatedCount.add(1, {email_requested: emailRequested}));\n}\n\nexport function recordWorkspaceInvitationAccepted(\n outcome: WorkspacesInvitationAcceptOutcome,\n): void {\n recordMetric(() => invitationAcceptedCount.add(1, {outcome}));\n}\n"],"names":["instanceMetrics","meter","getMeter","workspaceCreatedCount","createCounter","description","membershipChangedCount","invitationCreatedCount","invitationAcceptedCount","recordMetric","record","recordWorkspaceCreated","add","recordWorkspaceMembershipChanged","action","recordWorkspaceInvitationCreated","emailRequested","email_requested","recordWorkspaceInvitationAccepted","outcome"],"mappings":"AAAA,SAAQA,eAAe,QAAO,8BAA8B;
|
|
1
|
+
{"version":3,"sources":["../../src/metrics/instance.ts"],"sourcesContent":["import {instanceMetrics} from '@shipfox/node-opentelemetry';\n\nexport type WorkspacesInvitationEmailRequested = 'requested' | 'skipped';\nexport type WorkspacesMembershipChangeAction = 'added' | 'removed';\nexport type WorkspacesInvitationAcceptOutcome = 'added' | 'already_member';\nexport type WorkspacesRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';\n\nconst meter = instanceMetrics.getMeter('workspaces');\n\nconst workspaceCreatedCount = meter.createCounter<Record<string, never>>('workspaces_created', {\n description: 'Workspaces created',\n});\n\nconst membershipChangedCount = meter.createCounter<{\n action: WorkspacesMembershipChangeAction;\n}>('workspaces_membership_changed', {\n description: 'Workspace membership changes by action',\n});\n\nconst invitationCreatedCount = meter.createCounter<{\n email_requested: WorkspacesInvitationEmailRequested;\n}>('workspaces_invitation_created', {\n description: 'Workspace invitations created by whether an invitation email was requested',\n});\n\nconst invitationAcceptedCount = meter.createCounter<{\n outcome: WorkspacesInvitationAcceptOutcome;\n}>('workspaces_invitation_accepted', {\n description: 'Workspace invitations accepted by membership outcome',\n});\n\nconst workspaceRateLimitCheckCount = meter.createCounter<{\n action: 'slug-availability';\n outcome: WorkspacesRateLimitOutcome;\n}>('workspaces_rate_limit_checks', {\n description: 'Workspace rate-limit checks by action and outcome',\n});\n\nconst workspaceRateLimitPruneFailureCount = meter.createCounter(\n 'workspaces_rate_limit_prune_failures',\n {\n description: 'Workspace rate-limit prune failures',\n },\n);\n\nfunction recordMetric(record: () => void): void {\n try {\n record();\n } catch {\n // Metrics must not affect workspace mutations.\n }\n}\n\nexport function recordWorkspaceCreated(): void {\n recordMetric(() => workspaceCreatedCount.add(1));\n}\n\nexport function recordWorkspaceMembershipChanged(action: WorkspacesMembershipChangeAction): void {\n recordMetric(() => membershipChangedCount.add(1, {action}));\n}\n\nexport function recordWorkspaceInvitationCreated(\n emailRequested: WorkspacesInvitationEmailRequested,\n): void {\n recordMetric(() => invitationCreatedCount.add(1, {email_requested: emailRequested}));\n}\n\nexport function recordWorkspaceInvitationAccepted(\n outcome: WorkspacesInvitationAcceptOutcome,\n): void {\n recordMetric(() => invitationAcceptedCount.add(1, {outcome}));\n}\n\nexport function recordWorkspaceRateLimitCheck(params: {\n action: 'slug-availability';\n outcome: WorkspacesRateLimitOutcome;\n}): void {\n recordMetric(() => workspaceRateLimitCheckCount.add(1, params));\n}\n\nexport function recordWorkspaceRateLimitPruneFailure(): void {\n recordMetric(() => workspaceRateLimitPruneFailureCount.add(1));\n}\n"],"names":["instanceMetrics","meter","getMeter","workspaceCreatedCount","createCounter","description","membershipChangedCount","invitationCreatedCount","invitationAcceptedCount","workspaceRateLimitCheckCount","workspaceRateLimitPruneFailureCount","recordMetric","record","recordWorkspaceCreated","add","recordWorkspaceMembershipChanged","action","recordWorkspaceInvitationCreated","emailRequested","email_requested","recordWorkspaceInvitationAccepted","outcome","recordWorkspaceRateLimitCheck","params","recordWorkspaceRateLimitPruneFailure"],"mappings":"AAAA,SAAQA,eAAe,QAAO,8BAA8B;AAO5D,MAAMC,QAAQD,gBAAgBE,QAAQ,CAAC;AAEvC,MAAMC,wBAAwBF,MAAMG,aAAa,CAAwB,sBAAsB;IAC7FC,aAAa;AACf;AAEA,MAAMC,yBAAyBL,MAAMG,aAAa,CAE/C,iCAAiC;IAClCC,aAAa;AACf;AAEA,MAAME,yBAAyBN,MAAMG,aAAa,CAE/C,iCAAiC;IAClCC,aAAa;AACf;AAEA,MAAMG,0BAA0BP,MAAMG,aAAa,CAEhD,kCAAkC;IACnCC,aAAa;AACf;AAEA,MAAMI,+BAA+BR,MAAMG,aAAa,CAGrD,gCAAgC;IACjCC,aAAa;AACf;AAEA,MAAMK,sCAAsCT,MAAMG,aAAa,CAC7D,wCACA;IACEC,aAAa;AACf;AAGF,SAASM,aAAaC,MAAkB;IACtC,IAAI;QACFA;IACF,EAAE,OAAM;IACN,+CAA+C;IACjD;AACF;AAEA,OAAO,SAASC;IACdF,aAAa,IAAMR,sBAAsBW,GAAG,CAAC;AAC/C;AAEA,OAAO,SAASC,iCAAiCC,MAAwC;IACvFL,aAAa,IAAML,uBAAuBQ,GAAG,CAAC,GAAG;YAACE;QAAM;AAC1D;AAEA,OAAO,SAASC,iCACdC,cAAkD;IAElDP,aAAa,IAAMJ,uBAAuBO,GAAG,CAAC,GAAG;YAACK,iBAAiBD;QAAc;AACnF;AAEA,OAAO,SAASE,kCACdC,OAA0C;IAE1CV,aAAa,IAAMH,wBAAwBM,GAAG,CAAC,GAAG;YAACO;QAAO;AAC5D;AAEA,OAAO,SAASC,8BAA8BC,MAG7C;IACCZ,aAAa,IAAMF,6BAA6BK,GAAG,CAAC,GAAGS;AACzD;AAEA,OAAO,SAASC;IACdb,aAAa,IAAMD,oCAAoCI,GAAG,CAAC;AAC7D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"membership.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/membership.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAE,0BAA0B,EAAC,MAAM,6BAA6B,CAAC;AACnG,OAAO,KAAK,EAAC,kBAAkB,EAAE,uBAAuB,EAAC,MAAM,oBAAoB,CAAC;AAEpF,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,uBAAuB,GAClC,0BAA0B,
|
|
1
|
+
{"version":3,"file":"membership.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/membership.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAE,0BAA0B,EAAC,MAAM,6BAA6B,CAAC;AACnG,OAAO,KAAK,EAAC,kBAAkB,EAAE,uBAAuB,EAAC,MAAM,oBAAoB,CAAC;AAEpF,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,uBAAuB,GAClC,0BAA0B,CAW5B;AAED,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,kBAAkB,GAAG,qBAAqB,CAU7F"}
|
|
@@ -4,6 +4,7 @@ export function toMembershipWithWorkspaceDto(membership) {
|
|
|
4
4
|
user_id: membership.userId,
|
|
5
5
|
workspace_id: membership.workspaceId,
|
|
6
6
|
workspace_name: membership.workspaceName,
|
|
7
|
+
workspace_slug: membership.workspaceSlug,
|
|
7
8
|
workspace_status: membership.workspaceStatus,
|
|
8
9
|
created_at: membership.createdAt.toISOString(),
|
|
9
10
|
updated_at: membership.updatedAt.toISOString()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/dto/membership.ts"],"sourcesContent":["import type {MembershipWithUserDto, MembershipWithWorkspaceDto} from '@shipfox/api-workspaces-dto';\nimport type {MembershipWithUser, MembershipWithWorkspace} from '#db/memberships.js';\n\nexport function toMembershipWithWorkspaceDto(\n membership: MembershipWithWorkspace,\n): MembershipWithWorkspaceDto {\n return {\n id: membership.id,\n user_id: membership.userId,\n workspace_id: membership.workspaceId,\n workspace_name: membership.workspaceName,\n workspace_status: membership.workspaceStatus,\n created_at: membership.createdAt.toISOString(),\n updated_at: membership.updatedAt.toISOString(),\n };\n}\n\nexport function toMembershipWithUserDto(membership: MembershipWithUser): MembershipWithUserDto {\n return {\n id: membership.id,\n user_id: membership.userId,\n workspace_id: membership.workspaceId,\n user_email: membership.userEmail,\n user_name: membership.userName,\n created_at: membership.createdAt.toISOString(),\n updated_at: membership.updatedAt.toISOString(),\n };\n}\n"],"names":["toMembershipWithWorkspaceDto","membership","id","user_id","userId","workspace_id","workspaceId","workspace_name","workspaceName","workspace_status","workspaceStatus","created_at","createdAt","toISOString","updated_at","updatedAt","toMembershipWithUserDto","user_email","userEmail","user_name","userName"],"mappings":"AAGA,OAAO,SAASA,6BACdC,UAAmC;IAEnC,OAAO;QACLC,IAAID,WAAWC,EAAE;QACjBC,SAASF,WAAWG,MAAM;QAC1BC,cAAcJ,WAAWK,WAAW;QACpCC,gBAAgBN,WAAWO,aAAa;QACxCC,
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/dto/membership.ts"],"sourcesContent":["import type {MembershipWithUserDto, MembershipWithWorkspaceDto} from '@shipfox/api-workspaces-dto';\nimport type {MembershipWithUser, MembershipWithWorkspace} from '#db/memberships.js';\n\nexport function toMembershipWithWorkspaceDto(\n membership: MembershipWithWorkspace,\n): MembershipWithWorkspaceDto {\n return {\n id: membership.id,\n user_id: membership.userId,\n workspace_id: membership.workspaceId,\n workspace_name: membership.workspaceName,\n workspace_slug: membership.workspaceSlug,\n workspace_status: membership.workspaceStatus,\n created_at: membership.createdAt.toISOString(),\n updated_at: membership.updatedAt.toISOString(),\n };\n}\n\nexport function toMembershipWithUserDto(membership: MembershipWithUser): MembershipWithUserDto {\n return {\n id: membership.id,\n user_id: membership.userId,\n workspace_id: membership.workspaceId,\n user_email: membership.userEmail,\n user_name: membership.userName,\n created_at: membership.createdAt.toISOString(),\n updated_at: membership.updatedAt.toISOString(),\n };\n}\n"],"names":["toMembershipWithWorkspaceDto","membership","id","user_id","userId","workspace_id","workspaceId","workspace_name","workspaceName","workspace_slug","workspaceSlug","workspace_status","workspaceStatus","created_at","createdAt","toISOString","updated_at","updatedAt","toMembershipWithUserDto","user_email","userEmail","user_name","userName"],"mappings":"AAGA,OAAO,SAASA,6BACdC,UAAmC;IAEnC,OAAO;QACLC,IAAID,WAAWC,EAAE;QACjBC,SAASF,WAAWG,MAAM;QAC1BC,cAAcJ,WAAWK,WAAW;QACpCC,gBAAgBN,WAAWO,aAAa;QACxCC,gBAAgBR,WAAWS,aAAa;QACxCC,kBAAkBV,WAAWW,eAAe;QAC5CC,YAAYZ,WAAWa,SAAS,CAACC,WAAW;QAC5CC,YAAYf,WAAWgB,SAAS,CAACF,WAAW;IAC9C;AACF;AAEA,OAAO,SAASG,wBAAwBjB,UAA8B;IACpE,OAAO;QACLC,IAAID,WAAWC,EAAE;QACjBC,SAASF,WAAWG,MAAM;QAC1BC,cAAcJ,WAAWK,WAAW;QACpCa,YAAYlB,WAAWmB,SAAS;QAChCC,WAAWpB,WAAWqB,QAAQ;QAC9BT,YAAYZ,WAAWa,SAAS,CAACC,WAAW;QAC5CC,YAAYf,WAAWgB,SAAS,CAACF,WAAW;IAC9C;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/workspace.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAE3D,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/workspace.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAE3D,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,YAAY,CAUjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/dto/workspace.ts"],"sourcesContent":["import type {WorkspaceDto} from '@shipfox/api-workspaces-dto';\nimport type {Workspace} from '#core/entities/workspace.js';\n\nexport function toWorkspaceDto(workspace: Workspace): WorkspaceDto {\n return {\n id: workspace.id,\n name: workspace.name,\n status: workspace.status,\n settings: workspace.settings,\n created_at: workspace.createdAt.toISOString(),\n updated_at: workspace.updatedAt.toISOString(),\n };\n}\n"],"names":["toWorkspaceDto","workspace","id","name","status","settings","created_at","createdAt","toISOString","updated_at","updatedAt"],"mappings":"AAGA,OAAO,SAASA,eAAeC,SAAoB;IACjD,OAAO;QACLC,IAAID,UAAUC,EAAE;QAChBC,MAAMF,UAAUE,IAAI;QACpBC,
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/dto/workspace.ts"],"sourcesContent":["import type {WorkspaceDto} from '@shipfox/api-workspaces-dto';\nimport type {Workspace} from '#core/entities/workspace.js';\n\nexport function toWorkspaceDto(workspace: Workspace): WorkspaceDto {\n return {\n id: workspace.id,\n name: workspace.name,\n slug: workspace.slug,\n status: workspace.status,\n settings: workspace.settings,\n created_at: workspace.createdAt.toISOString(),\n updated_at: workspace.updatedAt.toISOString(),\n };\n}\n"],"names":["toWorkspaceDto","workspace","id","name","slug","status","settings","created_at","createdAt","toISOString","updated_at","updatedAt"],"mappings":"AAGA,OAAO,SAASA,eAAeC,SAAoB;IACjD,OAAO;QACLC,IAAID,UAAUC,EAAE;QAChBC,MAAMF,UAAUE,IAAI;QACpBC,MAAMH,UAAUG,IAAI;QACpBC,QAAQJ,UAAUI,MAAM;QACxBC,UAAUL,UAAUK,QAAQ;QAC5BC,YAAYN,UAAUO,SAAS,CAACC,WAAW;QAC3CC,YAAYT,UAAUU,SAAS,CAACF,WAAW;IAC7C;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-workspace.d.ts","sourceRoot":"","sources":["../../../src/presentation/e2eRoutes/create-workspace.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"create-workspace.d.ts","sourceRoot":"","sources":["../../../src/presentation/e2eRoutes/create-workspace.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,uBAAuB,iDA4BlC,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { e2eCreateWorkspaceBodySchema, e2eCreateWorkspaceResponseSchema } from '@shipfox/api-workspaces-dto';
|
|
2
|
-
import { defineRoute } from '@shipfox/node-fastify';
|
|
2
|
+
import { ClientError, defineRoute } from '@shipfox/node-fastify';
|
|
3
|
+
import { WorkspaceSlugConflictError } from '#core/errors.js';
|
|
3
4
|
import { createWorkspaceForUser } from '#core/index.js';
|
|
4
5
|
import { toWorkspaceDto } from '#presentation/dto/index.js';
|
|
5
6
|
export const createE2eWorkspaceRoute = defineRoute({
|
|
@@ -12,9 +13,18 @@ export const createE2eWorkspaceRoute = defineRoute({
|
|
|
12
13
|
201: e2eCreateWorkspaceResponseSchema
|
|
13
14
|
}
|
|
14
15
|
},
|
|
16
|
+
errorHandler: (error)=>{
|
|
17
|
+
if (error instanceof WorkspaceSlugConflictError) {
|
|
18
|
+
throw new ClientError('Workspace slug is already taken', 'slug-conflict', {
|
|
19
|
+
status: 409
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
throw error;
|
|
23
|
+
},
|
|
15
24
|
handler: async (request, reply)=>{
|
|
16
25
|
const workspace = await createWorkspaceForUser({
|
|
17
26
|
name: request.body.name,
|
|
27
|
+
slug: request.body.slug ?? `e2e-workspace-${crypto.randomUUID().slice(0, 8)}`,
|
|
18
28
|
userId: request.body.user_id,
|
|
19
29
|
userEmail: request.body.user_email,
|
|
20
30
|
userName: request.body.user_name
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/e2eRoutes/create-workspace.ts"],"sourcesContent":["import {\n e2eCreateWorkspaceBodySchema,\n e2eCreateWorkspaceResponseSchema,\n} from '@shipfox/api-workspaces-dto';\nimport {defineRoute} from '@shipfox/node-fastify';\nimport {createWorkspaceForUser} from '#core/index.js';\nimport {toWorkspaceDto} from '#presentation/dto/index.js';\n\nexport const createE2eWorkspaceRoute = defineRoute({\n method: 'POST',\n path: '/',\n description: 'Create a workspace for an existing user for E2E tests.',\n schema: {\n body: e2eCreateWorkspaceBodySchema,\n response: {\n 201: e2eCreateWorkspaceResponseSchema,\n },\n },\n handler: async (request, reply) => {\n const workspace = await createWorkspaceForUser({\n name: request.body.name,\n userId: request.body.user_id,\n userEmail: request.body.user_email,\n userName: request.body.user_name,\n });\n\n reply.code(201);\n return toWorkspaceDto(workspace);\n },\n});\n"],"names":["e2eCreateWorkspaceBodySchema","e2eCreateWorkspaceResponseSchema","defineRoute","createWorkspaceForUser","toWorkspaceDto","createE2eWorkspaceRoute","method","path","description","schema","body","response","handler","request","reply","workspace","name","userId","user_id","userEmail","user_email","userName","user_name","code"],"mappings":"AAAA,SACEA,4BAA4B,EAC5BC,gCAAgC,QAC3B,8BAA8B;AACrC,SAAQC,WAAW,QAAO,wBAAwB;
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/e2eRoutes/create-workspace.ts"],"sourcesContent":["import {\n e2eCreateWorkspaceBodySchema,\n e2eCreateWorkspaceResponseSchema,\n} from '@shipfox/api-workspaces-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {WorkspaceSlugConflictError} from '#core/errors.js';\nimport {createWorkspaceForUser} from '#core/index.js';\nimport {toWorkspaceDto} from '#presentation/dto/index.js';\n\nexport const createE2eWorkspaceRoute = defineRoute({\n method: 'POST',\n path: '/',\n description: 'Create a workspace for an existing user for E2E tests.',\n schema: {\n body: e2eCreateWorkspaceBodySchema,\n response: {\n 201: e2eCreateWorkspaceResponseSchema,\n },\n },\n errorHandler: (error) => {\n if (error instanceof WorkspaceSlugConflictError) {\n throw new ClientError('Workspace slug is already taken', 'slug-conflict', {status: 409});\n }\n throw error;\n },\n handler: async (request, reply) => {\n const workspace = await createWorkspaceForUser({\n name: request.body.name,\n slug: request.body.slug ?? `e2e-workspace-${crypto.randomUUID().slice(0, 8)}`,\n userId: request.body.user_id,\n userEmail: request.body.user_email,\n userName: request.body.user_name,\n });\n\n reply.code(201);\n return toWorkspaceDto(workspace);\n },\n});\n"],"names":["e2eCreateWorkspaceBodySchema","e2eCreateWorkspaceResponseSchema","ClientError","defineRoute","WorkspaceSlugConflictError","createWorkspaceForUser","toWorkspaceDto","createE2eWorkspaceRoute","method","path","description","schema","body","response","errorHandler","error","status","handler","request","reply","workspace","name","slug","crypto","randomUUID","slice","userId","user_id","userEmail","user_email","userName","user_name","code"],"mappings":"AAAA,SACEA,4BAA4B,EAC5BC,gCAAgC,QAC3B,8BAA8B;AACrC,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,0BAA0B,QAAO,kBAAkB;AAC3D,SAAQC,sBAAsB,QAAO,iBAAiB;AACtD,SAAQC,cAAc,QAAO,6BAA6B;AAE1D,OAAO,MAAMC,0BAA0BJ,YAAY;IACjDK,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,QAAQ;QACNC,MAAMZ;QACNa,UAAU;YACR,KAAKZ;QACP;IACF;IACAa,cAAc,CAACC;QACb,IAAIA,iBAAiBX,4BAA4B;YAC/C,MAAM,IAAIF,YAAY,mCAAmC,iBAAiB;gBAACc,QAAQ;YAAG;QACxF;QACA,MAAMD;IACR;IACAE,SAAS,OAAOC,SAASC;QACvB,MAAMC,YAAY,MAAMf,uBAAuB;YAC7CgB,MAAMH,QAAQN,IAAI,CAACS,IAAI;YACvBC,MAAMJ,QAAQN,IAAI,CAACU,IAAI,IAAI,CAAC,cAAc,EAAEC,OAAOC,UAAU,GAAGC,KAAK,CAAC,GAAG,IAAI;YAC7EC,QAAQR,QAAQN,IAAI,CAACe,OAAO;YAC5BC,WAAWV,QAAQN,IAAI,CAACiB,UAAU;YAClCC,UAAUZ,QAAQN,IAAI,CAACmB,SAAS;QAClC;QAEAZ,MAAMa,IAAI,CAAC;QACX,OAAO1B,eAAec;IACxB;AACF,GAAG"}
|
|
@@ -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,
|
|
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
|
|
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":"AAAA,SAAQA,6BAA6B,QAAO,2CAA2C;AACvF,SACEC,2BAA2B,EAC3BC,6BAA6B,QAExB,wBAAwB;AAC/B,SACEC,4BAA4B,EAC5BC,uBAAuB,EACvBC,qBAAqB,EACrBC,iBAAiB,EACjBC,iBAAiB,EACjBC,sBAAsB,EACtBC,sBAAsB,QACjB,kBAAkB;AACzB,SAAQC,yBAAyB,EAAEC,wBAAwB,QAAO,uBAAuB;AACzF,SACEC,mBAAmB,EACnBC,0BAA0B,EAC1BC,0BAA0B,QACrB,sBAAsB;AAC7B,SAAQC,qBAAqB,QAAO,qBAAqB;AAEzD,OAAO,SAASC;IAGd,OAAOd,8BAA8BF,+BAA+B;QAClEiB,+BAA+B,OAAO,EAACC,MAAM,EAAC,GAAM,CAAA;gBAClDC,aAAa,AAAC,CAAA,MAAMJ,sBAAsB;oBAACG;gBAAM,EAAC,EAAGE,GAAG,CACtD,CAAC,EAACC,WAAW,EAAEC,eAAe,EAAC,GAAM,CAAA;wBACnCD;wBACAE,MAAM;wBACND;oBACF,CAAA;YAEJ,CAAA;QACAV,qBAAqB,OAAOY;YAC1B,IAAI;gBACF,OAAO;oBAACC,eAAe,MAAMb,oBAAoBY;gBAAM;YACzD,EAAE,OAAOE,OAAO;gBACd,IAAIA,iBAAiBjB,wBAAwB;oBAC3C,MAAMR,4BACJD,8BAA8B2B,OAAO,CAACf,mBAAmB,EACzD,uBACA;wBAACS,aAAaG,MAAMH,WAAW;oBAAA;gBAEnC;gBACA,MAAMK;YACR;QACF;QACAb,4BAA4B,OAAOW;YACjC,IAAI;gBACF,OAAO;oBAACI,QAAQ,MAAMf,2BAA2BW;gBAAM;YACzD,EAAE,OAAOE,OAAO;gBACd,IAAIA,iBAAiBjB,wBAAwB;oBAC3C,MAAMR,4BACJD,8BAA8B2B,OAAO,CAACd,0BAA0B,EAChE,uBACA;wBAACQ,aAAaG,MAAMH,WAAW;oBAAA;gBAEnC;gBACA,MAAMK;YACR;QACF;QACAG,+BAA+B,OAAOL;YACpC,IAAI;gBACF,MAAMM,aAAa,MAAMnB,yBAAyB;oBAACoB,OAAOP,MAAMO,KAAK;gBAAA;gBACrE,IAAI,CAACD,YAAY,MAAM,IAAIvB,kBAAkB;gBAC7C,IAAIuB,WAAWE,UAAU,KAAK,MAAM,MAAM,IAAI3B;gBAC9C,IAAIyB,WAAWG,SAAS,CAACC,OAAO,MAAMC,KAAKC,GAAG,IAAI,MAAM,IAAI9B;gBAC5D,IAAIwB,WAAWO,KAAK,KAAKb,MAAMa,KAAK,EAAE,MAAM,IAAIlC;gBAChD,OAAO,CAAC;YACV,EAAE,OAAOuB,OAAO;gBACd,MAAMY,uBAAuB,iCAAiCZ;YAChE;QACF;QACAa,kBAAkB,OAAOf;YACvB,IAAI;gBACF,MAAMgB,SAAS,MAAM9B,0BAA0Bc;gBAC/C,OAAO;oBACLiB,YAAY;wBACVC,IAAIF,OAAOC,UAAU,CAACC,EAAE;wBACxBxB,QAAQsB,OAAOC,UAAU,CAACvB,MAAM;wBAChCG,aAAamB,OAAOC,UAAU,CAACpB,WAAW;oBAC5C;gBACF;YACF,EAAE,OAAOK,OAAO;gBACd,MAAMY,uBAAuB,oBAAoBZ;YACnD;QACF;QACAiB,yBAAyB,OAAOnB;YAC9B,IAAI;gBACF,MAAMV,2BAA2BU;gBACjC,OAAO,CAAC;YACV,EAAE,OAAOE,OAAO;gBACd,MAAMkB,SAAS5C,8BAA8B2B,OAAO,CAACgB,uBAAuB;gBAC5E,IAAIjB,iBAAiBtB,yBACnB,MAAMH,4BAA4B2C,QAAQ,uBAAuB;oBAC/DvB,aAAaG,MAAMH,WAAW;gBAChC;gBACF,IAAIK,iBAAiBjB,wBACnB,MAAMR,4BAA4B2C,QAAQ,uBAAuB;oBAC/DvB,aAAaG,MAAMH,WAAW;gBAChC;gBACF,IAAIK,iBAAiBlB,wBACnB,MAAMP,4BAA4B2C,QAAQ,sBAAsB;oBAC9DvB,aAAaG,MAAMH,WAAW;gBAChC;gBACF,MAAMK;YACR;QACF;IACF;AACF;AAEA,SAASY,uBACPO,UAAgE,EAChEnB,KAAc;IAEd,MAAMkB,SAAS5C,8BAA8B2B,OAAO,CAACkB,WAAW;IAChE,IAAInB,iBAAiBnB,mBACnB,OAAON,4BAA4B2C,QAAQ,4BAA4B,CAAC;IAC1E,IAAIlB,iBAAiBrB,uBACnB,OAAOJ,4BAA4B2C,QAAQ,yBAAyB,CAAC;IACvE,IAAIlB,iBAAiBpB,mBACnB,OAAOL,4BAA4B2C,QAAQ,4BAA4B,CAAC;IAC1E,IAAIlB,iBAAiBvB,8BACnB,OAAOF,4BAA4B2C,QAAQ,6BAA6B,CAAC;IAC3E,OAAOlB;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wCAAwC,CAAC;AACjF,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,uCAAuC,CAAC;AACpF,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wCAAwC,CAAC;AACjF,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,uCAAuC,CAAC;AACpF,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,uBAAuB,CAAC;AAWtD,eAAO,MAAM,gBAAgB,EAAE,UAAU,EAmBxC,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,IAAI,EAAE,qBAAqB,CAAC;IAC5B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,EAAE,wBAAwB,CAAC;CACnC,GAAG,UAAU,EAAE,CAEf"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createAdminWorkspacesRoutes } from './admin-workspaces.js';
|
|
2
2
|
import { invitationsAcceptGroup, invitationsWorkspaceScopedRoutes } from './invitations/index.js';
|
|
3
3
|
import { memberRoutes } from './members/index.js';
|
|
4
|
-
import { createWorkspaceRoute, listUserWorkspacesRoute } from './workspaces/index.js';
|
|
4
|
+
import { createWorkspaceRoute, listUserWorkspacesRoute, updateWorkspaceRoute, workspaceSlugAvailabilityRoute } from './workspaces/index.js';
|
|
5
5
|
export const workspacesRoutes = [
|
|
6
6
|
invitationsAcceptGroup,
|
|
7
7
|
{
|
|
@@ -9,6 +9,8 @@ export const workspacesRoutes = [
|
|
|
9
9
|
routes: [
|
|
10
10
|
listUserWorkspacesRoute,
|
|
11
11
|
createWorkspaceRoute,
|
|
12
|
+
workspaceSlugAvailabilityRoute,
|
|
13
|
+
updateWorkspaceRoute,
|
|
12
14
|
{
|
|
13
15
|
prefix: '/:workspaceId/members',
|
|
14
16
|
routes: memberRoutes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/index.ts"],"sourcesContent":["import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';\nimport type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';\nimport type {RunnersInterModuleClient} from '@shipfox/api-runners-dto/inter-module';\nimport type {RouteGroup} from '@shipfox/node-fastify';\nimport {createAdminWorkspacesRoutes} from './admin-workspaces.js';\nimport {invitationsAcceptGroup, invitationsWorkspaceScopedRoutes} from './invitations/index.js';\nimport {memberRoutes} from './members/index.js';\nimport {createWorkspaceRoute
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/index.ts"],"sourcesContent":["import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';\nimport type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';\nimport type {RunnersInterModuleClient} from '@shipfox/api-runners-dto/inter-module';\nimport type {RouteGroup} from '@shipfox/node-fastify';\nimport {createAdminWorkspacesRoutes} from './admin-workspaces.js';\nimport {invitationsAcceptGroup, invitationsWorkspaceScopedRoutes} from './invitations/index.js';\nimport {memberRoutes} from './members/index.js';\nimport {\n createWorkspaceRoute,\n listUserWorkspacesRoute,\n updateWorkspaceRoute,\n workspaceSlugAvailabilityRoute,\n} from './workspaces/index.js';\n\nexport const workspacesRoutes: RouteGroup[] = [\n invitationsAcceptGroup,\n {\n prefix: '/workspaces',\n routes: [\n listUserWorkspacesRoute,\n createWorkspaceRoute,\n workspaceSlugAvailabilityRoute,\n updateWorkspaceRoute,\n {\n prefix: '/:workspaceId/members',\n routes: memberRoutes,\n },\n {\n prefix: '/:workspaceId/invitations',\n routes: invitationsWorkspaceScopedRoutes,\n },\n ],\n },\n];\n\nexport function createWorkspacesRoutes(params: {\n auth: AuthInterModuleClient;\n projects: ProjectsModuleClient;\n runners: RunnersInterModuleClient;\n}): RouteGroup[] {\n return [...workspacesRoutes, createAdminWorkspacesRoutes(params)];\n}\n"],"names":["createAdminWorkspacesRoutes","invitationsAcceptGroup","invitationsWorkspaceScopedRoutes","memberRoutes","createWorkspaceRoute","listUserWorkspacesRoute","updateWorkspaceRoute","workspaceSlugAvailabilityRoute","workspacesRoutes","prefix","routes","createWorkspacesRoutes","params"],"mappings":"AAIA,SAAQA,2BAA2B,QAAO,wBAAwB;AAClE,SAAQC,sBAAsB,EAAEC,gCAAgC,QAAO,yBAAyB;AAChG,SAAQC,YAAY,QAAO,qBAAqB;AAChD,SACEC,oBAAoB,EACpBC,uBAAuB,EACvBC,oBAAoB,EACpBC,8BAA8B,QACzB,wBAAwB;AAE/B,OAAO,MAAMC,mBAAiC;IAC5CP;IACA;QACEQ,QAAQ;QACRC,QAAQ;YACNL;YACAD;YACAG;YACAD;YACA;gBACEG,QAAQ;gBACRC,QAAQP;YACV;YACA;gBACEM,QAAQ;gBACRC,QAAQR;YACV;SACD;IACH;CACD,CAAC;AAEF,OAAO,SAASS,uBAAuBC,MAItC;IACC,OAAO;WAAIJ;QAAkBR,4BAA4BY;KAAQ;AACnE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../../src/presentation/routes/invitations/create.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,qBAAqB,
|
|
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;
|
|
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,
|
|
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;
|
|
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,
|
|
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;
|
|
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,
|
|
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;
|
|
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,
|
|
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;
|
|
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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,YAAY,EAAE,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAW1F,wBAAgB,kDAAkD,KAClD,SAAS,cAAc,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAgC3E"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ClientError } from '@shipfox/node-fastify';
|
|
2
|
+
import { enforceRateLimit as enforceSharedRateLimit } from '@shipfox/node-rate-limit';
|
|
3
|
+
import { checkWorkspacesRateLimit, WORKSPACE_SLUG_AVAILABILITY_RATE_LIMIT } from '#core/rate-limit.js';
|
|
4
|
+
function routeName(request) {
|
|
5
|
+
return request.routeOptions.url ?? request.url.split('?')[0] ?? 'unknown';
|
|
6
|
+
}
|
|
7
|
+
export function createWorkspaceSlugAvailabilityRateLimitPreHandler() {
|
|
8
|
+
return async (request, reply)=>{
|
|
9
|
+
await enforceSharedRateLimit({
|
|
10
|
+
request,
|
|
11
|
+
reply,
|
|
12
|
+
check: ()=>checkWorkspacesRateLimit({
|
|
13
|
+
action: 'slug-availability',
|
|
14
|
+
scope: 'ip',
|
|
15
|
+
identifier: request.ip,
|
|
16
|
+
...WORKSPACE_SLUG_AVAILABILITY_RATE_LIMIT
|
|
17
|
+
}),
|
|
18
|
+
route: routeName(request),
|
|
19
|
+
unavailableCode: 'workspace-rate-limit-unavailable',
|
|
20
|
+
unavailableMessage: 'Workspace rate limiter unavailable',
|
|
21
|
+
setRetryAfter: (reply, retryAfterSeconds)=>{
|
|
22
|
+
reply.header('Retry-After', String(retryAfterSeconds));
|
|
23
|
+
},
|
|
24
|
+
logWarn: (request, context)=>{
|
|
25
|
+
request.log.warn(context, 'Workspace rate limit blocked request');
|
|
26
|
+
},
|
|
27
|
+
logError: (request, context)=>{
|
|
28
|
+
request.log.error(context, 'Workspace rate limiter unavailable');
|
|
29
|
+
},
|
|
30
|
+
createClientError: (presentation, cause)=>new ClientError(presentation.message, presentation.code, {
|
|
31
|
+
status: presentation.status,
|
|
32
|
+
...presentation.details ? {
|
|
33
|
+
details: presentation.details
|
|
34
|
+
} : {},
|
|
35
|
+
data: presentation.data,
|
|
36
|
+
cause
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/rate-limit.ts"],"sourcesContent":["import {ClientError, type FastifyReply, type FastifyRequest} from '@shipfox/node-fastify';\nimport {enforceRateLimit as enforceSharedRateLimit} from '@shipfox/node-rate-limit';\nimport {\n checkWorkspacesRateLimit,\n WORKSPACE_SLUG_AVAILABILITY_RATE_LIMIT,\n} from '#core/rate-limit.js';\n\nfunction routeName(request: FastifyRequest): string {\n return request.routeOptions.url ?? request.url.split('?')[0] ?? 'unknown';\n}\n\nexport function createWorkspaceSlugAvailabilityRateLimitPreHandler() {\n return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n await enforceSharedRateLimit({\n request,\n reply,\n check: () =>\n checkWorkspacesRateLimit({\n action: 'slug-availability',\n scope: 'ip',\n identifier: request.ip,\n ...WORKSPACE_SLUG_AVAILABILITY_RATE_LIMIT,\n }),\n route: routeName(request),\n unavailableCode: 'workspace-rate-limit-unavailable',\n unavailableMessage: 'Workspace rate limiter unavailable',\n setRetryAfter: (reply, retryAfterSeconds) => {\n reply.header('Retry-After', String(retryAfterSeconds));\n },\n logWarn: (request, context) => {\n request.log.warn(context, 'Workspace rate limit blocked request');\n },\n logError: (request, context) => {\n request.log.error(context, 'Workspace rate limiter unavailable');\n },\n createClientError: (presentation, cause) =>\n new ClientError(presentation.message, presentation.code, {\n status: presentation.status,\n ...(presentation.details ? {details: presentation.details} : {}),\n data: presentation.data,\n cause,\n }),\n });\n };\n}\n"],"names":["ClientError","enforceRateLimit","enforceSharedRateLimit","checkWorkspacesRateLimit","WORKSPACE_SLUG_AVAILABILITY_RATE_LIMIT","routeName","request","routeOptions","url","split","createWorkspaceSlugAvailabilityRateLimitPreHandler","reply","check","action","scope","identifier","ip","route","unavailableCode","unavailableMessage","setRetryAfter","retryAfterSeconds","header","String","logWarn","context","log","warn","logError","error","createClientError","presentation","cause","message","code","status","details","data"],"mappings":"AAAA,SAAQA,WAAW,QAA+C,wBAAwB;AAC1F,SAAQC,oBAAoBC,sBAAsB,QAAO,2BAA2B;AACpF,SACEC,wBAAwB,EACxBC,sCAAsC,QACjC,sBAAsB;AAE7B,SAASC,UAAUC,OAAuB;IACxC,OAAOA,QAAQC,YAAY,CAACC,GAAG,IAAIF,QAAQE,GAAG,CAACC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI;AAClE;AAEA,OAAO,SAASC;IACd,OAAO,OAAOJ,SAAyBK;QACrC,MAAMT,uBAAuB;YAC3BI;YACAK;YACAC,OAAO,IACLT,yBAAyB;oBACvBU,QAAQ;oBACRC,OAAO;oBACPC,YAAYT,QAAQU,EAAE;oBACtB,GAAGZ,sCAAsC;gBAC3C;YACFa,OAAOZ,UAAUC;YACjBY,iBAAiB;YACjBC,oBAAoB;YACpBC,eAAe,CAACT,OAAOU;gBACrBV,MAAMW,MAAM,CAAC,eAAeC,OAAOF;YACrC;YACAG,SAAS,CAAClB,SAASmB;gBACjBnB,QAAQoB,GAAG,CAACC,IAAI,CAACF,SAAS;YAC5B;YACAG,UAAU,CAACtB,SAASmB;gBAClBnB,QAAQoB,GAAG,CAACG,KAAK,CAACJ,SAAS;YAC7B;YACAK,mBAAmB,CAACC,cAAcC,QAChC,IAAIhC,YAAY+B,aAAaE,OAAO,EAAEF,aAAaG,IAAI,EAAE;oBACvDC,QAAQJ,aAAaI,MAAM;oBAC3B,GAAIJ,aAAaK,OAAO,GAAG;wBAACA,SAASL,aAAaK,OAAO;oBAAA,IAAI,CAAC,CAAC;oBAC/DC,MAAMN,aAAaM,IAAI;oBACvBL;gBACF;QACJ;IACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../../src/presentation/routes/workspaces/create.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../../src/presentation/routes/workspaces/create.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,oBAAoB,iDAoC/B,CAAC"}
|