@shipfox/api-workspaces 10.1.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +33 -0
- package/README.md +7 -1
- package/dist/core/admin-workspaces.d.ts +16 -0
- package/dist/core/admin-workspaces.d.ts.map +1 -1
- package/dist/core/admin-workspaces.js +52 -0
- package/dist/core/admin-workspaces.js.map +1 -1
- package/dist/core/errors.d.ts +12 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +24 -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 +6 -3
- package/dist/core/invitations.js.map +1 -1
- package/dist/core/workspaces.d.ts +1 -0
- package/dist/core/workspaces.d.ts.map +1 -1
- package/dist/core/workspaces.js +5 -3
- package/dist/core/workspaces.js.map +1 -1
- package/dist/db/admin-workspace-commands.d.ts +17 -0
- package/dist/db/admin-workspace-commands.d.ts.map +1 -0
- package/dist/db/admin-workspace-commands.js +86 -0
- package/dist/db/admin-workspace-commands.js.map +1 -0
- package/dist/db/db.d.ts +256 -0
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +2 -0
- package/dist/db/db.js.map +1 -1
- package/dist/db/index.d.ts +1 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/schema/admin-command-results.d.ts +135 -0
- package/dist/db/schema/admin-command-results.d.ts.map +1 -0
- package/dist/db/schema/admin-command-results.js +18 -0
- package/dist/db/schema/admin-command-results.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.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/admin-workspaces.d.ts.map +1 -1
- package/dist/presentation/routes/admin-workspaces.js +119 -14
- package/dist/presentation/routes/admin-workspaces.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/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0002_wide_enchantress.sql +11 -0
- package/drizzle/meta/0002_snapshot.json +555 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +6 -6
- package/src/core/admin-workspaces.ts +102 -0
- package/src/core/errors.ts +28 -0
- package/src/core/index.ts +8 -0
- package/src/core/invitations.test.ts +8 -8
- package/src/core/invitations.ts +3 -0
- package/src/core/workspaces.test.ts +22 -3
- package/src/core/workspaces.ts +4 -1
- package/src/db/admin-workspace-commands.ts +169 -0
- package/src/db/db.ts +2 -0
- package/src/db/index.ts +1 -0
- package/src/db/schema/admin-command-results.ts +30 -0
- package/src/index.test.ts +3 -1
- package/src/index.ts +17 -2
- package/src/presentation/inter-module.test.ts +51 -0
- package/src/presentation/inter-module.ts +7 -4
- package/src/presentation/routes/admin-workspaces.test.ts +273 -3
- package/src/presentation/routes/admin-workspaces.ts +143 -13
- 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/test/globalSetup.ts +3 -1
- package/test/routes.ts +8 -3
- package/tsconfig.build.tsbuildinfo +1 -1
- package/vitest.config.ts +3 -0
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { administrationActionEventSchemas } from '@shipfox/api-common-dto';
|
|
1
2
|
import { WORKSPACES_INVITATION_SEND_REQUESTED, workspacesEventSchemas } from '@shipfox/api-workspaces-dto';
|
|
2
3
|
import { subscriberFactory } from '@shipfox/node-module';
|
|
3
4
|
import { db, migrationsPath, workspacesOutbox } from '#db/index.js';
|
|
@@ -14,6 +15,10 @@ export { db, migrationsPath } from '#db/index.js';
|
|
|
14
15
|
export { listMembershipsByUser } from '#db/memberships.js';
|
|
15
16
|
export { createWorkspacesRoutes, workspacesRoutes as routes } from '#presentation/routes/index.js';
|
|
16
17
|
const subscriber = subscriberFactory();
|
|
18
|
+
const workspacesPublisherEventSchemas = {
|
|
19
|
+
...workspacesEventSchemas,
|
|
20
|
+
...administrationActionEventSchemas
|
|
21
|
+
};
|
|
17
22
|
export function createWorkspacesModule(options) {
|
|
18
23
|
return {
|
|
19
24
|
name: 'workspaces',
|
|
@@ -31,7 +36,7 @@ export function createWorkspacesModule(options) {
|
|
|
31
36
|
name: 'workspaces',
|
|
32
37
|
table: workspacesOutbox,
|
|
33
38
|
db,
|
|
34
|
-
eventSchemas:
|
|
39
|
+
eventSchemas: workspacesPublisherEventSchemas
|
|
35
40
|
}
|
|
36
41
|
],
|
|
37
42
|
subscribers: [
|
|
@@ -59,7 +64,7 @@ export function createWorkspacesModule(options) {
|
|
|
59
64
|
name: 'workspaces',
|
|
60
65
|
table: workspacesOutbox,
|
|
61
66
|
db,
|
|
62
|
-
eventSchemas:
|
|
67
|
+
eventSchemas: workspacesPublisherEventSchemas
|
|
63
68
|
}
|
|
64
69
|
],
|
|
65
70
|
subscribers: [
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/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 {\n WORKSPACES_INVITATION_SEND_REQUESTED,\n type WorkspacesEventMap,\n workspacesEventSchemas,\n} from '@shipfox/api-workspaces-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {db, migrationsPath, workspacesOutbox} from '#db/index.js';\nimport {registerWorkspacesServiceMetrics} from '#metrics/index.js';\nimport {workspacesE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {createWorkspacesInterModulePresentation} from '#presentation/inter-module.js';\nimport {createWorkspacesRoutes, workspacesRoutes} from '#presentation/routes/index.js';\nimport {onInvitationSendRequested} from '#presentation/subscribers/index.js';\n\nexport type {Invitation} from '#core/entities/invitation.js';\nexport type {Membership} from '#core/entities/membership.js';\nexport type {Workspace, WorkspaceStatus} from '#core/entities/workspace.js';\nexport {\n InvitationEmailMismatchError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nexport {\n acceptWorkspaceInvitation,\n peekInvitationByRawToken,\n reconcileWorkspaceInvitationAcceptance,\n type WorkspaceInvitationReconciliation,\n} from '#core/invitations.js';\nexport {type EnsureMembershipParams, ensureMembership} from '#core/memberships.js';\nexport {getWorkspace, requireWorkspaceMembership} from '#core/workspaces.js';\nexport {db, migrationsPath} from '#db/index.js';\nexport {listMembershipsByUser} from '#db/memberships.js';\nexport {createWorkspacesRoutes, workspacesRoutes as routes} from '#presentation/routes/index.js';\n\nconst subscriber = subscriberFactory<WorkspacesEventMap>();\n\nexport interface WorkspacesModuleOptions {\n auth: AuthInterModuleClient;\n projects: ProjectsModuleClient;\n runners: RunnersInterModuleClient;\n}\n\nexport function createWorkspacesModule(options: WorkspacesModuleOptions): ShipfoxModule {\n return {\n name: 'workspaces',\n database: {db, migrationsPath, databaseNamespace: 'workspaces'},\n routes: createWorkspacesRoutes(options),\n e2eRoutes: [workspacesE2eRoutes],\n publishers: [\n {name: 'workspaces'
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';\nimport {administrationActionEventSchemas} from '@shipfox/api-common-dto';\nimport type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';\nimport type {RunnersInterModuleClient} from '@shipfox/api-runners-dto/inter-module';\nimport {\n WORKSPACES_INVITATION_SEND_REQUESTED,\n type WorkspacesEventMap,\n workspacesEventSchemas,\n} from '@shipfox/api-workspaces-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {db, migrationsPath, workspacesOutbox} from '#db/index.js';\nimport {registerWorkspacesServiceMetrics} from '#metrics/index.js';\nimport {workspacesE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {createWorkspacesInterModulePresentation} from '#presentation/inter-module.js';\nimport {createWorkspacesRoutes, workspacesRoutes} from '#presentation/routes/index.js';\nimport {onInvitationSendRequested} from '#presentation/subscribers/index.js';\n\nexport type {Invitation} from '#core/entities/invitation.js';\nexport type {Membership} from '#core/entities/membership.js';\nexport type {Workspace, WorkspaceStatus} from '#core/entities/workspace.js';\nexport {\n InvitationEmailMismatchError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n WorkspaceNotFoundError,\n} from '#core/errors.js';\nexport {\n acceptWorkspaceInvitation,\n peekInvitationByRawToken,\n reconcileWorkspaceInvitationAcceptance,\n type WorkspaceInvitationReconciliation,\n} from '#core/invitations.js';\nexport {type EnsureMembershipParams, ensureMembership} from '#core/memberships.js';\nexport {getWorkspace, requireWorkspaceMembership} from '#core/workspaces.js';\nexport {db, migrationsPath} from '#db/index.js';\nexport {listMembershipsByUser} from '#db/memberships.js';\nexport {createWorkspacesRoutes, workspacesRoutes as routes} from '#presentation/routes/index.js';\n\nconst subscriber = subscriberFactory<WorkspacesEventMap>();\nconst workspacesPublisherEventSchemas = {\n ...workspacesEventSchemas,\n ...administrationActionEventSchemas,\n};\n\nexport interface WorkspacesModuleOptions {\n auth: AuthInterModuleClient;\n projects: ProjectsModuleClient;\n runners: RunnersInterModuleClient;\n}\n\nexport function createWorkspacesModule(options: WorkspacesModuleOptions): ShipfoxModule {\n return {\n name: 'workspaces',\n database: {db, migrationsPath, databaseNamespace: 'workspaces'},\n routes: createWorkspacesRoutes(options),\n e2eRoutes: [workspacesE2eRoutes],\n publishers: [\n {\n name: 'workspaces',\n table: workspacesOutbox,\n db,\n eventSchemas: workspacesPublisherEventSchemas,\n },\n ],\n subscribers: [subscriber(WORKSPACES_INVITATION_SEND_REQUESTED, onInvitationSendRequested)],\n metrics: registerWorkspacesServiceMetrics,\n interModulePresentations: [createWorkspacesInterModulePresentation()],\n };\n}\n\n/** @deprecated Use createWorkspacesModule from the application composition root. */\nexport const workspacesModule: ShipfoxModule = {\n name: 'workspaces',\n database: {db, migrationsPath, databaseNamespace: 'workspaces'},\n routes: workspacesRoutes,\n e2eRoutes: [workspacesE2eRoutes],\n publishers: [\n {\n name: 'workspaces',\n table: workspacesOutbox,\n db,\n eventSchemas: workspacesPublisherEventSchemas,\n },\n ],\n subscribers: [subscriber(WORKSPACES_INVITATION_SEND_REQUESTED, onInvitationSendRequested)],\n metrics: registerWorkspacesServiceMetrics,\n interModulePresentations: [createWorkspacesInterModulePresentation()],\n};\n"],"names":["administrationActionEventSchemas","WORKSPACES_INVITATION_SEND_REQUESTED","workspacesEventSchemas","subscriberFactory","db","migrationsPath","workspacesOutbox","registerWorkspacesServiceMetrics","workspacesE2eRoutes","createWorkspacesInterModulePresentation","createWorkspacesRoutes","workspacesRoutes","onInvitationSendRequested","InvitationEmailMismatchError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","WorkspaceNotFoundError","acceptWorkspaceInvitation","peekInvitationByRawToken","reconcileWorkspaceInvitationAcceptance","ensureMembership","getWorkspace","requireWorkspaceMembership","listMembershipsByUser","routes","subscriber","workspacesPublisherEventSchemas","createWorkspacesModule","options","name","database","databaseNamespace","e2eRoutes","publishers","table","eventSchemas","subscribers","metrics","interModulePresentations","workspacesModule"],"mappings":"AACA,SAAQA,gCAAgC,QAAO,0BAA0B;AAGzE,SACEC,oCAAoC,EAEpCC,sBAAsB,QACjB,8BAA8B;AAErC,SAAQC,iBAAiB,QAAO,uBAAuB;AACvD,SAAQC,EAAE,EAAEC,cAAc,EAAEC,gBAAgB,QAAO,eAAe;AAClE,SAAQC,gCAAgC,QAAO,oBAAoB;AACnE,SAAQC,mBAAmB,QAAO,mCAAmC;AACrE,SAAQC,uCAAuC,QAAO,gCAAgC;AACtF,SAAQC,sBAAsB,EAAEC,gBAAgB,QAAO,gCAAgC;AACvF,SAAQC,yBAAyB,QAAO,qCAAqC;AAK7E,SACEC,4BAA4B,EAC5BC,qBAAqB,EACrBC,iBAAiB,EACjBC,iBAAiB,EACjBC,sBAAsB,QACjB,kBAAkB;AACzB,SACEC,yBAAyB,EACzBC,wBAAwB,EACxBC,sCAAsC,QAEjC,uBAAuB;AAC9B,SAAqCC,gBAAgB,QAAO,uBAAuB;AACnF,SAAQC,YAAY,EAAEC,0BAA0B,QAAO,sBAAsB;AAC7E,SAAQnB,EAAE,EAAEC,cAAc,QAAO,eAAe;AAChD,SAAQmB,qBAAqB,QAAO,qBAAqB;AACzD,SAAQd,sBAAsB,EAAEC,oBAAoBc,MAAM,QAAO,gCAAgC;AAEjG,MAAMC,aAAavB;AACnB,MAAMwB,kCAAkC;IACtC,GAAGzB,sBAAsB;IACzB,GAAGF,gCAAgC;AACrC;AAQA,OAAO,SAAS4B,uBAAuBC,OAAgC;IACrE,OAAO;QACLC,MAAM;QACNC,UAAU;YAAC3B;YAAIC;YAAgB2B,mBAAmB;QAAY;QAC9DP,QAAQf,uBAAuBmB;QAC/BI,WAAW;YAACzB;SAAoB;QAChC0B,YAAY;YACV;gBACEJ,MAAM;gBACNK,OAAO7B;gBACPF;gBACAgC,cAAcT;YAChB;SACD;QACDU,aAAa;YAACX,WAAWzB,sCAAsCW;SAA2B;QAC1F0B,SAAS/B;QACTgC,0BAA0B;YAAC9B;SAA0C;IACvE;AACF;AAEA,kFAAkF,GAClF,OAAO,MAAM+B,mBAAkC;IAC7CV,MAAM;IACNC,UAAU;QAAC3B;QAAIC;QAAgB2B,mBAAmB;IAAY;IAC9DP,QAAQd;IACRsB,WAAW;QAACzB;KAAoB;IAChC0B,YAAY;QACV;YACEJ,MAAM;YACNK,OAAO7B;YACPF;YACAgC,cAAcT;QAChB;KACD;IACDU,aAAa;QAACX,WAAWzB,sCAAsCW;KAA2B;IAC1F0B,SAAS/B;IACTgC,0BAA0B;QAAC9B;KAA0C;AACvE,EAAE"}
|
|
@@ -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":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-workspaces.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/admin-workspaces.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,qBAAqB,EAE3B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wCAAwC,CAAC;AACjF,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"admin-workspaces.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/admin-workspaces.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,qBAAqB,EAE3B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wCAAwC,CAAC;AACjF,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,uCAAuC,CAAC;AASpF,OAAO,EAA2B,KAAK,UAAU,EAAC,MAAM,uBAAuB,CAAC;AA6GhF,wBAAgB,2BAA2B,CAAC,MAAM,EAAE;IAClD,IAAI,EAAE,qBAAqB,CAAC;IAC5B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,EAAE,wBAAwB,CAAC;CACnC,GAAG,UAAU,CA8Fb"}
|
|
@@ -1,10 +1,82 @@
|
|
|
1
1
|
import { AUTH_USER, getUserContext } from '@shipfox/api-auth-context';
|
|
2
2
|
import { authInterModuleContract } from '@shipfox/api-auth-dto/inter-module';
|
|
3
|
-
import { listWorkspaceAdminSummariesResponseSchema, workspaceAdminLookupQuerySchema } from '@shipfox/api-workspaces-dto';
|
|
3
|
+
import { listWorkspaceAdminSummariesResponseSchema, workspaceAdministrationMutationBodySchema, workspaceAdministrationMutationResponseSchema, workspaceAdminLookupQuerySchema } from '@shipfox/api-workspaces-dto';
|
|
4
4
|
import { isInterModuleKnownError } from '@shipfox/inter-module';
|
|
5
5
|
import { decodeStringIdCursor, encodeStringIdCursor } from '@shipfox/node-drizzle';
|
|
6
6
|
import { ClientError, defineRoute } from '@shipfox/node-fastify';
|
|
7
|
-
import {
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { listWorkspaceAdministratorSummaries, reactivateWorkspace, suspendWorkspace } from '#core/admin-workspaces.js';
|
|
9
|
+
import { WorkspaceAdminIdempotencyKeyReuseError, WorkspaceAlreadySuspendedError, WorkspaceDeletedError, WorkspaceNotFoundError, WorkspaceNotSuspendedError } from '#core/errors.js';
|
|
10
|
+
const idempotencyKeyMaxLength = 256;
|
|
11
|
+
function requireActorId(request) {
|
|
12
|
+
const client = getUserContext(request);
|
|
13
|
+
if (!client) {
|
|
14
|
+
throw new ClientError('Authentication required', 'unauthorized', {
|
|
15
|
+
status: 401
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return client.userId;
|
|
19
|
+
}
|
|
20
|
+
function requireIdempotencyKey(request) {
|
|
21
|
+
const value = request.headers['idempotency-key'];
|
|
22
|
+
const key = Array.isArray(value) ? value[0] : value;
|
|
23
|
+
if (!key || key.trim().length === 0 || key.length > idempotencyKeyMaxLength) {
|
|
24
|
+
throw new ClientError('Idempotency-Key header is required', 'idempotency-key-required', {
|
|
25
|
+
status: 400
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return key;
|
|
29
|
+
}
|
|
30
|
+
function translateAdminRoleError(error, message) {
|
|
31
|
+
if (isInterModuleKnownError(authInterModuleContract.methods.requireAdminRole, error) && error.code === 'admin-role-required') {
|
|
32
|
+
throw new ClientError(message, 'forbidden', {
|
|
33
|
+
status: 403,
|
|
34
|
+
details: {
|
|
35
|
+
required_role: error.details.requiredRole
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function translateAdministrationError(error) {
|
|
41
|
+
translateAdminRoleError(error, 'Administrator operator role required');
|
|
42
|
+
if (error instanceof WorkspaceNotFoundError) {
|
|
43
|
+
throw new ClientError('Workspace not found', 'workspace-not-found', {
|
|
44
|
+
status: 404
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (error instanceof WorkspaceDeletedError) {
|
|
48
|
+
throw new ClientError('Workspace is deleted', 'workspace-deleted', {
|
|
49
|
+
status: 409
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (error instanceof WorkspaceAlreadySuspendedError) {
|
|
53
|
+
throw new ClientError('Workspace is already suspended', 'workspace-already-suspended', {
|
|
54
|
+
status: 409
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (error instanceof WorkspaceNotSuspendedError) {
|
|
58
|
+
throw new ClientError('Workspace is not suspended', 'workspace-not-suspended', {
|
|
59
|
+
status: 409
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (error instanceof WorkspaceAdminIdempotencyKeyReuseError) {
|
|
63
|
+
throw new ClientError('Idempotency-Key was already used for a different workspace command', 'idempotency-key-reused', {
|
|
64
|
+
status: 409
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
function translateObserverAdministrationError(error) {
|
|
70
|
+
translateAdminRoleError(error, 'Administrator observer role required');
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
function toWorkspaceAdministrationMutationDto(result) {
|
|
74
|
+
return {
|
|
75
|
+
workspace_id: result.workspaceId,
|
|
76
|
+
status: result.status,
|
|
77
|
+
correlation_id: result.correlationId
|
|
78
|
+
};
|
|
79
|
+
}
|
|
8
80
|
function toWorkspaceAdministratorSummaryDto(summary) {
|
|
9
81
|
return {
|
|
10
82
|
id: summary.id,
|
|
@@ -29,17 +101,7 @@ export function createAdminWorkspacesRoutes(params) {
|
|
|
29
101
|
200: listWorkspaceAdminSummariesResponseSchema
|
|
30
102
|
}
|
|
31
103
|
},
|
|
32
|
-
errorHandler:
|
|
33
|
-
if (isInterModuleKnownError(authInterModuleContract.methods.requireAdminRole, error) && error.code === 'admin-role-required') {
|
|
34
|
-
throw new ClientError('Administrator observer role required', 'forbidden', {
|
|
35
|
-
status: 403,
|
|
36
|
-
details: {
|
|
37
|
-
required_role: error.details.requiredRole
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
throw error;
|
|
42
|
-
},
|
|
104
|
+
errorHandler: translateObserverAdministrationError,
|
|
43
105
|
handler: async (request)=>{
|
|
44
106
|
const client = getUserContext(request);
|
|
45
107
|
if (!client) {
|
|
@@ -73,10 +135,53 @@ export function createAdminWorkspacesRoutes(params) {
|
|
|
73
135
|
};
|
|
74
136
|
}
|
|
75
137
|
});
|
|
138
|
+
const createMutationRoute = (routeParams)=>defineRoute({
|
|
139
|
+
method: 'POST',
|
|
140
|
+
path: `/:workspaceId/${routeParams.action}`,
|
|
141
|
+
description: routeParams.description,
|
|
142
|
+
auth: AUTH_USER,
|
|
143
|
+
schema: {
|
|
144
|
+
params: z.object({
|
|
145
|
+
workspaceId: z.string().uuid()
|
|
146
|
+
}),
|
|
147
|
+
body: workspaceAdministrationMutationBodySchema,
|
|
148
|
+
response: {
|
|
149
|
+
200: workspaceAdministrationMutationResponseSchema
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
errorHandler: translateAdministrationError,
|
|
153
|
+
handler: async (request)=>{
|
|
154
|
+
const actorId = requireActorId(request);
|
|
155
|
+
const actorRole = (await params.auth.requireAdminRole({
|
|
156
|
+
userId: actorId,
|
|
157
|
+
minimumRole: 'admin-operator'
|
|
158
|
+
})).role;
|
|
159
|
+
return toWorkspaceAdministrationMutationDto(await routeParams.execute({
|
|
160
|
+
actorId,
|
|
161
|
+
actorRole,
|
|
162
|
+
workspaceId: request.params.workspaceId,
|
|
163
|
+
reason: request.body.reason,
|
|
164
|
+
idempotencyKey: requireIdempotencyKey(request),
|
|
165
|
+
correlationId: request.id
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
const suspendRoute = createMutationRoute({
|
|
170
|
+
action: 'suspend',
|
|
171
|
+
description: 'Suspend a workspace for reversible administrator moderation.',
|
|
172
|
+
execute: suspendWorkspace
|
|
173
|
+
});
|
|
174
|
+
const reactivateRoute = createMutationRoute({
|
|
175
|
+
action: 'reactivate',
|
|
176
|
+
description: 'Reactivate a suspended workspace.',
|
|
177
|
+
execute: reactivateWorkspace
|
|
178
|
+
});
|
|
76
179
|
return {
|
|
77
180
|
prefix: '/admin/workspaces',
|
|
78
181
|
routes: [
|
|
79
|
-
listRoute
|
|
182
|
+
listRoute,
|
|
183
|
+
suspendRoute,
|
|
184
|
+
reactivateRoute
|
|
80
185
|
]
|
|
81
186
|
};
|
|
82
187
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/admin-workspaces.ts"],"sourcesContent":["import {AUTH_USER, getUserContext} from '@shipfox/api-auth-context';\nimport {\n type AuthInterModuleClient,\n authInterModuleContract,\n} 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 {\n listWorkspaceAdminSummariesResponseSchema,\n workspaceAdminLookupQuerySchema,\n} from '@shipfox/api-workspaces-dto';\nimport {isInterModuleKnownError} from '@shipfox/inter-module';\nimport {decodeStringIdCursor, encodeStringIdCursor} from '@shipfox/node-drizzle';\nimport {ClientError, defineRoute, type RouteGroup} from '@shipfox/node-fastify';\nimport {\n listWorkspaceAdministratorSummaries,\n type WorkspaceAdministratorSummary,\n} from '#core/admin-workspaces.js';\n\nfunction toWorkspaceAdministratorSummaryDto(summary: WorkspaceAdministratorSummary) {\n return {\n id: summary.id,\n name: summary.name,\n status: summary.status,\n member_summary: summary.memberSummary,\n project_summary: summary.projectSummary,\n job_counts: summary.jobCounts,\n created_at: summary.createdAt.toISOString(),\n updated_at: summary.updatedAt.toISOString(),\n };\n}\n\nexport function createAdminWorkspacesRoutes(params: {\n auth: AuthInterModuleClient;\n projects: ProjectsModuleClient;\n runners: RunnersInterModuleClient;\n}): RouteGroup {\n const listRoute = defineRoute({\n method: 'GET',\n path: '/',\n description: 'List bounded safe workspace summaries for administrators.',\n auth: AUTH_USER,\n schema: {\n querystring: workspaceAdminLookupQuerySchema,\n response: {200: listWorkspaceAdminSummariesResponseSchema},\n },\n errorHandler: (error) => {\n if (\n isInterModuleKnownError(authInterModuleContract.methods.requireAdminRole, error) &&\n error.code === 'admin-role-required'\n ) {\n throw new ClientError('Administrator observer role required', 'forbidden', {\n status: 403,\n details: {required_role: error.details.requiredRole},\n });\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 await params.auth.requireAdminRole({\n userId: client.userId,\n minimumRole: 'admin-observer',\n });\n\n const {workspace_id: workspaceId, search, status, limit, cursor} = request.query;\n const decodedCursor = decodeStringIdCursor(cursor);\n if (cursor && !decodedCursor) {\n throw new ClientError('Invalid cursor', 'invalid-cursor', {status: 400});\n }\n\n const result = await listWorkspaceAdministratorSummaries({\n workspaceId,\n search,\n status,\n limit,\n cursor: decodedCursor,\n projects: params.projects,\n runners: params.runners,\n });\n\n return {\n workspaces: result.workspaces.map(toWorkspaceAdministratorSummaryDto),\n next_cursor: result.nextCursor ? encodeStringIdCursor(result.nextCursor) : null,\n };\n },\n });\n\n return {prefix: '/admin/workspaces', routes: [listRoute]};\n}\n"],"names":["AUTH_USER","getUserContext","authInterModuleContract","listWorkspaceAdminSummariesResponseSchema","workspaceAdminLookupQuerySchema","isInterModuleKnownError","decodeStringIdCursor","encodeStringIdCursor","ClientError","defineRoute","listWorkspaceAdministratorSummaries","toWorkspaceAdministratorSummaryDto","summary","id","name","status","member_summary","memberSummary","project_summary","projectSummary","job_counts","jobCounts","created_at","createdAt","toISOString","updated_at","updatedAt","createAdminWorkspacesRoutes","params","listRoute","method","path","description","auth","schema","querystring","response","errorHandler","error","methods","requireAdminRole","code","details","required_role","requiredRole","handler","request","client","userId","minimumRole","workspace_id","workspaceId","search","limit","cursor","query","decodedCursor","result","projects","runners","workspaces","map","next_cursor","nextCursor","prefix","routes"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,cAAc,QAAO,4BAA4B;AACpE,SAEEC,uBAAuB,QAClB,qCAAqC;AAG5C,SACEC,yCAAyC,EACzCC,+BAA+B,QAC1B,8BAA8B;AACrC,SAAQC,uBAAuB,QAAO,wBAAwB;AAC9D,SAAQC,oBAAoB,EAAEC,oBAAoB,QAAO,wBAAwB;AACjF,SAAQC,WAAW,EAAEC,WAAW,QAAwB,wBAAwB;AAChF,SACEC,mCAAmC,QAE9B,4BAA4B;AAEnC,SAASC,mCAAmCC,OAAsC;IAChF,OAAO;QACLC,IAAID,QAAQC,EAAE;QACdC,MAAMF,QAAQE,IAAI;QAClBC,QAAQH,QAAQG,MAAM;QACtBC,gBAAgBJ,QAAQK,aAAa;QACrCC,iBAAiBN,QAAQO,cAAc;QACvCC,YAAYR,QAAQS,SAAS;QAC7BC,YAAYV,QAAQW,SAAS,CAACC,WAAW;QACzCC,YAAYb,QAAQc,SAAS,CAACF,WAAW;IAC3C;AACF;AAEA,OAAO,SAASG,4BAA4BC,MAI3C;IACC,MAAMC,YAAYpB,YAAY;QAC5BqB,QAAQ;QACRC,MAAM;QACNC,aAAa;QACbC,MAAMjC;QACNkC,QAAQ;YACNC,aAAa/B;YACbgC,UAAU;gBAAC,KAAKjC;YAAyC;QAC3D;QACAkC,cAAc,CAACC;YACb,IACEjC,wBAAwBH,wBAAwBqC,OAAO,CAACC,gBAAgB,EAAEF,UAC1EA,MAAMG,IAAI,KAAK,uBACf;gBACA,MAAM,IAAIjC,YAAY,wCAAwC,aAAa;oBACzEO,QAAQ;oBACR2B,SAAS;wBAACC,eAAeL,MAAMI,OAAO,CAACE,YAAY;oBAAA;gBACrD;YACF;YACA,MAAMN;QACR;QACAO,SAAS,OAAOC;YACd,MAAMC,SAAS9C,eAAe6C;YAC9B,IAAI,CAACC,QAAQ;gBACX,MAAM,IAAIvC,YAAY,2BAA2B,gBAAgB;oBAACO,QAAQ;gBAAG;YAC/E;YACA,MAAMa,OAAOK,IAAI,CAACO,gBAAgB,CAAC;gBACjCQ,QAAQD,OAAOC,MAAM;gBACrBC,aAAa;YACf;YAEA,MAAM,EAACC,cAAcC,WAAW,EAAEC,MAAM,EAAErC,MAAM,EAAEsC,KAAK,EAAEC,MAAM,EAAC,GAAGR,QAAQS,KAAK;YAChF,MAAMC,gBAAgBlD,qBAAqBgD;YAC3C,IAAIA,UAAU,CAACE,eAAe;gBAC5B,MAAM,IAAIhD,YAAY,kBAAkB,kBAAkB;oBAACO,QAAQ;gBAAG;YACxE;YAEA,MAAM0C,SAAS,MAAM/C,oCAAoC;gBACvDyC;gBACAC;gBACArC;gBACAsC;gBACAC,QAAQE;gBACRE,UAAU9B,OAAO8B,QAAQ;gBACzBC,SAAS/B,OAAO+B,OAAO;YACzB;YAEA,OAAO;gBACLC,YAAYH,OAAOG,UAAU,CAACC,GAAG,CAAClD;gBAClCmD,aAAaL,OAAOM,UAAU,GAAGxD,qBAAqBkD,OAAOM,UAAU,IAAI;YAC7E;QACF;IACF;IAEA,OAAO;QAACC,QAAQ;QAAqBC,QAAQ;YAACpC;SAAU;IAAA;AAC1D"}
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/admin-workspaces.ts"],"sourcesContent":["import {AUTH_USER, getUserContext} from '@shipfox/api-auth-context';\nimport {\n type AuthInterModuleClient,\n authInterModuleContract,\n} 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 {\n listWorkspaceAdminSummariesResponseSchema,\n workspaceAdministrationMutationBodySchema,\n workspaceAdministrationMutationResponseSchema,\n workspaceAdminLookupQuerySchema,\n} from '@shipfox/api-workspaces-dto';\nimport {isInterModuleKnownError} from '@shipfox/inter-module';\nimport {decodeStringIdCursor, encodeStringIdCursor} from '@shipfox/node-drizzle';\nimport {ClientError, defineRoute, type RouteGroup} from '@shipfox/node-fastify';\nimport type {FastifyRequest} from 'fastify';\nimport {z} from 'zod';\nimport {\n listWorkspaceAdministratorSummaries,\n reactivateWorkspace,\n suspendWorkspace,\n type WorkspaceAdministrationMutationContext,\n type WorkspaceAdministratorSummary,\n} from '#core/admin-workspaces.js';\nimport {\n WorkspaceAdminIdempotencyKeyReuseError,\n WorkspaceAlreadySuspendedError,\n WorkspaceDeletedError,\n WorkspaceNotFoundError,\n WorkspaceNotSuspendedError,\n} from '#core/errors.js';\n\nconst idempotencyKeyMaxLength = 256;\n\nfunction requireActorId(request: FastifyRequest): string {\n const client = getUserContext(request);\n if (!client) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n return client.userId;\n}\n\nfunction requireIdempotencyKey(request: FastifyRequest): string {\n const value = request.headers['idempotency-key'];\n const key = Array.isArray(value) ? value[0] : value;\n if (!key || key.trim().length === 0 || key.length > idempotencyKeyMaxLength) {\n throw new ClientError('Idempotency-Key header is required', 'idempotency-key-required', {\n status: 400,\n });\n }\n return key;\n}\n\nfunction translateAdminRoleError(error: unknown, message: string): void {\n if (\n isInterModuleKnownError(authInterModuleContract.methods.requireAdminRole, error) &&\n error.code === 'admin-role-required'\n ) {\n throw new ClientError(message, 'forbidden', {\n status: 403,\n details: {required_role: error.details.requiredRole},\n });\n }\n}\n\nfunction translateAdministrationError(error: unknown): never {\n translateAdminRoleError(error, 'Administrator operator role required');\n if (error instanceof WorkspaceNotFoundError) {\n throw new ClientError('Workspace not found', 'workspace-not-found', {status: 404});\n }\n if (error instanceof WorkspaceDeletedError) {\n throw new ClientError('Workspace is deleted', 'workspace-deleted', {status: 409});\n }\n if (error instanceof WorkspaceAlreadySuspendedError) {\n throw new ClientError('Workspace is already suspended', 'workspace-already-suspended', {\n status: 409,\n });\n }\n if (error instanceof WorkspaceNotSuspendedError) {\n throw new ClientError('Workspace is not suspended', 'workspace-not-suspended', {\n status: 409,\n });\n }\n if (error instanceof WorkspaceAdminIdempotencyKeyReuseError) {\n throw new ClientError(\n 'Idempotency-Key was already used for a different workspace command',\n 'idempotency-key-reused',\n {status: 409},\n );\n }\n throw error;\n}\n\nfunction translateObserverAdministrationError(error: unknown): never {\n translateAdminRoleError(error, 'Administrator observer role required');\n throw error;\n}\n\nfunction toWorkspaceAdministrationMutationDto(result: {\n workspaceId: string;\n status: 'active' | 'suspended';\n correlationId: string;\n}) {\n return {\n workspace_id: result.workspaceId,\n status: result.status,\n correlation_id: result.correlationId,\n };\n}\n\nfunction toWorkspaceAdministratorSummaryDto(summary: WorkspaceAdministratorSummary) {\n return {\n id: summary.id,\n name: summary.name,\n status: summary.status,\n member_summary: summary.memberSummary,\n project_summary: summary.projectSummary,\n job_counts: summary.jobCounts,\n created_at: summary.createdAt.toISOString(),\n updated_at: summary.updatedAt.toISOString(),\n };\n}\n\nexport function createAdminWorkspacesRoutes(params: {\n auth: AuthInterModuleClient;\n projects: ProjectsModuleClient;\n runners: RunnersInterModuleClient;\n}): RouteGroup {\n const listRoute = defineRoute({\n method: 'GET',\n path: '/',\n description: 'List bounded safe workspace summaries for administrators.',\n auth: AUTH_USER,\n schema: {\n querystring: workspaceAdminLookupQuerySchema,\n response: {200: listWorkspaceAdminSummariesResponseSchema},\n },\n errorHandler: translateObserverAdministrationError,\n handler: async (request) => {\n const client = getUserContext(request);\n if (!client) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n await params.auth.requireAdminRole({\n userId: client.userId,\n minimumRole: 'admin-observer',\n });\n\n const {workspace_id: workspaceId, search, status, limit, cursor} = request.query;\n const decodedCursor = decodeStringIdCursor(cursor);\n if (cursor && !decodedCursor) {\n throw new ClientError('Invalid cursor', 'invalid-cursor', {status: 400});\n }\n\n const result = await listWorkspaceAdministratorSummaries({\n workspaceId,\n search,\n status,\n limit,\n cursor: decodedCursor,\n projects: params.projects,\n runners: params.runners,\n });\n\n return {\n workspaces: result.workspaces.map(toWorkspaceAdministratorSummaryDto),\n next_cursor: result.nextCursor ? encodeStringIdCursor(result.nextCursor) : null,\n };\n },\n });\n\n const createMutationRoute = (routeParams: {\n action: 'suspend' | 'reactivate';\n description: string;\n execute: (context: WorkspaceAdministrationMutationContext) => Promise<{\n workspaceId: string;\n status: 'active' | 'suspended';\n correlationId: string;\n }>;\n }) =>\n defineRoute({\n method: 'POST',\n path: `/:workspaceId/${routeParams.action}`,\n description: routeParams.description,\n auth: AUTH_USER,\n schema: {\n params: z.object({workspaceId: z.string().uuid()}),\n body: workspaceAdministrationMutationBodySchema,\n response: {200: workspaceAdministrationMutationResponseSchema},\n },\n errorHandler: translateAdministrationError,\n handler: async (request) => {\n const actorId = requireActorId(request);\n const actorRole = (\n await params.auth.requireAdminRole({userId: actorId, minimumRole: 'admin-operator'})\n ).role;\n return toWorkspaceAdministrationMutationDto(\n await routeParams.execute({\n actorId,\n actorRole,\n workspaceId: request.params.workspaceId,\n reason: request.body.reason,\n idempotencyKey: requireIdempotencyKey(request),\n correlationId: request.id,\n }),\n );\n },\n });\n\n const suspendRoute = createMutationRoute({\n action: 'suspend',\n description: 'Suspend a workspace for reversible administrator moderation.',\n execute: suspendWorkspace,\n });\n const reactivateRoute = createMutationRoute({\n action: 'reactivate',\n description: 'Reactivate a suspended workspace.',\n execute: reactivateWorkspace,\n });\n\n return {prefix: '/admin/workspaces', routes: [listRoute, suspendRoute, reactivateRoute]};\n}\n"],"names":["AUTH_USER","getUserContext","authInterModuleContract","listWorkspaceAdminSummariesResponseSchema","workspaceAdministrationMutationBodySchema","workspaceAdministrationMutationResponseSchema","workspaceAdminLookupQuerySchema","isInterModuleKnownError","decodeStringIdCursor","encodeStringIdCursor","ClientError","defineRoute","z","listWorkspaceAdministratorSummaries","reactivateWorkspace","suspendWorkspace","WorkspaceAdminIdempotencyKeyReuseError","WorkspaceAlreadySuspendedError","WorkspaceDeletedError","WorkspaceNotFoundError","WorkspaceNotSuspendedError","idempotencyKeyMaxLength","requireActorId","request","client","status","userId","requireIdempotencyKey","value","headers","key","Array","isArray","trim","length","translateAdminRoleError","error","message","methods","requireAdminRole","code","details","required_role","requiredRole","translateAdministrationError","translateObserverAdministrationError","toWorkspaceAdministrationMutationDto","result","workspace_id","workspaceId","correlation_id","correlationId","toWorkspaceAdministratorSummaryDto","summary","id","name","member_summary","memberSummary","project_summary","projectSummary","job_counts","jobCounts","created_at","createdAt","toISOString","updated_at","updatedAt","createAdminWorkspacesRoutes","params","listRoute","method","path","description","auth","schema","querystring","response","errorHandler","handler","minimumRole","search","limit","cursor","query","decodedCursor","projects","runners","workspaces","map","next_cursor","nextCursor","createMutationRoute","routeParams","action","object","string","uuid","body","actorId","actorRole","role","execute","reason","idempotencyKey","suspendRoute","reactivateRoute","prefix","routes"],"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,
|
|
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"}
|