Merge branch 'main' of github.com:specklesystems/speckle-server into gergo/web-1968-add-features-list

This commit is contained in:
Gergő Jedlicska
2024-10-20 14:53:52 +02:00
354 changed files with 14449 additions and 7284 deletions
@@ -8,9 +8,17 @@ import {
WorkspaceWithOptionalRole
} from '@/modules/workspacesCore/domain/types'
import { EventBusPayloads } from '@/modules/shared/services/eventBus'
import { PartialNullable, StreamRoles, WorkspaceRoles } from '@speckle/shared'
import {
MaybeNullOrUndefined,
Nullable,
PartialNullable,
StreamRoles,
WorkspaceRoles
} from '@speckle/shared'
import { WorkspaceRoleToDefaultProjectRoleMapping } from '@/modules/workspaces/domain/types'
import { WorkspaceTeam } from '@/modules/workspaces/domain/types'
import { Stream } from '@/modules/core/domain/streams/types'
import { TokenResourceIdentifier } from '@/modules/core/domain/tokens/types'
/** Workspace */
@@ -79,9 +87,9 @@ export type GetWorkspaceCollaboratorsArgs = {
cursor?: string
filter?: {
/**
* Optionally filter by workspace role
* Optionally filter by workspace role(s)
*/
role?: string
roles?: string[]
/**
* Optionally filter by user name or email
*/
@@ -184,6 +192,23 @@ export type GrantWorkspaceProjectRoles = (
args: GrantWorkspaceProjectRolesArgs
) => Promise<void>
type UpdateWorkspaceProjectRoleArgs = {
role: {
projectId: string
userId: string
// Undefined or null role means delete role
role?: Nullable<string>
}
updater: {
userId: string
resourceAccessRules: MaybeNullOrUndefined<TokenResourceIdentifier[]>
}
}
export type UpdateWorkspaceProjectRole = (
args: UpdateWorkspaceProjectRoleArgs
) => Promise<Stream | undefined>
/** Events */
export type EmitWorkspaceEvent = <TEvent extends WorkspaceEvents>(args: {
@@ -5,7 +5,8 @@ import {
} from '@/modules/core/events/projectsEmitter'
import {
deleteProjectRoleFactory,
getStream,
getStreamFactory,
legacyGetStreamsFactory,
upsertProjectRoleFactory
} from '@/modules/core/repositories/streams'
import {
@@ -42,9 +43,9 @@ import {
queryAllWorkspaceProjectsFactory,
getWorkspaceRoleToDefaultProjectRoleMappingFactory
} from '@/modules/workspaces/services/projects'
import { getStreams } from '@/modules/core/services/streams'
import { withTransaction } from '@/modules/shared/helpers/dbHelper'
import { findVerifiedEmailsByUserIdFactory } from '@/modules/core/repositories/userEmails'
import { GetStream } from '@/modules/core/domain/streams/operations'
export const onProjectCreatedFactory =
({
@@ -89,7 +90,7 @@ export const onProjectCreatedFactory =
export const onInviteFinalizedFactory =
(deps: {
getStream: typeof getStream
getStream: GetStream
logger: typeof logger
updateWorkspaceRole: ReturnType<typeof updateWorkspaceRoleFactory>
}) =>
@@ -216,6 +217,7 @@ export const initializeEventListenersFactory =
({ db }: { db: Knex }) =>
() => {
const eventBus = getEventBus()
const getStreams = legacyGetStreamsFactory({ db })
const quitCbs = [
ProjectsEmitter.listen(ProjectEvents.Created, async (payload) => {
const onProjectCreated = onProjectCreatedFactory({
@@ -230,7 +232,7 @@ export const initializeEventListenersFactory =
}),
eventBus.listen(ServerInvitesEvents.Finalized, async ({ payload }) => {
const onInviteFinalized = onInviteFinalizedFactory({
getStream,
getStream: getStreamFactory({ db }),
logger: moduleLogger,
updateWorkspaceRole: updateWorkspaceRoleFactory({
getWorkspaceWithDomains: getWorkspaceWithDomainsFactory({ db }),
@@ -4,15 +4,17 @@ import { removePrivateFields } from '@/modules/core/helpers/userHelper'
import {
getProjectCollaboratorsFactory,
getProjectFactory,
getStream,
getUserStreams,
getUserStreamsCount,
updateProjectFactory,
upsertProjectRoleFactory,
getRolesByUserIdFactory
getRolesByUserIdFactory,
getStreamFactory,
deleteStreamFactory,
revokeStreamPermissionsFactory,
grantStreamPermissionsFactory,
legacyGetStreamsFactory,
getUserStreamsPageFactory,
getUserStreamsCountFactory
} from '@/modules/core/repositories/streams'
import { getUser, getUsers } from '@/modules/core/repositories/users'
import { getStreams } from '@/modules/core/services/streams'
import { InviteCreateValidationError } from '@/modules/serverinvites/errors'
import {
deleteAllResourceInvitesFactory,
@@ -45,8 +47,6 @@ import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { WorkspaceInviteResourceType } from '@/modules/workspaces/domain/constants'
import {
WorkspaceAdminError,
WorkspaceInvalidProjectError,
WorkspaceInvalidRoleError,
WorkspaceJoinNotAllowedError,
WorkspaceNotFoundError,
@@ -100,7 +100,8 @@ import {
getWorkspaceProjectsFactory,
getWorkspaceRoleToDefaultProjectRoleMappingFactory,
moveProjectToWorkspaceFactory,
queryAllWorkspaceProjectsFactory
queryAllWorkspaceProjectsFactory,
updateWorkspaceProjectRoleFactory
} from '@/modules/workspaces/services/projects'
import {
getDiscoverableWorkspacesForUserFactory,
@@ -109,7 +110,6 @@ import {
} from '@/modules/workspaces/services/retrieval'
import { Roles, WorkspaceRoles, removeNullOrUndefinedKeys } from '@speckle/shared'
import { chunk } from 'lodash'
import { deleteStream } from '@/modules/core/repositories/streams'
import {
findEmailsByUserIdFactory,
findVerifiedEmailsByUserIdFactory,
@@ -129,13 +129,31 @@ import {
deleteWorkspaceDomainFactory,
isUserWorkspaceDomainPolicyCompliantFactory
} from '@/modules/workspaces/services/domains'
import { getServerInfo } from '@/modules/core/services/generic'
import { updateStreamRoleAndNotify } from '@/modules/core/services/streams/management'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { parseDefaultProjectRole } from '@/modules/workspaces/domain/logic'
import { saveActivityFactory } from '@/modules/activitystream/repositories'
import {
addOrUpdateStreamCollaboratorFactory,
isStreamCollaboratorFactory,
removeStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import {
addStreamInviteAcceptedActivityFactory,
addStreamPermissionsAddedActivityFactory,
addStreamPermissionsRevokedActivityFactory
} from '@/modules/activitystream/services/streamActivity'
import { publish } from '@/modules/shared/utils/subscriptions'
import { updateStreamRoleAndNotifyFactory } from '@/modules/core/services/streams/management'
import { getUserFactory, getUsersFactory } from '@/modules/core/repositories/users'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
const getUsers = getUsersFactory({ db })
const getStream = getStreamFactory({ db })
const requestNewEmailVerification = requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
@@ -155,7 +173,7 @@ const buildCollectAndValidateResourceTargets = () =>
const buildCreateAndSendServerOrProjectInvite = () =>
createAndSendInviteFactory({
findUserByTarget: findUserByTargetFactory(),
findUserByTarget: findUserByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
collectAndValidateResourceTargets: buildCollectAndValidateResourceTargets(),
buildInviteEmailContents: buildCoreInviteEmailContentsFactory({
@@ -165,12 +183,14 @@ const buildCreateAndSendServerOrProjectInvite = () =>
getEventBus().emit({
eventName,
payload
})
}),
getUser,
getServerInfo
})
const buildCreateAndSendWorkspaceInvite = () =>
createAndSendInviteFactory({
findUserByTarget: findUserByTargetFactory(),
findUserByTarget: findUserByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
collectAndValidateResourceTargets: buildCollectAndValidateResourceTargets(),
buildInviteEmailContents: buildWorkspaceInviteEmailContentsFactory({
@@ -181,8 +201,44 @@ const buildCreateAndSendWorkspaceInvite = () =>
getEventBus().emit({
eventName,
payload
})
}),
getUser,
getServerInfo
})
const deleteStream = deleteStreamFactory({ db })
const saveActivity = saveActivityFactory({ db })
const validateStreamAccess = validateStreamAccessFactory({ authorizeResolver })
const isStreamCollaborator = isStreamCollaboratorFactory({
getStream
})
const removeStreamCollaborator = removeStreamCollaboratorFactory({
validateStreamAccess,
isStreamCollaborator,
revokeStreamPermissions: revokeStreamPermissionsFactory({ db }),
addStreamPermissionsRevokedActivity: addStreamPermissionsRevokedActivityFactory({
saveActivity,
publish
})
})
const updateStreamRoleAndNotify = updateStreamRoleAndNotifyFactory({
isStreamCollaborator,
addOrUpdateStreamCollaborator: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess,
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({
saveActivity,
publish
}),
addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({
saveActivity,
publish
})
}),
removeStreamCollaborator
})
const getUserStreams = getUserStreamsPageFactory({ db })
const getUserStreamsCount = getUserStreamsCountFactory({ db })
const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
@@ -348,6 +404,7 @@ export = FF_WORKSPACES_MODULE_ENABLED
)
// Delete workspace and associated resources (i.e. invites)
const getStreams = legacyGetStreamsFactory({ db })
const deleteWorkspace = deleteWorkspaceFactory({
deleteWorkspace: repoDeleteWorkspaceFactory({ db }),
deleteProject: deleteStream,
@@ -538,12 +595,14 @@ export = FF_WORKSPACES_MODULE_ENABLED
getStream,
getWorkspace: getWorkspaceFactory({ db })
}),
findUserByTarget: findUserByTargetFactory(),
findUserByTarget: findUserByTargetFactory({ db }),
findInvite: findInviteFactory({
db,
filterQuery: workspaceInviteValidityFilter
}),
markInviteUpdated: markInviteUpdatedfactory({ db })
markInviteUpdated: markInviteUpdatedfactory({ db }),
getUser,
getServerInfo
})
await resendInviteEmail({
@@ -681,29 +740,19 @@ export = FF_WORKSPACES_MODULE_ENABLED
},
WorkspaceProjectMutations: {
updateRole: async (_parent, args, context) => {
const { projectId, userId, role } = args.input
const { workspaceId } = (await getStream({ streamId: projectId })) ?? {}
if (!workspaceId) {
throw new WorkspaceInvalidProjectError()
}
const currentRole = await getWorkspaceRoleForUserFactory({ db })({
workspaceId,
userId
const updateWorkspaceProjectRole = updateWorkspaceProjectRoleFactory({
getStream,
updateStreamRoleAndNotify,
getWorkspaceRoleForUser: getWorkspaceRoleForUserFactory({ db })
})
if (currentRole?.role === Roles.Workspace.Admin) {
// User is workspace admin and cannot have their project roles changed
throw new WorkspaceAdminError()
}
return await updateStreamRoleAndNotify(
{ projectId, userId, role },
context.userId!,
context.resourceAccessRules
)
return await updateWorkspaceProjectRole({
role: args.input,
updater: {
userId: context.userId!,
resourceAccessRules: context.resourceAccessRules
}
})
},
moveToWorkspace: async (_parent, args, context) => {
const { projectId, workspaceId } = args
@@ -724,10 +773,10 @@ export = FF_WORKSPACES_MODULE_ENABLED
const trx = await db.transaction()
const moveProjectToWorkspace = moveProjectToWorkspaceFactory({
getProject: getProjectFactory(),
getProject: getProjectFactory({ db }),
updateProject: updateProjectFactory({ db: trx }),
upsertProjectRole: upsertProjectRoleFactory({ db: trx }),
getProjectCollaborators: getProjectCollaboratorsFactory(),
getProjectCollaborators: getProjectCollaboratorsFactory({ db }),
getWorkspaceRoles: getWorkspaceRolesFactory({ db: trx }),
getWorkspaceRoleToDefaultProjectRoleMapping:
getWorkspaceRoleToDefaultProjectRoleMappingFactory({
@@ -265,7 +265,7 @@ export const getWorkspaceCollaboratorsFactory =
.where(DbWorkspaceAcl.col.workspaceId, workspaceId)
.orderBy('workspaceRoleCreatedAt', 'desc')
const { search, role } = filter || {}
const { search, roles } = filter || {}
if (search) {
query.andWhere((builder) => {
@@ -275,8 +275,10 @@ export const getWorkspaceCollaboratorsFactory =
})
}
if (role) {
query.andWhere(DbWorkspaceAcl.col.role, role)
if (roles) {
query.andWhere((builder) => {
builder.whereIn(DbWorkspaceAcl.col.role, roles)
})
}
if (cursor) {
@@ -36,14 +36,14 @@ import {
sessionMiddlewareFactory
} from '@/modules/auth/middleware'
import { createAuthorizationCodeFactory } from '@/modules/auth/repositories/apps'
import { getUserById } from '@/modules/core/services/users'
import { legacyGetUserFactory } from '@/modules/core/repositories/users'
const router = Router()
const sessionMiddleware = sessionMiddlewareFactory()
const finalizeAuthMiddleware = finalizeAuthMiddlewareFactory({
createAuthorizationCode: createAuthorizationCodeFactory({ db }),
getUserById
getUser: legacyGetUserFactory({ db })
})
const buildAuthRedirectUrl = (workspaceSlug: string): URL =>
@@ -144,6 +144,7 @@ router.get(
const logger = req.log.child({ workspaceSlug: req.params.workspaceSlug })
let provider: OIDCProvider | null = null
if (req.query.validate === 'true') {
const workspace = await getWorkspaceBySlugFactory({ db })({
workspaceSlug: req.params.workspaceSlug
@@ -9,7 +9,6 @@ import { getWorkspaceRoute } from '@/modules/core/helpers/routeHelper'
import { isResourceAllowed } from '@/modules/core/helpers/token'
import { UserRecord } from '@/modules/core/helpers/types'
import { removePrivateFields } from '@/modules/core/helpers/userHelper'
import { getUser } from '@/modules/core/repositories/users'
import {
ProjectInviteResourceType,
ServerInviteResourceType
@@ -71,7 +70,8 @@ import {
anyEmailCompliantWithWorkspaceDomains,
userEmailsCompliantWithWorkspaceDomains
} from '@/modules/workspaces/domain/logic'
import { getStream } from '@/modules/core/repositories/streams'
import { GetStream } from '@/modules/core/domain/streams/operations'
import { GetUser } from '@/modules/core/domain/users/operations'
const isWorkspaceResourceTarget = (
target: InviteResourceTarget
@@ -124,7 +124,7 @@ type CollectAndValidateWorkspaceTargetsFactoryDeps =
getWorkspace: GetWorkspace
getWorkspaceDomains: GetWorkspaceDomains
findVerifiedEmailsByUserId: FindVerifiedEmailsByUserId
getStream: typeof getStream
getStream: GetStream
}
export const collectAndValidateWorkspaceTargetsFactory =
@@ -381,7 +381,7 @@ function buildPendingWorkspaceCollaboratorModel(
export const getUserPendingWorkspaceInviteFactory =
(deps: {
findInvite: FindInvite
getUser: typeof getUser
getUser: GetUser
getWorkspaceBySlug: GetWorkspaceBySlug
}) =>
async (params: {
@@ -425,10 +425,7 @@ export const getUserPendingWorkspaceInviteFactory =
}
export const getUserPendingWorkspaceInvitesFactory =
(deps: {
getUserResourceInvites: QueryAllUserResourceInvites
getUser: typeof getUser
}) =>
(deps: { getUserResourceInvites: QueryAllUserResourceInvites; getUser: GetUser }) =>
async (userId: string): Promise<PendingWorkspaceCollaboratorGraphQLReturn[]> => {
if (!userId) return []
@@ -26,7 +26,6 @@ import {
validateWorkspaceSlug
} from '@speckle/shared'
import cryptoRandomString from 'crypto-random-string'
import { deleteStream } from '@/modules/core/repositories/streams'
import {
DeleteWorkspaceRole,
GetWorkspaceRoleForUser,
@@ -64,6 +63,7 @@ import { chunk, isEmpty, omit } from 'lodash'
import { userEmailsCompliantWithWorkspaceDomains } from '@/modules/workspaces/domain/logic'
import { workspaceRoles as workspaceRoleDefinitions } from '@/modules/workspaces/roles'
import { blockedDomains } from '@speckle/shared'
import { DeleteStreamRecord } from '@/modules/core/domain/streams/operations'
type WorkspaceCreateArgs = {
userId: string
@@ -278,7 +278,7 @@ export const deleteWorkspaceFactory =
deleteAllResourceInvites
}: {
deleteWorkspace: DeleteWorkspace
deleteProject: typeof deleteStream
deleteProject: DeleteStreamRecord
queryAllWorkspaceProjects: QueryAllWorkspaceProjects
deleteAllResourceInvites: DeleteAllResourceInvites
}) =>
@@ -1,15 +1,17 @@
import { StreamRecord } from '@/modules/core/helpers/types'
import { getStreams as serviceGetStreams } from '@/modules/core/services/streams'
import { getUserStreams } from '@/modules/core/repositories/streams'
import {
GetWorkspace,
GetWorkspaceRoleForUser,
GetWorkspaceRoles,
GetWorkspaceRoleToDefaultProjectRoleMapping,
QueryAllWorkspaceProjects,
UpdateWorkspaceProjectRole,
UpdateWorkspaceRole
} from '@/modules/workspaces/domain/operations'
import {
WorkspaceAdminError,
WorkspaceInvalidProjectError,
WorkspaceInvalidRoleError,
WorkspaceNotFoundError,
WorkspaceQueryError
} from '@/modules/workspaces/errors/workspace'
@@ -23,12 +25,17 @@ import { chunk } from 'lodash'
import { Roles, StreamRoles } from '@speckle/shared'
import { orderByWeight } from '@/modules/shared/domain/rolesAndScopes/logic'
import coreUserRoles from '@/modules/core/roles'
import {
GetStream,
GetUserStreamsPage,
LegacyGetStreams,
UpdateStreamRole
} from '@/modules/core/domain/streams/operations'
export const queryAllWorkspaceProjectsFactory = ({
getStreams
}: {
// TODO: Core service factory functions
getStreams: typeof serviceGetStreams
getStreams: LegacyGetStreams
}): QueryAllWorkspaceProjects =>
async function* queryAllWorkspaceProjects({
workspaceId
@@ -75,7 +82,7 @@ type GetWorkspaceProjectsReturnValue = {
}
export const getWorkspaceProjectsFactory =
({ getStreams }: { getStreams: typeof getUserStreams }) =>
({ getStreams }: { getStreams: GetUserStreamsPage }) =>
async (
args: GetWorkspaceProjectsArgs,
opts: GetWorkspaceProjectsOptions
@@ -196,3 +203,42 @@ export const getWorkspaceRoleToDefaultProjectRoleMappingFactory =
[Roles.Workspace.Admin]: Roles.Stream.Owner
}
}
export const updateWorkspaceProjectRoleFactory =
({
getStream,
getWorkspaceRoleForUser,
updateStreamRoleAndNotify
}: {
getStream: GetStream
getWorkspaceRoleForUser: GetWorkspaceRoleForUser
updateStreamRoleAndNotify: UpdateStreamRole
}): UpdateWorkspaceProjectRole =>
async ({ role, updater }) => {
const { workspaceId } = (await getStream({ streamId: role.projectId })) ?? {}
if (!workspaceId) throw new WorkspaceInvalidProjectError()
const currentWorkspaceRole = await getWorkspaceRoleForUser({
workspaceId,
userId: role.userId
})
if (currentWorkspaceRole?.role === Roles.Workspace.Admin) {
// User is workspace admin and cannot have their project roles changed
throw new WorkspaceAdminError()
}
if (
currentWorkspaceRole?.role === Roles.Workspace.Guest &&
role.role === Roles.Stream.Owner
) {
// Workspace guests cannot be project owners
throw new WorkspaceInvalidRoleError('Workspace guests cannot be project owners.')
}
return await updateStreamRoleAndNotify(
role,
updater.userId!,
updater.resourceAccessRules
)
}
@@ -1,5 +1,4 @@
import { db } from '@/db/knex'
import { getStream } from '@/modules/core/repositories/streams'
import {
findEmailsByUserIdFactory,
findVerifiedEmailsByUserIdFactory
@@ -45,6 +44,9 @@ import {
StreamRoles,
WorkspaceRoles
} from '@speckle/shared'
import { getStreamFactory } from '@/modules/core/repositories/streams'
import { getUserFactory } from '@/modules/core/repositories/users'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
export type BasicTestWorkspace = {
/**
@@ -207,8 +209,11 @@ export const createWorkspaceInviteDirectly = async (
args: CreateWorkspaceInviteMutationVariables,
inviterId: string
) => {
const getServerInfo = getServerInfoFactory({ db })
const getStream = getStreamFactory({ db })
const getUser = getUserFactory({ db })
const createAndSendInvite = createAndSendInviteFactory({
findUserByTarget: findUserByTargetFactory(),
findUserByTarget: findUserByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
collectAndValidateResourceTargets: collectAndValidateWorkspaceTargetsFactory({
getStream,
@@ -224,7 +229,9 @@ export const createWorkspaceInviteDirectly = async (
getEventBus().emit({
eventName,
payload
})
}),
getUser,
getServerInfo
})
const createInvite = createWorkspaceInviteFactory({
@@ -65,7 +65,6 @@ import {
import type { Express } from 'express'
import { AllScopes } from '@/modules/core/helpers/mainConstants'
import { getWorkspaceFactory } from '@/modules/workspaces/repositories/workspaces'
import { getStream } from '@/modules/core/repositories/streams'
import {
createUserEmailFactory,
deleteUserEmailFactory,
@@ -75,10 +74,25 @@ import {
} from '@/modules/core/repositories/userEmails'
import { markUserEmailAsVerifiedFactory } from '@/modules/core/services/users/emailVerification'
import { createRandomPassword } from '@/modules/core/helpers/testHelpers'
import { addOrUpdateStreamCollaborator } from '@/modules/core/services/streams/streamAccessService'
import { WorkspaceProtectedError } from '@/modules/workspaces/errors/workspace'
import { ForbiddenError } from '@/modules/shared/errors'
import cryptoRandomString from 'crypto-random-string'
import {
getStreamFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import { saveActivityFactory } from '@/modules/activitystream/repositories'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
import {
addStreamInviteAcceptedActivityFactory,
addStreamPermissionsAddedActivityFactory
} from '@/modules/activitystream/services/streamActivity'
import { publish } from '@/modules/shared/utils/subscriptions'
import { getUserFactory } from '@/modules/core/repositories/users'
enum InviteByTarget {
Email = 'email',
@@ -87,6 +101,25 @@ enum InviteByTarget {
type TestGraphQLOperations = ReturnType<typeof buildGraphqlOperations>
const getStream = getStreamFactory({ db })
const saveActivity = saveActivityFactory({ db })
const validateStreamAccess = validateStreamAccessFactory({ authorizeResolver })
const getUser = getUserFactory({ db })
const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({
validateStreamAccess,
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({
saveActivity,
publish
}),
addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({
saveActivity,
publish
})
})
const buildGraphqlOperations = (deps: { apollo: TestApolloServer }) => {
const { apollo } = deps
@@ -1,5 +1,6 @@
import { db } from '@/db/knex'
import { AllScopes } from '@/modules/core/helpers/mainConstants'
import { grantStreamPermissions } from '@/modules/core/repositories/streams'
import { grantStreamPermissionsFactory } from '@/modules/core/repositories/streams'
import {
BasicTestWorkspace,
createTestWorkspace
@@ -27,6 +28,8 @@ import { Roles } from '@speckle/shared'
import { expect } from 'chai'
import cryptoRandomString from 'crypto-random-string'
const grantStreamPermissions = grantStreamPermissionsFactory({ db })
describe('Workspace project GQL CRUD', () => {
let apollo: TestApolloServer
@@ -38,7 +38,7 @@ import {
import { truncateTables } from '@/test/hooks'
import { createTestStream } from '@/test/speckle-helpers/streamHelper'
import {
grantStreamPermissions,
grantStreamPermissionsFactory,
upsertProjectRoleFactory
} from '@/modules/core/repositories/streams'
import { omit } from 'lodash'
@@ -58,6 +58,7 @@ const createUserEmail = createUserEmailFactory({ db })
const updateUserEmail = updateUserEmailFactory({ db })
const getUserDiscoverableWorkspaces = getUserDiscoverableWorkspacesFactory({ db })
const upsertProjectRole = upsertProjectRoleFactory({ db })
const grantStreamPermissions = grantStreamPermissionsFactory({ db })
const upsertWorkspace = upsertWorkspaceFactory({ db })
const createAndStoreTestWorkspace = createAndStoreTestWorkspaceFactory({
@@ -1,5 +1,6 @@
import { db } from '@/db/knex'
import { AllScopes } from '@/modules/core/helpers/mainConstants'
import { grantStreamPermissions } from '@/modules/core/repositories/streams'
import { grantStreamPermissionsFactory } from '@/modules/core/repositories/streams'
import {
assignToWorkspace,
BasicTestWorkspace,
@@ -29,6 +30,8 @@ import { expect } from 'chai'
import cryptoRandomString from 'crypto-random-string'
import { isUndefined } from 'lodash'
const grantStreamPermissions = grantStreamPermissionsFactory({ db })
describe('Workspaces Roles GQL', () => {
let apollo: TestApolloServer
@@ -49,8 +49,10 @@ import {
createRandomString
} from '@/modules/core/helpers/testHelpers'
import { getBranchesByStreamId } from '@/modules/core/services/branches'
import { grantStreamPermissions } from '@/modules/core/repositories/streams'
import { getWorkspaceFactory } from '@/modules/workspaces/repositories/workspaces'
import { grantStreamPermissionsFactory } from '@/modules/core/repositories/streams'
const grantStreamPermissions = grantStreamPermissionsFactory({ db })
const createProjectWithVersions =
({ apollo }: { apollo: TestApolloServer }) =>
@@ -325,11 +327,11 @@ describe('Workspaces GQL CRUD', () => {
expect(res.data?.workspace.team.items[0].user.name).to.equal('John C Speckle')
})
it('should respect role filters', async () => {
it('should respect role filters with one value', async () => {
const res = await largeWorkspaceApollo.execute(GetWorkspaceTeamDocument, {
workspaceId: largeWorkspace.id,
filter: {
role: 'workspace:member'
roles: ['workspace:member']
}
})
@@ -337,6 +339,18 @@ describe('Workspaces GQL CRUD', () => {
expect(res.data?.workspace.team.items.length).to.equal(2)
})
it('should respect role filters with multiple values', async () => {
const res = await largeWorkspaceApollo.execute(GetWorkspaceTeamDocument, {
workspaceId: largeWorkspace.id,
filter: {
roles: ['workspace:admin', 'workspace:member']
}
})
expect(res).to.not.haveGraphQLErrors()
expect(res.data?.workspace.team.items.length).to.equal(4)
})
it('should respect search limits', async () => {
const res = await largeWorkspaceApollo.execute(GetWorkspaceTeamDocument, {
workspaceId: largeWorkspace.id,
@@ -1,13 +1,15 @@
import { ProjectTeamMember } from '@/modules/core/domain/projects/types'
import { Stream } from '@/modules/core/domain/streams/types'
import { StreamAclRecord, StreamRecord } from '@/modules/core/helpers/types'
import {
moveProjectToWorkspaceFactory,
queryAllWorkspaceProjectsFactory
queryAllWorkspaceProjectsFactory,
updateWorkspaceProjectRoleFactory
} from '@/modules/workspaces/services/projects'
import { WorkspaceAcl } from '@/modules/workspacesCore/domain/types'
import { expectToThrow } from '@/test/assertionHelper'
import { Roles } from '@speckle/shared'
import { expect } from 'chai'
import { assert, expect } from 'chai'
import cryptoRandomString from 'crypto-random-string'
const getWorkspaceRoleToDefaultProjectRoleMapping = async () => ({
@@ -393,4 +395,33 @@ describe('Project management services', () => {
expect(updatedRoles[0].role).to.equal(Roles.Stream.Owner)
})
})
describe('updateWorkspaceProjectRoleFactory returns a function, that', () => {
it('should throw when attempting to promote a workspace guest to project owner', async () => {
const workspaceId = cryptoRandomString({ length: 9 })
const projectId = cryptoRandomString({ length: 9 })
const userId = cryptoRandomString({ length: 9 })
const updateWorkspaceProjectRole = updateWorkspaceProjectRoleFactory({
getStream: async () => {
return { workspaceId } as Stream
},
getWorkspaceRoleForUser: async () => ({
workspaceId,
userId,
role: Roles.Workspace.Guest,
createdAt: new Date()
}),
updateStreamRoleAndNotify: async () => {
assert.fail()
}
})
await expectToThrow(() =>
updateWorkspaceProjectRole({
role: { userId, projectId, role: Roles.Stream.Owner },
updater: { userId: '', resourceAccessRules: [] }
})
)
})
})
})