fix(workspaces): assign role on workspace join (#2864)

This commit is contained in:
Gergő Jedlicska
2024-09-03 18:54:06 +02:00
committed by GitHub
parent beabb5552b
commit c3b05abd6f
5 changed files with 127 additions and 9 deletions
@@ -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)
)
]
+7 -1
View File
@@ -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