fix(workspaces): assign role on workspace join (#2864)
This commit is contained in:
@@ -6,7 +6,8 @@ import {
|
||||
import { getStream } from '@/modules/core/repositories/streams'
|
||||
import {
|
||||
GetWorkspaceRoles,
|
||||
GetWorkspaceRoleToDefaultProjectRoleMapping
|
||||
GetWorkspaceRoleToDefaultProjectRoleMapping,
|
||||
QueryAllWorkspaceProjects
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import {
|
||||
ServerInvitesEvents,
|
||||
@@ -20,8 +21,9 @@ import { logger } from '@/logging/logging'
|
||||
import { updateWorkspaceRoleFactory } from '@/modules/workspaces/services/management'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { WorkspaceInviteResourceType } from '@/modules/workspaces/domain/constants'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { Roles, WorkspaceRoles } from '@speckle/shared'
|
||||
import { UpsertProjectRole } from '@/modules/core/domain/projects/operations'
|
||||
import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
|
||||
|
||||
export const onProjectCreatedFactory =
|
||||
({
|
||||
@@ -107,19 +109,64 @@ export const onInviteFinalizedFactory =
|
||||
})
|
||||
}
|
||||
|
||||
export const onWorkspaceJoinedFactory =
|
||||
({
|
||||
getDefaultWorkspaceProjectRoleMapping,
|
||||
queryAllWorkspaceProjects,
|
||||
upsertProjectRole
|
||||
}: {
|
||||
getDefaultWorkspaceProjectRoleMapping: GetWorkspaceRoleToDefaultProjectRoleMapping
|
||||
queryAllWorkspaceProjects: QueryAllWorkspaceProjects
|
||||
upsertProjectRole: UpsertProjectRole
|
||||
}) =>
|
||||
async ({
|
||||
userId,
|
||||
role,
|
||||
workspaceId
|
||||
}: {
|
||||
userId: string
|
||||
role: WorkspaceRoles
|
||||
workspaceId: string
|
||||
}) => {
|
||||
const defaultRoleMapping = await getDefaultWorkspaceProjectRoleMapping({
|
||||
workspaceId
|
||||
})
|
||||
|
||||
const maybeProjectRole = defaultRoleMapping[role]
|
||||
if (!maybeProjectRole) return
|
||||
|
||||
for await (const projects of queryAllWorkspaceProjects({ workspaceId })) {
|
||||
await Promise.all(
|
||||
projects.map(async (project) => {
|
||||
await upsertProjectRole({
|
||||
projectId: project.id,
|
||||
userId,
|
||||
role: maybeProjectRole
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const initializeEventListenersFactory =
|
||||
({
|
||||
onProjectCreated,
|
||||
onInviteFinalized
|
||||
onInviteFinalized,
|
||||
onWorkspaceJoined
|
||||
}: {
|
||||
onProjectCreated: ReturnType<typeof onProjectCreatedFactory>
|
||||
onInviteFinalized: ReturnType<typeof onInviteFinalizedFactory>
|
||||
onWorkspaceJoined: ReturnType<typeof onWorkspaceJoinedFactory>
|
||||
}) =>
|
||||
() => {
|
||||
const eventBus = getEventBus()
|
||||
const quitCbs = [
|
||||
ProjectsEmitter.listen(ProjectEvents.Created, onProjectCreated),
|
||||
getEventBus().listen(ServerInvitesEvents.Finalized, ({ payload }) =>
|
||||
eventBus.listen(ServerInvitesEvents.Finalized, ({ payload }) =>
|
||||
onInviteFinalized(payload)
|
||||
),
|
||||
eventBus.listen(WorkspaceEvents.JoinedFromDiscovery, ({ payload }) =>
|
||||
onWorkspaceJoined(payload)
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ import { registerOrUpdateRole } from '@/modules/shared/repositories/roles'
|
||||
import {
|
||||
initializeEventListenersFactory,
|
||||
onInviteFinalizedFactory,
|
||||
onProjectCreatedFactory
|
||||
onProjectCreatedFactory,
|
||||
onWorkspaceJoinedFactory
|
||||
} from '@/modules/workspaces/events/eventListener'
|
||||
import {
|
||||
getWorkspaceRolesFactory,
|
||||
@@ -63,6 +64,11 @@ const workspacesModule: SpeckleModule = {
|
||||
upsertProjectRole: upsertProjectRoleFactory({ db }),
|
||||
getWorkspaceRoles: getWorkspaceRolesFactory({ db })
|
||||
}),
|
||||
onWorkspaceJoined: onWorkspaceJoinedFactory({
|
||||
getDefaultWorkspaceProjectRoleMapping: mapWorkspaceRoleToInitialProjectRole,
|
||||
queryAllWorkspaceProjects: queryAllWorkspaceProjectsFactory({ getStreams }),
|
||||
upsertProjectRole: upsertProjectRoleFactory({ db })
|
||||
}),
|
||||
onInviteFinalized: onInviteFinalizedFactory({
|
||||
getStream,
|
||||
logger: moduleLogger,
|
||||
|
||||
@@ -46,7 +46,7 @@ export const joinWorkspaceFactory =
|
||||
await upsertWorkspaceRole({ userId, workspaceId, role, createdAt: new Date() })
|
||||
await emitWorkspaceEvent({
|
||||
eventName: WorkspaceEvents.JoinedFromDiscovery,
|
||||
payload: { userId, workspaceId }
|
||||
payload: { userId, workspaceId, role }
|
||||
})
|
||||
await emitWorkspaceEvent({
|
||||
eventName: WorkspaceEvents.RoleUpdated,
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { WorkspaceAcl } from '@/modules/workspacesCore/domain/types'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { Roles, StreamRoles } from '@speckle/shared'
|
||||
import { StreamAclRecord, StreamRecord } from '@/modules/core/helpers/types'
|
||||
import { onProjectCreatedFactory } from '@/modules/workspaces/events/eventListener'
|
||||
import {
|
||||
onProjectCreatedFactory,
|
||||
onWorkspaceJoinedFactory
|
||||
} from '@/modules/workspaces/events/eventListener'
|
||||
import { expect } from 'chai'
|
||||
import { mapWorkspaceRoleToInitialProjectRole } from '@/modules/workspaces/domain/logic'
|
||||
import { chunk } from 'lodash'
|
||||
|
||||
describe('Event handlers', () => {
|
||||
describe('onProjectCreatedFactory creates a function, that', () => {
|
||||
@@ -57,4 +61,60 @@ describe('Event handlers', () => {
|
||||
expect(projectRoles.length).to.equal(2)
|
||||
})
|
||||
})
|
||||
describe('onWorkspaceJoinedFactory creates a function, that', () => {
|
||||
it('assigns no project roles if the role mapping returns null', async () => {
|
||||
await onWorkspaceJoinedFactory({
|
||||
getDefaultWorkspaceProjectRoleMapping: async () => ({
|
||||
[Roles.Workspace.Admin]: Roles.Stream.Owner,
|
||||
[Roles.Workspace.Member]: Roles.Stream.Contributor,
|
||||
[Roles.Workspace.Guest]: null
|
||||
}),
|
||||
async *queryAllWorkspaceProjects() {
|
||||
expect.fail()
|
||||
},
|
||||
upsertProjectRole: async () => {
|
||||
expect.fail()
|
||||
}
|
||||
})({
|
||||
role: Roles.Workspace.Guest,
|
||||
userId: cryptoRandomString({ length: 10 }),
|
||||
workspaceId: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
})
|
||||
it('assigns the mapped projects roles to all queried project', async () => {
|
||||
const projectIds = [
|
||||
cryptoRandomString({ length: 10 }),
|
||||
cryptoRandomString({ length: 10 }),
|
||||
cryptoRandomString({ length: 10 }),
|
||||
cryptoRandomString({ length: 10 })
|
||||
]
|
||||
const userId = cryptoRandomString({ length: 10 })
|
||||
const projectRole = Roles.Stream.Reviewer
|
||||
|
||||
const storedRoles: { userId: string; role: StreamRoles; projectId: string }[] = []
|
||||
await onWorkspaceJoinedFactory({
|
||||
getDefaultWorkspaceProjectRoleMapping: async () => ({
|
||||
[Roles.Workspace.Admin]: Roles.Stream.Owner,
|
||||
[Roles.Workspace.Member]: projectRole,
|
||||
[Roles.Workspace.Guest]: null
|
||||
}),
|
||||
async *queryAllWorkspaceProjects() {
|
||||
for (const projIds of chunk(projectIds, 3)) {
|
||||
yield projIds.map((projId) => ({ id: projId } as unknown as StreamRecord))
|
||||
}
|
||||
},
|
||||
upsertProjectRole: async (args) => {
|
||||
storedRoles.push(args)
|
||||
return {} as StreamRecord
|
||||
}
|
||||
})({
|
||||
role: Roles.Workspace.Member,
|
||||
userId,
|
||||
workspaceId: cryptoRandomString({ length: 10 })
|
||||
})
|
||||
expect(storedRoles).deep.equals(
|
||||
projectIds.map((projectId) => ({ projectId, role: projectRole, userId }))
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Workspace, WorkspaceAcl } from '@/modules/workspacesCore/domain/types'
|
||||
import { WorkspaceRoles } from '@speckle/shared'
|
||||
|
||||
export const workspaceEventNamespace = 'workspace' as const
|
||||
|
||||
@@ -20,7 +21,11 @@ type WorkspaceCreatedPayload = Workspace & {
|
||||
type WorkspaceUpdatedPayload = Workspace
|
||||
type WorkspaceRoleDeletedPayload = Pick<WorkspaceAcl, 'userId' | 'workspaceId' | 'role'>
|
||||
type WorkspaceRoleUpdatedPayload = Pick<WorkspaceAcl, 'userId' | 'workspaceId' | 'role'>
|
||||
type WorkspaceJoinedFromDiscoveryPayload = { userId: string; workspaceId: string }
|
||||
type WorkspaceJoinedFromDiscoveryPayload = {
|
||||
userId: string
|
||||
workspaceId: string
|
||||
role: WorkspaceRoles
|
||||
}
|
||||
|
||||
export type WorkspaceEventsPayloads = {
|
||||
[WorkspaceEvents.Created]: WorkspaceCreatedPayload
|
||||
|
||||
Reference in New Issue
Block a user