feat(server): workspace project invite auto-accept (for existing wp members) (#4622)

* WIP ts

* DI fix & tests moved to TS

* auto-accept seems to work

* CR comments
This commit is contained in:
Kristaps Fabians Geikins
2025-04-30 17:39:07 +03:00
committed by GitHub
parent e3a09a932d
commit c6dcf18bdb
41 changed files with 2954 additions and 1191 deletions
@@ -49,7 +49,7 @@ const addStreamInviteAcceptedActivityFactory =
getProjectInviteProject: GetProjectInviteProject
}) =>
async (payload: EventPayload<typeof ServerInvitesEvents.Finalized>) => {
const { invite } = payload.payload
const { invite, trueFinalizerUserId } = payload.payload
const project = await deps.getProjectInviteProject({ invite })
if (!project) return
@@ -58,14 +58,18 @@ const addStreamInviteAcceptedActivityFactory =
getResourceTypeRole(invite.resource, ProjectInviteResourceType) ||
Roles.Stream.Contributor
const differentFinalizer = trueFinalizerUserId !== userTarget.userId
await deps.saveActivity({
streamId: project.id,
resourceType: ResourceTypes.Stream,
resourceId: project.id,
actionType: ActionTypes.Stream.InviteAccepted,
userId: userTarget.userId!,
info: { inviterUser: invite.inviterId, role },
message: `User ${userTarget.userId!} has accepted an invitation to become a ${role}`
userId: trueFinalizerUserId,
info: { inviterUser: invite.inviterId, role, targetUserId: userTarget.userId! },
message: differentFinalizer
? `User ${trueFinalizerUserId} has auto-accepted ${userTarget.userId!} invitation to become a ${role}`
: `User ${userTarget.userId!} has accepted an invitation to become a ${role}`
})
}
@@ -20,7 +20,8 @@ import { db } from '@/db/knex'
import {
createStreamFactory,
deleteStreamFactory,
getStreamFactory
getStreamFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import {
createStreamReturnRecordFactory,
@@ -29,8 +30,12 @@ import {
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import {
deleteInvitesByTargetFactory,
deleteServerOnlyInvitesFactory,
findInviteFactory,
findUserByTargetFactory,
insertInviteAndDeleteOldFactory
insertInviteAndDeleteOldFactory,
updateAllInviteTargetsFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
@@ -38,6 +43,29 @@ import { getEventBus } from '@/modules/shared/services/eventBus'
import { createBranchFactory } from '@/modules/core/repositories/branches'
import { getUserFactory, getUsersFactory } from '@/modules/core/repositories/users'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
import {
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory,
findEmailFactory
} from '@/modules/core/repositories/userEmails'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
const cleanup = async () => {
await truncateTables([StreamActivity.name, Users.name])
@@ -53,6 +81,52 @@ const createActivitySummary = createActivitySummaryFactory({
getActivity: getActivityFactory({ db }),
getUser
})
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -71,7 +145,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -12,7 +12,8 @@ import {
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory
updateAllInviteTargetsFactory,
deleteInvitesByTargetFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { db } from '@/db/knex'
import {
@@ -24,7 +25,8 @@ import { createAndSendInviteFactory } from '@/modules/serverinvites/services/cre
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import {
getStreamFactory,
createStreamFactory
createStreamFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
import { getEventBus } from '@/modules/shared/services/eventBus'
@@ -48,7 +50,10 @@ import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import {
getServerInfoFactory,
updateServerInfoFactory
@@ -57,6 +62,15 @@ import { temporarilyEnableRateLimiter } from '@/modules/core/tests/ratelimiter.s
import { passportAuthenticationCallbackFactory } from '@/modules/auth/services/passportService'
import { testLogger as logger } from '@/observability/logging'
import { Application } from 'express'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
@@ -64,6 +78,52 @@ const getUsers = getUsersFactory({ db })
const createInviteDirectly = createStreamInviteDirectly
const findInvite = findInviteFactory({ db })
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -82,7 +142,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -205,7 +266,7 @@ describe('Auth @auth', () => {
}@speckle.systems`
const inviterUser = await getUserByEmail({ email: registeredUserEmail })
const { token, inviteId } = await createInviteDirectly(
const { token, id: inviteId } = await createInviteDirectly(
streamInvite
? {
email: targetEmail,
@@ -1,193 +0,0 @@
const { buildApolloServer } = require('@/app')
const { truncateTables } = require('@/test/hooks')
const { gql } = require('graphql-tag')
const { createBlobs } = require('@/modules/blobstorage/tests/helpers')
const { expect } = require('chai')
const { Users, Streams } = require('@/modules/core/dbSchema')
const { createAuthedTestContext, executeOperation } = require('@/test/graphqlHelper')
const {
getStreamFactory,
createStreamFactory
} = require('@/modules/core/repositories/streams')
const { db } = require('@/db/knex')
const {
legacyCreateStreamFactory,
createStreamReturnRecordFactory
} = require('@/modules/core/services/streams/management')
const {
inviteUsersToProjectFactory
} = require('@/modules/serverinvites/services/projectInviteManagement')
const {
createAndSendInviteFactory
} = require('@/modules/serverinvites/services/creation')
const {
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory
} = require('@/modules/serverinvites/repositories/serverInvites')
const {
collectAndValidateCoreTargetsFactory
} = require('@/modules/serverinvites/services/coreResourceCollection')
const {
buildCoreInviteEmailContentsFactory
} = require('@/modules/serverinvites/services/coreEmailContents')
const { getEventBus } = require('@/modules/shared/services/eventBus')
const { createBranchFactory } = require('@/modules/core/repositories/branches')
const {
getUsersFactory,
getUserFactory,
storeUserFactory,
countAdminUsersFactory,
storeUserAclFactory
} = require('@/modules/core/repositories/users')
const {
findEmailFactory,
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory
} = require('@/modules/core/repositories/userEmails')
const {
requestNewEmailVerificationFactory
} = require('@/modules/emails/services/verification/request')
const {
deleteOldAndInsertNewVerificationFactory
} = require('@/modules/emails/repositories')
const { renderEmail } = require('@/modules/emails/services/emailRendering')
const { sendEmail } = require('@/modules/emails/services/sending')
const { createUserFactory } = require('@/modules/core/services/users/management')
const {
validateAndCreateUserEmailFactory
} = require('@/modules/core/services/userEmails')
const {
finalizeInvitedServerRegistrationFactory
} = require('@/modules/serverinvites/services/processing')
const { getServerInfoFactory } = require('@/modules/core/repositories/server')
const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
const getUsers = getUsersFactory({ db })
const getStream = getStreamFactory({ db })
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
createAndSendInvite: createAndSendInviteFactory({
findUserByTarget: findUserByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
buildInviteEmailContents: buildCoreInviteEmailContentsFactory({
getStream
}),
emitEvent: ({ eventName, payload }) =>
getEventBus().emit({
eventName,
payload
}),
getUser,
getServerInfo
}),
getUsers
}),
createStream: createStreamFactory({ db }),
createBranch: createBranchFactory({ db }),
emitEvent: getEventBus().emit
})
})
const findEmail = findEmailFactory({ db })
const requestNewEmailVerification = requestNewEmailVerificationFactory({
findEmail,
getUser: getUserFactory({ db }),
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
renderEmail,
sendEmail
})
const createUser = createUserFactory({
getServerInfo,
findEmail,
storeUser: storeUserFactory({ db }),
countAdminUsers: countAdminUsersFactory({ db }),
storeUserAcl: storeUserAclFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail,
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification
}),
emitEvent: getEventBus().emit
})
describe('Blobs graphql @blobstorage', () => {
/** @type {import('@/test/graphqlHelper').ServerAndContext} */
let graphqlServer
const user = {
name: 'Baron Von Blubba',
email: 'zebarron@bubble.bobble',
password: 'bubblesAreMyBlobs'
}
before(async () => {
await truncateTables(['blob_storage', Users.name, Streams.name])
user.id = await createUser(user)
graphqlServer = {
apollo: await buildApolloServer(),
context: await createAuthedTestContext(user.id)
}
})
it('Stream has blob metadata for a single blob', async () => {
const query = gql`
query ($streamId: String!, $blobId: String!) {
stream(id: $streamId) {
id
blob(id: $blobId) {
id
fileName
uploadStatus
fileSize
fileHash
}
}
}
`
const streamId = await createStream({ ownerId: user.id })
const [blob] = await createBlobs({ streamId, number: 1 })
const result = await executeOperation(graphqlServer, query, {
streamId,
blobId: blob.id
})
const blobMetadata = result.data.stream.blob
expect(blobMetadata.id).to.equal(blob.id)
expect(blobMetadata.fileSize).to.equal(blob.fileSize)
expect(blobMetadata.fileHash).to.equal(blob.fileHash)
})
it('Blob metadata collection returns proper summary values', async () => {
const query = gql`
query ($streamId: String!) {
stream(id: $streamId) {
id
blobs {
totalCount
totalSize
}
}
}
`
const streamId = await createStream({ ownerId: user.id })
const number = 10
const fileSize = 123
await createBlobs({ streamId, number, fileSize })
const result = await executeOperation(graphqlServer, query, { streamId })
expect(result.data.stream.blobs.totalCount).to.equal(number)
expect(result.data.stream.blobs.totalSize).to.equal(number * fileSize)
})
})
@@ -0,0 +1,244 @@
import { buildApolloServer } from '@/app'
import { truncateTables } from '@/test/hooks'
import gql from 'graphql-tag'
import { createBlobs } from '@/modules/blobstorage/tests/helpers'
import { expect } from 'chai'
import { Users, Streams } from '@/modules/core/dbSchema'
import {
createAuthedTestContext,
executeOperation,
ServerAndContext
} from '@/test/graphqlHelper'
import {
getStreamFactory,
createStreamFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import { db } from '@/db/knex'
import {
legacyCreateStreamFactory,
createStreamReturnRecordFactory
} from '@/modules/core/services/streams/management'
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import {
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory,
findInviteFactory,
deleteInvitesByTargetFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { createBranchFactory } from '@/modules/core/repositories/branches'
import {
getUsersFactory,
getUserFactory,
storeUserFactory,
countAdminUsersFactory,
storeUserAclFactory
} from '@/modules/core/repositories/users'
import {
findEmailFactory,
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory
} from '@/modules/core/repositories/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
const getUsers = getUsersFactory({ db })
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
createAndSendInvite: createAndSendInviteFactory({
findUserByTarget: findUserByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
buildInviteEmailContents: buildCoreInviteEmailContentsFactory({
getStream
}),
emitEvent: ({ eventName, payload }) =>
getEventBus().emit({
eventName,
payload
}),
getUser,
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
createStream: createStreamFactory({ db }),
createBranch: createBranchFactory({ db }),
emitEvent: getEventBus().emit
})
})
const findEmail = findEmailFactory({ db })
const requestNewEmailVerification = requestNewEmailVerificationFactory({
findEmail,
getUser: getUserFactory({ db }),
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
renderEmail,
sendEmail
})
const createUser = createUserFactory({
getServerInfo,
findEmail,
storeUser: storeUserFactory({ db }),
countAdminUsers: countAdminUsersFactory({ db }),
storeUserAcl: storeUserAclFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail,
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification
}),
emitEvent: getEventBus().emit
})
describe('Blobs graphql @blobstorage', () => {
let graphqlServer: ServerAndContext
const user = {
name: 'Baron Von Blubba',
email: 'zebarron@bubble.bobble',
password: 'bubblesAreMyBlobs',
id: ''
}
before(async () => {
await truncateTables(['blob_storage', Users.name, Streams.name])
user.id = await createUser(user)
graphqlServer = {
apollo: await buildApolloServer(),
context: await createAuthedTestContext(user.id)
}
})
it('Stream has blob metadata for a single blob', async () => {
const query = gql`
query ($streamId: String!, $blobId: String!) {
stream(id: $streamId) {
id
blob(id: $blobId) {
id
fileName
uploadStatus
fileSize
fileHash
}
}
}
`
const streamId = await createStream({ ownerId: user.id })
const [blob] = await createBlobs({ streamId, number: 1 })
const result = await executeOperation(graphqlServer, query, {
streamId,
blobId: blob.id
})
const blobMetadata = result.data!.stream.blob
expect(blobMetadata.id).to.equal(blob.id)
expect(blobMetadata.fileSize).to.equal(blob.fileSize)
expect(blobMetadata.fileHash).to.equal(blob.fileHash)
})
it('Blob metadata collection returns proper summary values', async () => {
const query = gql`
query ($streamId: String!) {
stream(id: $streamId) {
id
blobs {
totalCount
totalSize
}
}
}
`
const streamId = await createStream({ ownerId: user.id })
const number = 10
const fileSize = 123
await createBlobs({ streamId, number, fileSize })
const result = await executeOperation(graphqlServer, query, { streamId })
expect(result.data!.stream.blobs.totalCount).to.equal(number)
expect(result.data!.stream.blobs.totalSize).to.equal(number * fileSize)
})
})
@@ -1,120 +1,117 @@
const expect = require('chai').expect
import { expect } from 'chai'
const crs = require('crypto-random-string')
const { buildApolloServer } = require('@/app')
const { beforeEachContext } = require('@/test/hooks')
const { Roles } = require('@/modules/core/helpers/mainConstants')
const { gql } = require('graphql-tag')
const {
convertBasicStringToDocument
} = require('@/modules/core/services/richTextEditorService')
const {
import crs from 'crypto-random-string'
import { buildApolloServer } from '@/app'
import { beforeEachContext } from '@/test/hooks'
import { Roles } from '@/modules/core/helpers/mainConstants'
import gql from 'graphql-tag'
import { convertBasicStringToDocument } from '@/modules/core/services/richTextEditorService'
import {
createTestContext,
createAuthedTestContext,
executeOperation
} = require('@/test/graphqlHelper')
const {
executeOperation,
ServerAndContext,
ExecuteOperationResponse
} from '@/test/graphqlHelper'
import {
streamResourceCheckFactory,
createCommentFactory
} = require('@/modules/comments/services')
const {
} from '@/modules/comments/services'
import {
checkStreamResourceAccessFactory,
markCommentViewedFactory,
insertCommentsFactory,
insertCommentLinksFactory,
deleteCommentFactory,
getCommentsResourcesFactory
} = require('@/modules/comments/repositories/comments')
const { db } = require('@/db/knex')
const {
validateInputAttachmentsFactory
} = require('@/modules/comments/services/commentTextService')
const { getBlobsFactory } = require('@/modules/blobstorage/repositories')
const {
} from '@/modules/comments/repositories/comments'
import { db } from '@/db/knex'
import { validateInputAttachmentsFactory } from '@/modules/comments/services/commentTextService'
import { getBlobsFactory } from '@/modules/blobstorage/repositories'
import {
createCommitByBranchIdFactory,
createCommitByBranchNameFactory
} = require('@/modules/core/services/commit/management')
const {
} from '@/modules/core/services/commit/management'
import {
createCommitFactory,
insertStreamCommitsFactory,
insertBranchCommitsFactory,
getCommitsAndTheirBranchIdsFactory
} = require('@/modules/core/repositories/commits')
const {
} from '@/modules/core/repositories/commits'
import {
getBranchByIdFactory,
markCommitBranchUpdatedFactory,
getStreamBranchByNameFactory,
createBranchFactory
} = require('@/modules/core/repositories/branches')
const {
} from '@/modules/core/repositories/branches'
import {
getStreamFactory,
createStreamFactory,
updateStreamFactory,
grantStreamPermissionsFactory,
markCommitStreamUpdatedFactory
} = require('@/modules/core/repositories/streams')
const {
} from '@/modules/core/repositories/streams'
import {
getObjectFactory,
storeSingleObjectIfNotFoundFactory,
getStreamObjectsFactory
} = require('@/modules/core/repositories/objects')
const {
} from '@/modules/core/repositories/objects'
import {
legacyCreateStreamFactory,
createStreamReturnRecordFactory,
legacyUpdateStreamFactory
} = require('@/modules/core/services/streams/management')
const {
inviteUsersToProjectFactory
} = require('@/modules/serverinvites/services/projectInviteManagement')
const {
createAndSendInviteFactory
} = require('@/modules/serverinvites/services/creation')
const {
} from '@/modules/core/services/streams/management'
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import {
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory
} = require('@/modules/serverinvites/repositories/serverInvites')
const {
collectAndValidateCoreTargetsFactory
} = require('@/modules/serverinvites/services/coreResourceCollection')
const {
buildCoreInviteEmailContentsFactory
} = require('@/modules/serverinvites/services/coreEmailContents')
const { getEventBus } = require('@/modules/shared/services/eventBus')
const {
updateAllInviteTargetsFactory,
findInviteFactory,
deleteInvitesByTargetFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
import { getEventBus } from '@/modules/shared/services/eventBus'
import {
getUsersFactory,
getUserFactory,
storeUserFactory,
countAdminUsersFactory,
storeUserAclFactory
} = require('@/modules/core/repositories/users')
const {
} from '@/modules/core/repositories/users'
import {
findEmailFactory,
ensureNoPrimaryEmailForUserFactory,
createUserEmailFactory
} = require('@/modules/core/repositories/userEmails')
const {
requestNewEmailVerificationFactory
} = require('@/modules/emails/services/verification/request')
const {
deleteOldAndInsertNewVerificationFactory
} = require('@/modules/emails/repositories')
const { renderEmail } = require('@/modules/emails/services/emailRendering')
const { sendEmail } = require('@/modules/emails/services/sending')
const { createUserFactory } = require('@/modules/core/services/users/management')
const {
validateAndCreateUserEmailFactory
} = require('@/modules/core/services/userEmails')
const {
finalizeInvitedServerRegistrationFactory
} = require('@/modules/serverinvites/services/processing')
const { getServerInfoFactory } = require('@/modules/core/repositories/server')
const { createObjectFactory } = require('@/modules/core/services/objects/management')
const {
} from '@/modules/core/repositories/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import { createObjectFactory } from '@/modules/core/services/objects/management'
import {
getViewerResourcesFromLegacyIdentifiersFactory,
getViewerResourcesForCommentsFactory
} = require('@/modules/core/services/commit/viewerResources')
} from '@/modules/core/services/commit/viewerResources'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
import { SetNonNullable } from 'type-fest'
const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
@@ -168,6 +165,51 @@ const createCommitByBranchName = createCommitByBranchNameFactory({
})
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -186,7 +228,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -232,45 +275,54 @@ const createObject = createObjectFactory({
storeSingleObjectIfNotFoundFactory: storeSingleObjectIfNotFoundFactory({ db })
})
function buildCommentInputFromString(textString) {
function buildCommentInputFromString(textString: string) {
return convertBasicStringToDocument(textString)
}
const testForbiddenResponse = (result) => {
const testForbiddenResponse = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result: ExecuteOperationResponse<Record<string, any>>
) => {
expect(result.errors, 'This should have failed').to.exist
expect(result.errors.length).to.be.above(0)
expect(result.errors[0].extensions.code).to.match(
expect(result.errors!.length).to.be.above(0)
expect(result.errors![0].extensions!.code).to.match(
/(STREAM_INVALID_ACCESS_ERROR|FORBIDDEN|UNAUTHORIZED_ACCESS_ERROR)/
)
}
const testResult = (shouldSucceed, result, successTests) => {
const testResult = (
shouldSucceed: boolean,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result: ExecuteOperationResponse<Record<string, any>>,
successTests: (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result: SetNonNullable<ExecuteOperationResponse<Record<string, any>>, 'data'>
) => void
) => {
if (shouldSucceed) {
expect(result.errors, 'This should not have failed').to.not.exist
successTests(result)
successTests(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result as SetNonNullable<ExecuteOperationResponse<Record<string, any>>, 'data'>
)
} else {
testForbiddenResponse(result)
}
}
/**
* @typedef {{
* apollo: import('@/test/graphqlHelper').ServerAndContext,
* resources: {
* streamId: string,
* objectId: string,
* commentId: string,
* testActorId: string
* },
* shouldSucceed: boolean,
* streamId: string
* }} TestContext
*/
type TestContext = {
apollo: ServerAndContext
resources: {
streamId: string
objectId: string
commentId: string
testActorId: string
}
shouldSucceed: boolean
streamId: string
}
/**
* @param {TestContext} param0
*/
const writeComment = async ({ apollo, resources, shouldSucceed }) => {
const writeComment = async ({ apollo, resources, shouldSucceed }: TestContext) => {
const res = await executeOperation(
apollo,
gql`
@@ -294,10 +346,11 @@ const writeComment = async ({ apollo, resources, shouldSucceed }) => {
})
}
/**
* @param {TestContext} param0
*/
const broadcastViewerActivity = async ({ apollo, resources, shouldSucceed }) => {
const broadcastViewerActivity = async ({
apollo,
resources,
shouldSucceed
}: TestContext) => {
const res = await executeOperation(
apollo,
gql`
@@ -320,10 +373,11 @@ const broadcastViewerActivity = async ({ apollo, resources, shouldSucceed }) =>
})
}
/**
* @param {TestContext} param0
*/
const broadcastCommentActivity = async ({ apollo, resources, shouldSucceed }) => {
const broadcastCommentActivity = async ({
apollo,
resources,
shouldSucceed
}: TestContext) => {
const res = await executeOperation(
apollo,
gql`
@@ -346,10 +400,7 @@ const broadcastCommentActivity = async ({ apollo, resources, shouldSucceed }) =>
})
}
/**
* @param {TestContext} param0
*/
const viewAComment = async ({ apollo, resources, shouldSucceed }) => {
const viewAComment = async ({ apollo, resources, shouldSucceed }: TestContext) => {
const res = await executeOperation(
apollo,
gql`
@@ -367,13 +418,10 @@ const viewAComment = async ({ apollo, resources, shouldSucceed }) => {
})
}
/**
* @param {TestContext} param0
*/
const archiveMyComment = async ({ apollo, resources, shouldSucceed }) => {
const archiveMyComment = async ({ apollo, resources, shouldSucceed }: TestContext) => {
const context = apollo.context
const { id: commentId } = await createComment({
userId: context.userId,
userId: context!.userId!,
input: {
streamId: resources.streamId,
text: buildCommentInputFromString('i wrote this myself'),
@@ -399,10 +447,11 @@ const archiveMyComment = async ({ apollo, resources, shouldSucceed }) => {
})
}
/**
* @param {TestContext} param0
*/
const archiveOthersComment = async ({ apollo, resources, shouldSucceed }) => {
const archiveOthersComment = async ({
apollo,
resources,
shouldSucceed
}: TestContext) => {
const res = await executeOperation(
apollo,
gql`
@@ -420,12 +469,9 @@ const archiveOthersComment = async ({ apollo, resources, shouldSucceed }) => {
})
}
/**
* @param {TestContext} param0
*/
const editMyComment = async ({ apollo, resources, shouldSucceed }) => {
const editMyComment = async ({ apollo, resources, shouldSucceed }: TestContext) => {
const { id: commentId } = await createComment({
userId: apollo.context.userId,
userId: apollo.context!.userId!,
input: {
streamId: resources.streamId,
text: buildCommentInputFromString('i wrote this myself'),
@@ -458,10 +504,7 @@ const editMyComment = async ({ apollo, resources, shouldSucceed }) => {
})
}
/**
* @param {TestContext} param0
*/
const editOthersComment = async ({ apollo, resources, shouldSucceed }) => {
const editOthersComment = async ({ apollo, resources, shouldSucceed }: TestContext) => {
const res = await executeOperation(
apollo,
gql`
@@ -485,10 +528,7 @@ const editOthersComment = async ({ apollo, resources, shouldSucceed }) => {
})
}
/**
* @param {TestContext} param0
*/
const replyToAComment = async ({ apollo, resources, shouldSucceed }) => {
const replyToAComment = async ({ apollo, resources, shouldSucceed }: TestContext) => {
const res = await executeOperation(
apollo,
gql`
@@ -514,10 +554,7 @@ const replyToAComment = async ({ apollo, resources, shouldSucceed }) => {
})
}
/**
* @param {TestContext} param0
*/
const queryComment = async ({ apollo, resources, shouldSucceed }) => {
const queryComment = async ({ apollo, resources, shouldSucceed }: TestContext) => {
const res = await executeOperation(
apollo,
gql`
@@ -547,10 +584,7 @@ const queryComment = async ({ apollo, resources, shouldSucceed }) => {
})
}
/**
* @param {TestContext} param0
*/
const queryComments = async ({ apollo, resources, shouldSucceed }) => {
const queryComments = async ({ apollo, resources, shouldSucceed }: TestContext) => {
const object = {
foo: 123,
bar: crs({ length: 5 })
@@ -599,14 +633,17 @@ const queryComments = async ({ apollo, resources, shouldSucceed }) => {
)
testResult(shouldSucceed, res, (res) => {
expect(res.data.comments.totalCount).to.be.equal(numberOfComments)
expect(res.data.comments.items.map((i) => i.id)).to.be.equalInAnyOrder(commentIds)
expect(
res.data.comments.items.map((i: { id: string }) => i.id)
).to.deep.equalInAnyOrder(commentIds)
})
}
/**
* @param {TestContext} param0
*/
const queryStreamCommentCount = async ({ apollo, resources, shouldSucceed }) => {
const queryStreamCommentCount = async ({
apollo,
resources,
shouldSucceed
}: TestContext) => {
await createComment({
userId: resources.testActorId,
input: {
@@ -635,10 +672,11 @@ const queryStreamCommentCount = async ({ apollo, resources, shouldSucceed }) =>
})
}
/**
* @param {TestContext} param0
*/
const queryObjectCommentCount = async ({ apollo, resources, shouldSucceed }) => {
const queryObjectCommentCount = async ({
apollo,
resources,
shouldSucceed
}: TestContext) => {
const objectId = await createObject({
streamId: resources.streamId,
object: {
@@ -675,10 +713,11 @@ const queryObjectCommentCount = async ({ apollo, resources, shouldSucceed }) =>
})
}
/**
* @param {TestContext} param0
*/
const queryCommitCommentCount = async ({ apollo, resources, shouldSucceed }) => {
const queryCommitCommentCount = async ({
apollo,
resources,
shouldSucceed
}: TestContext) => {
const objectId = await createObject({
streamId: resources.streamId,
object: {
@@ -722,14 +761,11 @@ const queryCommitCommentCount = async ({ apollo, resources, shouldSucceed }) =>
})
}
/**
* @param {TestContext} param0
*/
const queryCommitCollectionCommentCount = async ({
apollo,
resources,
shouldSucceed
}) => {
}: TestContext) => {
const objectId = await createObject({
streamId: resources.streamId,
object: {
@@ -772,16 +808,13 @@ const queryCommitCollectionCommentCount = async ({
)
testResult(shouldSucceed, res, (res) => {
res.data.otherUser.commits.items
.map((i) => i.commentCount)
.map((commentCount) => {
.map((i: { commentCount: number }) => i.commentCount)
.map((commentCount: number) => {
expect(commentCount).to.be.greaterThanOrEqual(1)
})
})
}
// eslint-disable-next-line no-unused-vars
const actions = ['queryCommitCommentCount', 'queryCommitCollectionCommentCount']
describe('Graphql @comments', () => {
// this user will be admin by default
// it will be used to create all resources, that the other actors can
@@ -789,59 +822,69 @@ describe('Graphql @comments', () => {
const myTestActor = {
name: 'Gergo Jedlicska',
email: 'gergo@jedlicska.com',
password: 'sn3aky-1337-b1m'
password: 'sn3aky-1337-b1m',
id: ''
}
const chadTheEngineer = {
name: 'Chad the Engineer',
email: 'chad@engineering.acme',
password: 'tryingNotToBeACadMonkey',
role: Roles.Server.User
role: Roles.Server.User,
id: ''
}
const archived = {
name: 'The Balrog of Morgoth',
email: 'durinsbane@moria.bridge',
role: Roles.Server.ArchivedUser
password: 'tryingNotToBeACadMonkey',
role: Roles.Server.ArchivedUser,
id: ''
}
const ownedStream = {
name: 'stream owner',
isPublic: false,
role: Roles.Stream.Owner
role: Roles.Stream.Owner,
id: ''
}
const contributorStream = {
name: 'contributions are welcome',
isPublic: false,
role: Roles.Stream.Contributor
role: Roles.Stream.Contributor,
id: ''
}
const reviewerStream = {
name: 'no work, just talk',
isPublic: false,
role: Roles.Stream.Reviewer
role: Roles.Stream.Reviewer,
id: ''
}
const noAccessStream = {
name: 'aint nobody canna cross it',
isPublic: false,
role: null
role: null,
id: ''
}
const publicStream = {
name: 'come take a look',
isPublic: true,
role: null
role: null,
id: ''
}
const publicStreamWithPublicComments = {
name: 'the gossip protocol',
isPublic: true,
role: null
role: null,
id: ''
}
const testData = [
const testData = <const>[
{
user: chadTheEngineer,
streamData: [
@@ -1116,11 +1159,8 @@ describe('Graphql @comments', () => {
}`, () => {
userContext.streamData.forEach((streamContext) => {
const stream = streamContext.stream
let resources
/**
* @type {import('@/test/graphqlHelper').ServerAndContext}
*/
let apollo
let resources: TestContext['resources']
let apollo: ServerAndContext
before(async () => {
apollo = {
@@ -55,7 +55,8 @@ import { getBlobsFactory } from '@/modules/blobstorage/repositories'
import {
getStreamFactory,
createStreamFactory,
markCommitStreamUpdatedFactory
markCommitStreamUpdatedFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import {
createCommitByBranchIdFactory,
@@ -88,7 +89,9 @@ import {
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory
updateAllInviteTargetsFactory,
findInviteFactory,
deleteInvitesByTargetFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
@@ -111,7 +114,10 @@ import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import { createObjectFactory } from '@/modules/core/services/objects/management'
import type express from 'express'
@@ -130,6 +136,15 @@ import {
getViewerResourcesFromLegacyIdentifiersFactory
} from '@/modules/core/services/commit/viewerResources'
import { StreamRecord } from '@/modules/core/helpers/types'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
type LegacyCommentRecord = CommentRecord & {
total_count: string
@@ -223,6 +238,51 @@ const createCommitByBranchName = createCommitByBranchNameFactory({
getBranchById: getBranchByIdFactory({ db })
})
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -241,7 +301,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -77,8 +77,12 @@ import {
} from '@/modules/multiregion/utils/dbSelector'
import {
deleteAllResourceInvitesFactory,
deleteInvitesByTargetFactory,
deleteServerOnlyInvitesFactory,
findInviteFactory,
findUserByTargetFactory,
insertInviteAndDeleteOldFactory
insertInviteAndDeleteOldFactory,
updateAllInviteTargetsFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
@@ -94,11 +98,75 @@ import {
import { has } from 'lodash'
import { throwIfAuthNotOk } from '@/modules/shared/helpers/errorHelper'
import { withOperationLogging } from '@/observability/domain/businessLogging'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory,
findEmailFactory
} from '@/modules/core/repositories/userEmails'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
const getServerInfo = getServerInfoFactory({ db })
const getUsers = getUsersFactory({ db })
const getUser = getUserFactory({ db })
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStreamReturnRecord = createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
createAndSendInvite: createAndSendInviteFactory({
@@ -116,7 +184,8 @@ const createStreamReturnRecord = createStreamReturnRecordFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -48,9 +48,13 @@ import {
} from '@/modules/core/graph/generated/graphql'
import {
deleteAllResourceInvitesFactory,
deleteInvitesByTargetFactory,
deleteServerOnlyInvitesFactory,
findInviteFactory,
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
queryAllResourceInvitesFactory
queryAllResourceInvitesFactory,
updateAllInviteTargetsFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import db from '@/db/knex'
import { getInvitationTargetUsersFactory } from '@/modules/serverinvites/services/retrieval'
@@ -75,6 +79,24 @@ import { getUserFactory, getUsersFactory } from '@/modules/core/repositories/use
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import { adminOverrideEnabled } from '@/modules/shared/helpers/envHelper'
import { withOperationLogging } from '@/observability/domain/businessLogging'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory,
findEmailFactory
} from '@/modules/core/repositories/userEmails'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
const getServerInfo = getServerInfoFactory({ db })
const getUsers = getUsersFactory({ db })
@@ -84,6 +106,52 @@ const getFavoriteStreamsCollection = getFavoriteStreamsCollectionFactory({
getFavoritedStreamsPage: getFavoritedStreamsPageFactory({ db })
})
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStreamReturnRecord = createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
createAndSendInvite: createAndSendInviteFactory({
@@ -101,7 +169,8 @@ const createStreamReturnRecord = createStreamReturnRecordFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -208,8 +208,9 @@ export const addOrUpdateStreamCollaboratorFactory =
eventName: ServerInvitesEvents.Finalized,
payload: {
invite: fromInvite,
finalizerUserId: addedById,
accept: true
finalizerUserId: userId,
accept: true,
trueFinalizerUserId: addedById
}
})
} else {
@@ -27,7 +27,8 @@ import {
getStreamFactory,
createStreamFactory,
markBranchStreamUpdatedFactory,
markCommitStreamUpdatedFactory
markCommitStreamUpdatedFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import {
createCommitByBranchIdFactory,
@@ -52,7 +53,9 @@ import {
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory
updateAllInviteTargetsFactory,
findInviteFactory,
deleteInvitesByTargetFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
@@ -75,12 +78,24 @@ import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import { getPaginatedStreamBranchesFactory } from '@/modules/core/services/branch/retrieval'
import { createObjectFactory } from '@/modules/core/services/objects/management'
import { ensureError } from '@speckle/shared'
import { ModelEvents } from '@/modules/core/domain/branches/events'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
const db = knex
const Commits = () => knex('commits')
@@ -125,6 +140,51 @@ const createCommitByBranchName = createCommitByBranchNameFactory({
getBranchById: getBranchByIdFactory({ db })
})
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -143,7 +203,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -37,7 +37,8 @@ import {
getStreamFactory,
getCommitStreamFactory,
createStreamFactory,
markCommitStreamUpdatedFactory
markCommitStreamUpdatedFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import {
getObjectFactory,
@@ -53,7 +54,9 @@ import {
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory
updateAllInviteTargetsFactory,
findInviteFactory,
deleteInvitesByTargetFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
@@ -76,7 +79,10 @@ import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import {
getBranchCommitsTotalCountByNameFactory,
@@ -85,6 +91,15 @@ import {
import { createObjectFactory } from '@/modules/core/services/objects/management'
import { ensureError } from '@speckle/shared'
import { VersionEvents } from '@/modules/core/domain/commits/events'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
@@ -139,6 +154,51 @@ const updateCommitAndNotify = updateCommitAndNotifyFactory({
})
const getStreamCommitCount = getStreamCommitCountFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -157,7 +217,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -1,78 +1,124 @@
/* instanbul ignore file */
const expect = require('chai').expect
import { expect } from 'chai'
const { buildApolloServer } = require('@/app')
const { StreamFavorites, Streams, Users } = require('@/modules/core/dbSchema')
const { truncateTables } = require('@/test/hooks')
const { gql } = require('graphql-tag')
const { sleep } = require('@/test/helpers')
const {
import { buildApolloServer } from '@/app'
import { StreamFavorites, Streams, Users } from '@/modules/core/dbSchema'
import { truncateTables } from '@/test/hooks'
import gql from 'graphql-tag'
import { sleep } from '@/test/helpers'
import {
createAuthedTestContext,
createTestContext,
executeOperation
} = require('@/test/graphqlHelper')
const {
executeOperation,
ServerAndContext
} from '@/test/graphqlHelper'
import {
getStreamFactory,
createStreamFactory
} = require('@/modules/core/repositories/streams')
const { db } = require('@/db/knex')
const {
createStreamFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import { db } from '@/db/knex'
import {
legacyCreateStreamFactory,
createStreamReturnRecordFactory
} = require('@/modules/core/services/streams/management')
const {
inviteUsersToProjectFactory
} = require('@/modules/serverinvites/services/projectInviteManagement')
const {
createAndSendInviteFactory
} = require('@/modules/serverinvites/services/creation')
const {
} from '@/modules/core/services/streams/management'
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import {
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory
} = require('@/modules/serverinvites/repositories/serverInvites')
const {
collectAndValidateCoreTargetsFactory
} = require('@/modules/serverinvites/services/coreResourceCollection')
const {
buildCoreInviteEmailContentsFactory
} = require('@/modules/serverinvites/services/coreEmailContents')
const { getEventBus } = require('@/modules/shared/services/eventBus')
const { createBranchFactory } = require('@/modules/core/repositories/branches')
const {
updateAllInviteTargetsFactory,
findInviteFactory,
deleteInvitesByTargetFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { createBranchFactory } from '@/modules/core/repositories/branches'
import {
getUsersFactory,
getUserFactory,
storeUserFactory,
countAdminUsersFactory,
storeUserAclFactory
} = require('@/modules/core/repositories/users')
const {
} from '@/modules/core/repositories/users'
import {
findEmailFactory,
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory
} = require('@/modules/core/repositories/userEmails')
const {
requestNewEmailVerificationFactory
} = require('@/modules/emails/services/verification/request')
const {
deleteOldAndInsertNewVerificationFactory
} = require('@/modules/emails/repositories')
const { renderEmail } = require('@/modules/emails/services/emailRendering')
const { sendEmail } = require('@/modules/emails/services/sending')
const { createUserFactory } = require('@/modules/core/services/users/management')
const {
validateAndCreateUserEmailFactory
} = require('@/modules/core/services/userEmails')
const {
finalizeInvitedServerRegistrationFactory
} = require('@/modules/serverinvites/services/processing')
const { getServerInfoFactory } = require('@/modules/core/repositories/server')
} from '@/modules/core/repositories/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
const getUsers = getUsersFactory({ db })
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -91,7 +137,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -205,29 +252,35 @@ const totalOwnedStreamsFavoritesNew = gql`
describe('Favorite streams', () => {
const myPubStream = {
name: 'My Stream 1',
isPublic: false
isPublic: false,
id: ''
}
const myStream = {
name: 'My Stream 2',
isPublic: true
isPublic: true,
id: ''
}
const notMyStream = {
name: 'Not My Stream 1',
isPublic: false
isPublic: false,
id: ''
}
const notMyPubStream = {
name: 'Not My Stream 2',
isPublic: true
isPublic: true,
id: ''
}
const me = {
name: 'Itsa Me',
email: 'me@example.org',
password: 'sn3aky-1337-b1m'
password: 'sn3aky-1337-b1m',
id: ''
}
const otherGuy = {
name: 'Some Other DUde',
email: 'otherguy@example.org',
password: 'sn3aky-1337-b1m'
password: 'sn3aky-1337-b1m',
id: ''
}
before(async function () {
@@ -259,9 +312,9 @@ describe('Favorite streams', () => {
describe('when authenticated', () => {
/** @type {import('@/test/graphqlHelper').ServerAndContext} */
let apollo
let apollo: ServerAndContext
const favoriteStream = async (sid, favorited) =>
const favoriteStream = async (sid: string, favorited: boolean) =>
await executeOperation(apollo, favoriteMutationGql, { sid, favorited })
before(async () => {
@@ -274,7 +327,7 @@ describe('Favorite streams', () => {
await StreamFavorites.knex().truncate()
})
const accessibleStreamIds = [
const accessibleStreamIds = <const>[
[() => myPubStream.id, 'owned and public'],
[() => myStream.id, 'owned and not public'],
[() => notMyPubStream.id, 'not owned, but public']
@@ -292,7 +345,7 @@ describe('Favorite streams', () => {
expect(result.errors).to.not.be.ok
expect(result.data?.streamFavorite?.favoritedDate).to.be.a('date')
expect(result.data?.streamFavorite?.favoritedDate.getTime()).to.satisfy(
(t) => t > beforeTime && t < afterTime
(t: number) => t > beforeTime && t < afterTime
)
expect(result.data?.streamFavorite?.id).to.equal(streamId)
expect(result.data?.streamFavorite?.favoritesCount).to.equal(1)
@@ -302,19 +355,19 @@ describe('Favorite streams', () => {
it("can't be favorited if not owned and not public", async () => {
const result = await favoriteStream(notMyStream.id, true)
expect(result.data.streamFavorite).to.not.be.ok
expect(result.data!.streamFavorite).to.not.be.ok
expect(result.errors).to.have.lengthOf(1)
expect(result.errors.at(0).message).to.contain("doesn't have access")
expect(result.errors!.at(0)!.message).to.contain("doesn't have access")
})
describe('and favorited', () => {
const favoritedStream = {
name: 'Favorited Stream',
isPublic: true
isPublic: true,
id: ''
}
/** @type {{favoritedDate: Date, favoritesCount: number, id: string}} */
let favoritingResults
let favoritingResults: { favoritedDate: Date; favoritesCount: number; id: string }
before(async () => {
favoritedStream.id = await createStream({ ...favoritedStream, ownerId: me.id })
@@ -345,12 +398,12 @@ describe('Favorite streams', () => {
describe('and being queried', () => {
const favoritableStreams = [
{ name: 'Random 1', isPublic: true },
{ name: 'Random 2', isPublic: true },
{ name: 'Random 2', isPublic: true }
{ name: 'Random 1', isPublic: true, id: '' },
{ name: 'Random 2', isPublic: true, id: '' },
{ name: 'Random 2', isPublic: true, id: '' }
]
const getFavorites = async (cursor, limit = 10) =>
const getFavorites = async (cursor: string | null, limit = 10) =>
await executeOperation(apollo, favoriteStreamsQueryGql, { cursor, limit })
const favoritedStreamIds = () => favoritableStreams.map((s) => s.id)
@@ -380,7 +433,7 @@ describe('Favorite streams', () => {
)
expect(data).to.be.ok
expect(data.otherUser?.favoriteStreams).to.not.be.ok
expect(data!.otherUser?.favoriteStreams).to.not.be.ok
expect((errors || []).map((e) => e.message).join()).to.match(
/cannot view another user's favorite streams/i
)
@@ -394,22 +447,24 @@ describe('Favorite streams', () => {
expect(results.data?.activeUser?.favoriteStreams?.items).to.have.lengthOf(
ids.length
)
expect(results.data.activeUser.favoriteStreams.totalCount).to.equal(ids.length)
expect(results.data.activeUser.favoriteStreams.cursor).to.be.a('string')
expect(results.data!.activeUser.favoriteStreams.totalCount).to.equal(ids.length)
expect(results.data!.activeUser.favoriteStreams.cursor).to.be.a('string')
})
it('are paginated correctly', async () => {
let nextCursor = null
let returnedStreamIds = []
let returnedStreamIds: string[] = []
const getPaginatedAndAssert = async (nextCursor) => {
const getPaginatedAndAssert = async (nextCursor: string | null) => {
const results = await getFavorites(nextCursor, 1)
expect(results.errors).to.not.be.ok
expect(results.data?.activeUser?.favoriteStreams).to.be.ok
return {
cursor: results.data.activeUser.favoriteStreams.cursor,
sids: results.data.activeUser.favoriteStreams.items.map((i) => i.id)
cursor: results.data!.activeUser.favoriteStreams.cursor,
sids: results.data!.activeUser.favoriteStreams.items.map(
(i: { id: string }) => i.id
)
}
}
@@ -456,8 +511,7 @@ describe('Favorite streams', () => {
})
describe('when not authenticated', () => {
/** @type {import('@/test/graphqlHelper').ServerAndContext} */
let apollo
let apollo: ServerAndContext
before(async () => {
apollo = {
@@ -472,15 +526,15 @@ describe('Favorite streams', () => {
favorited: true
})
expect(result.data.streamFavorite).to.not.be.ok
expect(result.data!.streamFavorite).to.not.be.ok
expect(result.errors).to.have.lengthOf(1)
expect(result.errors.at(0).message).to.contain('Must provide an auth token')
expect(result.errors!.at(0)!.message).to.contain('Must provide an auth token')
})
it("can't be retrieved", async () => {
const result = await executeOperation(apollo, favoriteStreamsQueryGql)
expect(result.data.activeUser).to.be.null
expect(result.data!.activeUser).to.be.null
expect(result.errors).to.not.be.ok
})
})
@@ -1,71 +1,115 @@
/* istanbul ignore file */
const expect = require('chai').expect
import { expect } from 'chai'
const { beforeEachContext } = require('@/test/hooks')
import { beforeEachContext } from '@/test/hooks'
const { validateScopes, authorizeResolver } = require('@/modules/shared')
const { buildContext } = require('@/modules/shared/middleware')
const { Roles, Scopes } = require('@speckle/shared')
const { throwForNotHavingServerRole } = require('@/modules/shared/authz')
const { ForbiddenError } = require('@/modules/shared/errors')
const {
import { validateScopes, authorizeResolver } from '@/modules/shared'
import { buildContext } from '@/modules/shared/middleware'
import { AvailableRoles, Roles, Scopes, ServerRoles } from '@speckle/shared'
import { throwForNotHavingServerRole } from '@/modules/shared/authz'
import { ForbiddenError } from '@/modules/shared/errors'
import {
getStreamFactory,
createStreamFactory
} = require('@/modules/core/repositories/streams')
const { db } = require('@/db/knex')
const {
createStreamFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import { db } from '@/db/knex'
import {
legacyCreateStreamFactory,
createStreamReturnRecordFactory
} = require('@/modules/core/services/streams/management')
const {
inviteUsersToProjectFactory
} = require('@/modules/serverinvites/services/projectInviteManagement')
const {
createAndSendInviteFactory
} = require('@/modules/serverinvites/services/creation')
const {
} from '@/modules/core/services/streams/management'
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import {
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory
} = require('@/modules/serverinvites/repositories/serverInvites')
const {
collectAndValidateCoreTargetsFactory
} = require('@/modules/serverinvites/services/coreResourceCollection')
const {
buildCoreInviteEmailContentsFactory
} = require('@/modules/serverinvites/services/coreEmailContents')
const { getEventBus } = require('@/modules/shared/services/eventBus')
const { createBranchFactory } = require('@/modules/core/repositories/branches')
const {
updateAllInviteTargetsFactory,
findInviteFactory,
deleteInvitesByTargetFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { createBranchFactory } from '@/modules/core/repositories/branches'
import {
getUsersFactory,
getUserFactory,
storeUserFactory,
countAdminUsersFactory,
storeUserAclFactory
} = require('@/modules/core/repositories/users')
const {
} from '@/modules/core/repositories/users'
import {
findEmailFactory,
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory
} = require('@/modules/core/repositories/userEmails')
const {
requestNewEmailVerificationFactory
} = require('@/modules/emails/services/verification/request')
const {
deleteOldAndInsertNewVerificationFactory
} = require('@/modules/emails/repositories')
const { renderEmail } = require('@/modules/emails/services/emailRendering')
const { sendEmail } = require('@/modules/emails/services/sending')
const { createUserFactory } = require('@/modules/core/services/users/management')
const {
validateAndCreateUserEmailFactory
} = require('@/modules/core/services/userEmails')
const {
finalizeInvitedServerRegistrationFactory
} = require('@/modules/serverinvites/services/processing')
const { getServerInfoFactory } = require('@/modules/core/repositories/server')
const { mockAdminOverride } = require('@/test/mocks/global')
} from '@/modules/core/repositories/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import { mockAdminOverride } from '@/test/mocks/global'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { Request } from 'express'
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
@@ -89,7 +133,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -134,7 +179,7 @@ describe('Generic AuthN & AuthZ controller tests', () => {
})
it('Validate scopes', async () => {
await validateScopes()
await validateScopes(undefined, undefined as unknown as string)
.then(() => {
throw new Error('This should have been rejected')
})
@@ -156,14 +201,17 @@ describe('Generic AuthN & AuthZ controller tests', () => {
await validateScopes(['a', 'b'], 'b') // should pass
})
;[
['BS header', { req: { headers: { authorization: 'Bearer BS' } } }],
['Null header', { req: { headers: { authorization: null } } }],
['Undefined header', { req: { headers: { authorization: undefined } } }],
;(<const>[
['BS header', { req: { headers: { authorization: 'Bearer BS' } } as Request }],
[
'Null header',
{ req: { headers: { authorization: null as string | null } } as Request }
],
['Undefined header', { req: { headers: { authorization: undefined } } as Request }],
['BS token', { token: 'Bearer BS' }],
['Null token', { token: null }],
['Undefined token', { token: undefined }]
].map(([caseName, contextInput]) =>
]).map(([caseName, contextInput]) =>
it(`Should create proper context ${caseName}`, async () => {
const res = await buildContext(contextInput)
expect(res.auth).to.equal(false)
@@ -182,7 +230,10 @@ describe('Generic AuthN & AuthZ controller tests', () => {
expect('You do not have the required server role').to.equal(err.message)
)
await throwForNotHavingServerRole({ auth: true, role: 'HACZOR' }, '133TCR3w')
await throwForNotHavingServerRole(
{ auth: true, role: 'HACZOR' as ServerRoles },
'133TCR3w' as ServerRoles
)
.then(() => {
throw new Error('This should have been rejected')
})
@@ -192,7 +243,7 @@ describe('Generic AuthN & AuthZ controller tests', () => {
await throwForNotHavingServerRole(
{ auth: true, role: Roles.Server.Admin },
'133TCR3w'
'133TCR3w' as ServerRoles
)
.then(() => {
throw new Error('This should have been rejected')
@@ -209,14 +260,19 @@ describe('Generic AuthN & AuthZ controller tests', () => {
})
it('Resolver Authorization Should fail nicely when roles & resources are wanky', async () => {
await authorizeResolver(null, 'foo', 'bar')
await authorizeResolver(null, 'foo', 'bar' as AvailableRoles, null)
.then(() => {
throw new Error('This should have been rejected')
})
.catch((err) => expect('Unknown role: bar').to.equal(err.message))
// this caught me out, but streams:read is not a valid role for now
await authorizeResolver('foo', 'bar', Scopes.Streams.Read)
await authorizeResolver(
'foo',
'bar' as AvailableRoles,
Scopes.Streams.Read as AvailableRoles,
null
)
.then(() => {
throw new Error('This should have been rejected')
})
@@ -226,21 +282,25 @@ describe('Generic AuthN & AuthZ controller tests', () => {
describe('Authorize resolver ', () => {
const myStream = {
name: 'My Stream 2',
isPublic: true
isPublic: true,
id: ''
}
const notMyStream = {
name: 'Not My Stream 1',
isPublic: false
isPublic: false,
id: ''
}
const serverOwner = {
name: 'Itsa Me',
email: 'me@example.org',
password: 'sn3aky-1337-b1m'
password: 'sn3aky-1337-b1m',
id: ''
}
const otherGuy = {
name: 'Some Other DUde',
email: 'otherguy@example.org',
password: 'sn3aky-1337-b1m'
password: 'sn3aky-1337-b1m',
id: ''
}
before(async function () {
@@ -290,7 +350,7 @@ describe('Generic AuthN & AuthZ controller tests', () => {
Roles.Stream.Contributor,
null
)
throw 'This should have thrown'
throw new Error('This should have thrown')
} catch (e) {
expect(e instanceof ForbiddenError)
}
@@ -315,7 +375,7 @@ describe('Generic AuthN & AuthZ controller tests', () => {
Roles.Stream.Contributor,
null
)
throw 'This should have thrown'
throw new Error('This should have thrown')
} catch (e) {
expect(e instanceof ForbiddenError)
}
@@ -331,7 +391,7 @@ describe('Generic AuthN & AuthZ controller tests', () => {
Roles.Stream.Contributor,
null
)
throw 'This should have thrown'
throw new Error('This should have thrown')
} catch (e) {
expect(e instanceof ForbiddenError)
}
@@ -1,75 +1,65 @@
/* istanbul ignore file */
/* eslint-disable camelcase */
const expect = require('chai').expect
const assert = require('assert')
const { cloneDeep, times, random, padStart } = require('lodash')
import { expect } from 'chai'
import assert from 'assert'
import { cloneDeep, times, random, padStart } from 'lodash'
const { beforeEachContext } = require('@/test/hooks')
const { getAnIdForThisOnePlease } = require('@/test/helpers')
import { beforeEachContext } from '@/test/hooks'
import { getAnIdForThisOnePlease } from '@/test/helpers'
const {
import {
getStreamFactory,
createStreamFactory
} = require('@/modules/core/repositories/streams')
const { db } = require('@/db/knex')
const {
createStreamFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import { db } from '@/db/knex'
import {
legacyCreateStreamFactory,
createStreamReturnRecordFactory
} = require('@/modules/core/services/streams/management')
const {
inviteUsersToProjectFactory
} = require('@/modules/serverinvites/services/projectInviteManagement')
const {
createAndSendInviteFactory
} = require('@/modules/serverinvites/services/creation')
const {
} from '@/modules/core/services/streams/management'
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import {
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory
} = require('@/modules/serverinvites/repositories/serverInvites')
const {
collectAndValidateCoreTargetsFactory
} = require('@/modules/serverinvites/services/coreResourceCollection')
const {
buildCoreInviteEmailContentsFactory
} = require('@/modules/serverinvites/services/coreEmailContents')
const { getEventBus } = require('@/modules/shared/services/eventBus')
const { createBranchFactory } = require('@/modules/core/repositories/branches')
const {
updateAllInviteTargetsFactory,
findInviteFactory,
deleteInvitesByTargetFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { createBranchFactory } from '@/modules/core/repositories/branches'
import {
getUsersFactory,
getUserFactory,
storeUserFactory,
countAdminUsersFactory,
storeUserAclFactory
} = require('@/modules/core/repositories/users')
const {
} from '@/modules/core/repositories/users'
import {
findEmailFactory,
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory
} = require('@/modules/core/repositories/userEmails')
const {
requestNewEmailVerificationFactory
} = require('@/modules/emails/services/verification/request')
const {
deleteOldAndInsertNewVerificationFactory
} = require('@/modules/emails/repositories')
const { renderEmail } = require('@/modules/emails/services/emailRendering')
const { sendEmail } = require('@/modules/emails/services/sending')
const { createUserFactory } = require('@/modules/core/services/users/management')
const {
validateAndCreateUserEmailFactory
} = require('@/modules/core/services/userEmails')
const {
finalizeInvitedServerRegistrationFactory
} = require('@/modules/serverinvites/services/processing')
const { getServerInfoFactory } = require('@/modules/core/repositories/server')
const {
} from '@/modules/core/repositories/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import {
createObjectFactory,
createObjectsBatchedAndNoClosuresFactory,
createObjectsFactory
} = require('@/modules/core/services/objects/management')
const {
} from '@/modules/core/services/objects/management'
import {
storeSingleObjectIfNotFoundFactory,
storeObjectsIfNotFoundFactory,
getFormattedObjectFactory,
@@ -77,7 +67,17 @@ const {
getObjectChildrenFactory,
getObjectChildrenQueryFactory,
getStreamObjectsFactory
} = require('@/modules/core/repositories/objects')
} from '@/modules/core/repositories/objects'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
import { ObjectRecord } from '@/modules/core/helpers/types'
const sampleCommit = JSON.parse(`{
"Objects": [
@@ -106,6 +106,52 @@ const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
const getUsers = getUsersFactory({ db })
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -124,7 +170,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -180,12 +227,14 @@ describe('Objects @core-objects', () => {
const userOne = {
name: 'Dimitrie Stefanescu',
email: 'didimitrie43@example.org',
password: 'sn3aky-1337-b1m'
password: 'sn3aky-1337-b1m',
id: ''
}
const stream = {
name: 'Test Streams',
description: 'Whatever goes in here usually...'
description: 'Whatever goes in here usually...',
id: ''
}
before(async () => {
@@ -202,8 +251,8 @@ describe('Objects @core-objects', () => {
const objCount_1 = 10
const objCount_2 = 1000
const objs = []
const objs2 = []
const objs: Array<Record<string, unknown> & { id?: string }> = []
const objs2: Array<Record<string, unknown> & { id?: string }> = []
it(`Should create ${objCount_1} objects`, async () => {
for (let i = 0; i < objCount_1; i++) {
@@ -258,7 +307,7 @@ describe('Objects @core-objects', () => {
]).reduce((obj, [key, value]) => {
obj[key] = value
return obj
}, {})
}, {} as Record<string, unknown>)
}
const id = await createObject({ streamId: stream.id, object: obj })
expect(id).to.be.ok
@@ -272,20 +321,20 @@ describe('Objects @core-objects', () => {
it('Should get more objects', async () => {
const myObjs = await getObjects(
stream.id,
objs.map((o) => o.id)
objs.map((o) => o.id!)
)
expect(myObjs).to.have.lengthOf(objs.length)
const match1 = myObjs.find((o) => o.id === objs[0].id)
expect(match1).to.not.be.null
expect(match1.id).to.equal(objs[0].id)
expect(match1!.id).to.equal(objs[0].id)
const match2 = myObjs.find((o) => o.id === objs[2].id)
expect(match2).to.not.be.null
expect(match2.id).to.equal(objs[2].id)
expect(match2!.id).to.equal(objs[2].id)
})
let parentObjectId
let parentObjectId: string
it('Should get object children', async () => {
const objs_1 = createManyObjects(100, 'noise__')
@@ -368,7 +417,7 @@ describe('Objects @core-objects', () => {
{ field: 'test.value', operator: '<', value: 24 },
{ verb: 'OR', field: 'test.value', operator: '=', value: 42 }
],
orderBy: { field: 'test.value', direction: 'asc' }
orderBy: { field: 'test.value' as keyof ObjectRecord, direction: 'asc' }
})
const test2 = await getObjectChildrenQuery({
@@ -381,7 +430,7 @@ describe('Objects @core-objects', () => {
{ field: 'test.value', operator: '<', value: 24 },
{ verb: 'OR', field: 'test.value', operator: '=', value: 42 }
],
orderBy: { field: 'test.value', direction: 'asc' },
orderBy: { field: 'test.value' as keyof ObjectRecord, direction: 'asc' },
cursor: test.cursor
})
@@ -403,14 +452,19 @@ describe('Objects @core-objects', () => {
expect(test.totalCount).to.equal(23)
expect(test2.totalCount).to.equal(23)
expect(test.objects[0].data.test.value).to.be.below(test.objects[1].data.test.value)
expect(test2.objects[0].data.test.value).to.be.below(
test2.objects[1].data.test.value
)
const testObjects = test.objects as unknown as Array<{
data: { test: { value: number } }
}>
const test2Objects = test2.objects as unknown as Array<{
data: { test: { value: number } }
}>
expect(testObjects[0].data.test.value).to.be.below(testObjects[1].data.test.value)
expect(test2Objects[0].data.test.value).to.be.below(test2Objects[1].data.test.value)
// continuity
expect(test.objects[test.objects.length - 1].data.test.value + 1).to.equal(
test2.objects[0].data.test.value
expect(testObjects[testObjects.length - 1].data.test.value + 1).to.equal(
test2Objects[0].data.test.value
)
})
@@ -424,7 +478,7 @@ describe('Objects @core-objects', () => {
{ field: 'similar', operator: '>=', value: 0 },
{ field: 'similar', operator: '<', value: 100 }
],
orderBy: { field: 'similar', direction: 'asc' },
orderBy: { field: 'similar' as keyof ObjectRecord, direction: 'asc' },
limit: 5
})
@@ -436,7 +490,7 @@ describe('Objects @core-objects', () => {
{ field: 'similar', operator: '>=', value: 0 },
{ field: 'similar', operator: '<', value: 100 }
],
orderBy: { field: 'similar', direction: 'asc' },
orderBy: { field: 'similar' as keyof ObjectRecord, direction: 'asc' },
cursor: test3.cursor,
limit: 5
})
@@ -453,17 +507,24 @@ describe('Objects @core-objects', () => {
expect(test3.totalCount).to.equal(100)
expect(test4.totalCount).to.equal(100)
expect(test3.objects[0].data.similar).to.be.below(test3.objects[1].data.similar) // 0, 1, 1, 1, ...
expect(test4.objects[0].data.similar).to.be.below(test4.objects[3].data.similar)
const test3Objects = test3.objects as unknown as Array<{
data: { similar: number }
}>
const test4Objects = test4.objects as unknown as Array<{
data: { similar: number }
}>
expect(test3Objects[0].data.similar).to.be.below(test3Objects[1].data.similar) // 0, 1, 1, 1, ...
expect(test4Objects[0].data.similar).to.be.below(test4Objects[3].data.similar)
// continuity (in reverse)
expect(test3.objects[test3.objects.length - 1].data.similar).to.equal(
test3.objects[test3.objects.length - 2].data.similar + 1
expect(test3Objects[test3Objects.length - 1].data.similar).to.equal(
test3Objects[test3Objects.length - 2].data.similar + 1
)
expect(test3.objects[test3.objects.length - 1].data.similar).to.equal(
test4.objects[0].data.similar
expect(test3Objects[test3Objects.length - 1].data.similar).to.equal(
test4Objects[0].data.similar
)
expect(test4.objects[1].data.similar).to.equal(test4.objects[2].data.similar - 1)
expect(test4Objects[1].data.similar).to.equal(test4Objects[2].data.similar - 1)
})
it('should query object children with no results ', async () => {
@@ -474,7 +535,7 @@ describe('Objects @core-objects', () => {
{ field: 'test.value', operator: '>=', value: 10 },
{ field: 'test.value', operator: '<', value: 9 }
],
orderBy: { field: 'test.value', direction: 'desc' }
orderBy: { field: 'test.value' as keyof ObjectRecord, direction: 'desc' }
})
expect(test.totalCount).to.equal(0)
@@ -494,7 +555,7 @@ describe('Objects @core-objects', () => {
},
{ field: 'test.value', operator: '<', value: 9 }
],
orderBy: { field: 'test.value', direction: 'desc' }
orderBy: { field: 'test.value' as keyof ObjectRecord, direction: 'desc' }
})
assert.fail('sql injections are bad for health')
} catch {
@@ -509,7 +570,7 @@ describe('Objects @core-objects', () => {
limit: 5,
select: ['test.value', 'nest.duck'],
query: [{ field: 'test.value', operator: '<', value: 10 }],
orderBy: { field: 'nest.duck', direction: 'desc' }
orderBy: { field: 'nest.duck' as keyof ObjectRecord, direction: 'desc' }
})
const test2 = await getObjectChildrenQuery({
@@ -518,12 +579,18 @@ describe('Objects @core-objects', () => {
limit: 5,
select: ['test.value', 'nest.duck'],
query: [{ field: 'test.value', operator: '<', value: 10 }],
orderBy: { field: 'nest.duck', direction: 'desc' },
orderBy: { field: 'nest.duck' as keyof ObjectRecord, direction: 'desc' },
cursor: test.cursor
})
expect(test.objects[0].data.nest.duck).to.equal(true)
expect(test2.objects[test2.objects.length - 1].data.nest.duck).to.equal(false) // last duck should be false
const testObjects = test.objects as unknown as Array<{
data: { test: { value: number }; nest: { duck: boolean } }
}>
const test2Objects = test2.objects as unknown as Array<{
data: { test: { value: number }; nest: { duck: boolean } }
}>
expect(testObjects[0].data.nest.duck).to.equal(true)
expect(test2Objects[test2Objects.length - 1].data.nest.duck).to.equal(false) // last duck should be false
})
it('should query children and sort them by a string value ', async () => {
@@ -534,7 +601,7 @@ describe('Objects @core-objects', () => {
objectId: parentObjectId,
limit: 5,
query: [{ field: 'test.value', operator: '<', value: limVal }],
orderBy: { field: 'name', direction: 'asc' }
orderBy: { field: 'name' as keyof ObjectRecord, direction: 'asc' }
})
const test2 = await getObjectChildrenQuery({
@@ -542,18 +609,25 @@ describe('Objects @core-objects', () => {
objectId: parentObjectId,
limit: 5,
query: [{ field: 'test.value', operator: '<', value: limVal }],
orderBy: { field: 'name', direction: 'asc' },
orderBy: { field: 'name' as keyof ObjectRecord, direction: 'asc' },
cursor: test.cursor
})
expect(test.objects.length).to.equal(5)
expect(test.cursor).to.be.a('string')
expect(test.objects[0].data.name).to.equal('mr. 0')
expect(test.objects[1].data.name).to.equal('mr. 1')
expect(test.objects[2].data.name).to.equal('mr. 10') // remember kids, this is a lexicographical sort
expect(test.objects[4].data.name).to.equal('mr. 12')
expect(test2.objects[0].data.name).to.equal('mr. 13')
const testObjects = test.objects as unknown as Array<{
data: { name: string; test: { value: number } }
}>
const test2Objects = test2.objects as unknown as Array<{
data: { name: string; test: { value: number } }
}>
expect(testObjects[0].data.name).to.equal('mr. 0')
expect(testObjects[1].data.name).to.equal('mr. 1')
expect(testObjects[2].data.name).to.equal('mr. 10') // remember kids, this is a lexicographical sort
expect(testObjects[4].data.name).to.equal('mr. 12')
expect(test2Objects[0].data.name).to.equal('mr. 13')
})
it('should query children and sort them by id by default ', async () => {
@@ -588,41 +662,53 @@ describe('Objects @core-objects', () => {
streamId: stream.id,
objectId: parentObjectId,
limit: 2,
orderBy: { field: 'test.value', direction: 'desc' }
orderBy: { field: 'test.value' as keyof ObjectRecord, direction: 'desc' }
})
const test2 = await getObjectChildrenQuery({
streamId: stream.id,
objectId: parentObjectId,
limit: 2,
orderBy: { field: 'test.value', direction: 'desc' },
orderBy: { field: 'test.value' as keyof ObjectRecord, direction: 'desc' },
cursor: test.cursor
})
expect(test.objects[1].data.test.value).to.equal(
test2.objects[0].data.test.value + 1
) // continuity check
const testObjects = test.objects as unknown as Array<{
data: { test: { value: number } }
}>
const test2Objects = test2.objects as unknown as Array<{
data: { test: { value: number } }
}>
expect(testObjects[1].data.test.value).to.equal(test2Objects[0].data.test.value + 1) // continuity check
const test3 = await getObjectChildrenQuery({
streamId: stream.id,
objectId: parentObjectId,
limit: 50,
orderBy: { field: 'nest.duck', direction: 'desc' }
orderBy: { field: 'nest.duck' as keyof ObjectRecord, direction: 'desc' }
})
const test4 = await getObjectChildrenQuery({
streamId: stream.id,
objectId: parentObjectId,
limit: 50,
orderBy: { field: 'nest.duck', direction: 'desc' },
orderBy: { field: 'nest.duck' as keyof ObjectRecord, direction: 'desc' },
cursor: test3.cursor
})
expect(test3.objects[49].data.nest.duck).to.equal(true)
expect(test4.objects[0].data.nest.duck).to.equal(false)
const test3Objects = test3.objects as unknown as Array<{
data: { nest: { duck: boolean } }
}>
const test4Objects = test4.objects as unknown as Array<{
data: { nest: { duck: boolean } }
}>
expect(test3Objects[49].data.nest.duck).to.equal(true)
expect(test4Objects[0].data.nest.duck).to.equal(false)
})
let commitId
let commitId: string
it('should batch create objects', async () => {
const objs = createManyObjects(3333, 'perlin merlin magic')
commitId = objs[0].id
@@ -641,6 +727,7 @@ describe('Objects @core-objects', () => {
it('should stream objects back', (done) => {
let tcount = 0
// eslint-disable-next-line @typescript-eslint/no-floating-promises
getObjectChildrenStream({ streamId: stream.id, objectId: commitId }).then(
(stream) => {
stream.on('data', () => tcount++)
@@ -656,7 +743,7 @@ describe('Objects @core-objects', () => {
this.timeout(5000)
const objs = createManyObjects(5000, 'perlin merlin magic')
function shuffleArray(array) {
function shuffleArray(array: Array<unknown>) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[array[i], array[j]] = [array[j], array[i]]
@@ -686,13 +773,18 @@ describe('Objects @core-objects', () => {
})
})
function createManyObjects(num, noise) {
function createManyObjects(num: number, noise: string | number) {
num = num || 10000
noise = noise || Math.random() * 100
const objs = []
const base = { name: 'base bastard 2', noise, __closure: {} }
const base = {
name: 'base bastard 2',
noise,
__closure: {} as Record<string, number>,
id: ''
}
objs.push(base)
let k = 0
@@ -706,7 +798,8 @@ function createManyObjects(num, noise) {
objArr: [{ a: i }, { b: i * i }, { c: true }],
noise,
sortValueA: i,
sortValueB: i * 0.42 * i
sortValueB: i * 0.42 * i,
id: ''
}
if (i % 3 === 0) k++
@@ -1,84 +1,130 @@
/* istanbul ignore file */
const expect = require('chai').expect
const request = require('supertest')
import { expect } from 'chai'
import request from 'supertest'
const assert = require('assert')
const crypto = require('crypto')
import assert from 'assert'
import crypto from 'crypto'
const { beforeEachContext } = require('@/test/hooks')
const { createManyObjects } = require('@/test/helpers')
import { beforeEachContext } from '@/test/hooks'
import { createManyObjects } from '@/test/helpers'
const { Scopes } = require('@speckle/shared')
const {
import { Scopes } from '@speckle/shared'
import {
getStreamFactory,
createStreamFactory
} = require('@/modules/core/repositories/streams')
const { db } = require('@/db/knex')
const {
createStreamFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import { db } from '@/db/knex'
import {
legacyCreateStreamFactory,
createStreamReturnRecordFactory
} = require('@/modules/core/services/streams/management')
const {
inviteUsersToProjectFactory
} = require('@/modules/serverinvites/services/projectInviteManagement')
const {
createAndSendInviteFactory
} = require('@/modules/serverinvites/services/creation')
const {
} from '@/modules/core/services/streams/management'
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import {
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory
} = require('@/modules/serverinvites/repositories/serverInvites')
const {
collectAndValidateCoreTargetsFactory
} = require('@/modules/serverinvites/services/coreResourceCollection')
const {
buildCoreInviteEmailContentsFactory
} = require('@/modules/serverinvites/services/coreEmailContents')
const { getEventBus } = require('@/modules/shared/services/eventBus')
const { createBranchFactory } = require('@/modules/core/repositories/branches')
const {
updateAllInviteTargetsFactory,
findInviteFactory,
deleteInvitesByTargetFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { createBranchFactory } from '@/modules/core/repositories/branches'
import {
getUsersFactory,
getUserFactory,
storeUserFactory,
countAdminUsersFactory,
storeUserAclFactory
} = require('@/modules/core/repositories/users')
const {
} from '@/modules/core/repositories/users'
import {
findEmailFactory,
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory
} = require('@/modules/core/repositories/userEmails')
const {
requestNewEmailVerificationFactory
} = require('@/modules/emails/services/verification/request')
const {
deleteOldAndInsertNewVerificationFactory
} = require('@/modules/emails/repositories')
const { renderEmail } = require('@/modules/emails/services/emailRendering')
const { sendEmail } = require('@/modules/emails/services/sending')
const { createUserFactory } = require('@/modules/core/services/users/management')
const {
validateAndCreateUserEmailFactory
} = require('@/modules/core/services/userEmails')
const {
finalizeInvitedServerRegistrationFactory
} = require('@/modules/serverinvites/services/processing')
const { createPersonalAccessTokenFactory } = require('@/modules/core/services/tokens')
const {
} from '@/modules/core/repositories/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { createPersonalAccessTokenFactory } from '@/modules/core/services/tokens'
import {
storeTokenScopesFactory,
storeApiTokenFactory,
storeTokenResourceAccessDefinitionsFactory,
storePersonalApiTokenFactory
} = require('@/modules/core/repositories/tokens')
const { getServerInfoFactory } = require('@/modules/core/repositories/server')
const cryptoRandomString = require('crypto-random-string')
} from '@/modules/core/repositories/tokens'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import cryptoRandomString from 'crypto-random-string'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
import type Express from 'express'
const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
const getUsers = getUsersFactory({ db })
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -97,7 +143,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -147,22 +194,33 @@ describe('Upload/Download Routes @api-rest', () => {
const userA = {
name: 'd1',
email: 'd.1@speckle.systems',
password: 'wowwow8charsplease'
password: 'wowwow8charsplease',
id: '',
token: ''
}
const userB = {
name: 'd2',
email: 'd.2@speckle.systems',
password: 'wowwow8charsplease'
password: 'wowwow8charsplease',
id: '',
token: ''
}
const testStream = {
name: 'Test Stream 01',
description: 'wonderful test stream'
description: 'wonderful test stream',
id: '',
ownerId: ''
}
const privateTestStream = { name: 'Private Test Stream', isPublic: false }
const privateTestStream = {
name: 'Private Test Stream',
isPublic: false,
id: '',
ownerId: ''
}
let app
let app: Express.Express
before(async () => {
;({ app } = await beforeEachContext())
@@ -277,7 +335,12 @@ describe('Upload/Download Routes @api-rest', () => {
.post(`/objects/${testStream.id}`)
.set('Authorization', userA.token)
.set('Content-type', 'application/json')
.attach(Buffer.from(JSON.stringify(objBatches[0]), 'utf8'))
.attach(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Buffer.from(JSON.stringify(objBatches[0]), 'utf8') as any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
undefined as any
)
expect(res).to.have.status(400)
expect(res.text).to.equal(
'Failed to parse request headers and body content as valid multipart/form-data.'
@@ -289,7 +352,8 @@ describe('Upload/Download Routes @api-rest', () => {
.post(`/objects/${testStream.id}`)
.set('Authorization', userA.token)
.set('Content-type', 'multipart/form-data')
.attach(JSON.stringify(objBatches[0], 'utf8'))
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.attach(JSON.stringify(objBatches[0]) as any, undefined as any)
expect(res).to.have.status(400)
expect(res.text).to.equal(
'Failed to parse request headers and body content as valid multipart/form-data.'
@@ -341,7 +405,8 @@ describe('Upload/Download Routes @api-rest', () => {
it('Should not allow upload with invalid body (not contained within array)', async () => {
//creating a single valid object
const objectToPost = {
name: 'yet again cannot believe i have to create this'
name: 'yet again cannot believe i have to create this',
id: ''
}
const objectId = crypto
.createHash('md5')
@@ -384,7 +449,7 @@ describe('Upload/Download Routes @api-rest', () => {
// expect(res.text).contains('Object too large')
// })
let parentId
let parentId: string
const numObjs = 5000
const objBatches = [
createManyObjects(numObjs),
@@ -412,19 +477,22 @@ describe('Upload/Download Routes @api-rest', () => {
})
it('Should properly download an object, with all its children, into a application/json response', (done) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
new Promise((resolve) => setTimeout(resolve, 1500)) // avoids race condition
.then(() => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
request(app)
.get(`/objects/${testStream.id}/${parentId}`)
.set('Authorization', userA.token)
.buffer()
.parse((res, cb) => {
res.data = ''
res.on('data', (chunk) => {
res.data += chunk.toString()
const resTyped = res as typeof res & { data: string }
resTyped.data = ''
resTyped.on('data', (chunk) => {
resTyped.data += chunk.toString()
})
res.on('end', () => {
cb(null, res.data)
resTyped.on('end', () => {
cb(null, resTyped.data)
})
})
.end((err, res) => {
@@ -442,24 +510,27 @@ describe('Upload/Download Routes @api-rest', () => {
})
it('Should properly download an object, with all its children, into a text/plain response', (done) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
request(app)
.get(`/objects/${testStream.id}/${parentId}`)
.set('Authorization', userA.token)
.set('Accept', 'text/plain')
.buffer()
.parse((res, cb) => {
res.data = ''
res.on('data', (chunk) => {
res.data += chunk.toString()
const resTyped = res as typeof res & { data: string }
resTyped.data = ''
resTyped.on('data', (chunk) => {
resTyped.data += chunk.toString()
})
res.on('end', () => {
cb(null, res.data)
resTyped.on('end', () => {
cb(null, resTyped.data)
})
})
.end((err, res) => {
if (err) done(err)
try {
const o = res.body.split('\n').filter((l) => l !== '')
const o = res.body.split('\n').filter((l: string) => l !== '')
expect(o.length).to.equal(numObjs + 1)
expect(res).to.be.text
done()
@@ -474,6 +545,7 @@ describe('Upload/Download Routes @api-rest', () => {
for (let i = 0; i < objBatches[0].length; i++) {
objectIds.push(objBatches[0][i].id)
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
request(app)
.post(`/api/getobjects/${testStream.id}`)
.set('Authorization', userA.token)
@@ -481,18 +553,20 @@ describe('Upload/Download Routes @api-rest', () => {
.send({ objects: JSON.stringify(objectIds) })
.buffer()
.parse((res, cb) => {
res.data = ''
res.on('data', (chunk) => {
res.data += chunk.toString()
const resTyped = res as typeof res & { data: string }
resTyped.data = ''
resTyped.on('data', (chunk) => {
resTyped.data += chunk.toString()
})
res.on('end', () => {
cb(null, res.data)
resTyped.on('end', () => {
cb(null, resTyped.data)
})
})
.end((err, res) => {
if (err) done(err)
try {
const o = res.body.split('\n').filter((l) => l !== '')
const o = res.body.split('\n').filter((l: string) => l !== '')
expect(o.length).to.equal(objectIds.length)
expect(res).to.be.text
done()
@@ -530,7 +604,7 @@ describe('Upload/Download Routes @api-rest', () => {
for (let i = 0; i < objBatches[0].length; i++) {
objectIds.push(objBatches[0][i].id)
}
const fakeIds = []
const fakeIds: string[] = []
for (let i = 0; i < 100; i++) {
const fakeId = crypto
.createHash('md5')
@@ -540,18 +614,21 @@ describe('Upload/Download Routes @api-rest', () => {
objectIds.push(fakeId)
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
request(app)
.post(`/api/diff/${testStream.id}`)
.set('Authorization', userA.token)
.send({ objects: JSON.stringify(objectIds) })
.buffer()
.parse((res, cb) => {
res.data = ''
res.on('data', (chunk) => {
res.data += chunk.toString()
const resTyped = res as typeof res & { data: string }
resTyped.data = ''
resTyped.on('data', (chunk) => {
resTyped.data += chunk.toString()
})
res.on('end', () => {
cb(null, res.data)
resTyped.on('end', () => {
cb(null, resTyped.data)
})
})
.end((err, res) => {
@@ -590,7 +667,7 @@ describe('Upload/Download Routes @api-rest', () => {
})
describe('Express @core-rest', () => {
let app
let app: Express.Express
before(async () => {
;({ app } = await beforeEachContext())
})
@@ -73,8 +73,12 @@ import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/pr
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import {
deleteAllResourceInvitesFactory,
deleteInvitesByTargetFactory,
deleteServerOnlyInvitesFactory,
findInviteFactory,
findUserByTargetFactory,
insertInviteAndDeleteOldFactory
insertInviteAndDeleteOldFactory,
updateAllInviteTargetsFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
@@ -94,6 +98,24 @@ import {
import { changeUserRoleFactory } from '@/modules/core/services/users/management'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import { createObjectFactory } from '@/modules/core/services/objects/management'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory,
findEmailFactory
} from '@/modules/core/repositories/userEmails'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
@@ -129,6 +151,51 @@ const createCommitByBranchName = createCommitByBranchNameFactory({
getBranchById: getBranchByIdFactory({ db })
})
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -147,7 +214,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -54,7 +54,9 @@ import {
insertInviteAndDeleteOldFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory,
deleteAllUserInvitesFactory
deleteAllUserInvitesFactory,
findInviteFactory,
deleteInvitesByTargetFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
@@ -95,7 +97,10 @@ import {
changeUserRoleFactory
} from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { dbLogger } from '@/observability/logging'
import {
storeApiTokenFactory,
@@ -114,6 +119,15 @@ import { getServerInfoFactory } from '@/modules/core/repositories/server'
import { getPaginatedBranchCommitsItemsByNameFactory } from '@/modules/core/services/commit/retrieval'
import { getPaginatedStreamBranchesFactory } from '@/modules/core/services/branch/retrieval'
import { createObjectFactory } from '@/modules/core/services/objects/management'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
const getServerInfo = getServerInfoFactory({ db })
const getUser = legacyGetUserFactory({ db })
@@ -141,6 +155,51 @@ const createCommitByBranchName = createCommitByBranchNameFactory({
getBranchById: getBranchByIdFactory({ db })
})
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -159,7 +218,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser: getUserFactory({ db }),
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -11,7 +11,8 @@ import { wait } from '@speckle/shared'
import { createAuthedTestContext, ServerAndContext } from '@/test/graphqlHelper'
import {
createStreamFactory,
getStreamFactory
getStreamFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import { db } from '@/db/knex'
import {
@@ -21,7 +22,9 @@ import {
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import {
deleteInvitesByTargetFactory,
deleteServerOnlyInvitesFactory,
findInviteFactory,
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
updateAllInviteTargetsFactory
@@ -48,8 +51,20 @@ import { sendEmail } from '@/modules/emails/services/sending'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
// To ensure that the invites are created in the correct order, we need to wait a bit between each creation
const WAIT_TIMEOUT = 5
@@ -58,6 +73,52 @@ const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
const getUsers = getUsersFactory({ db })
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -76,7 +137,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -264,7 +326,10 @@ describe('[Admin users list]', () => {
userId
},
ownerId
)
).then((invite) => ({
inviteId: invite.id,
token: invite.token
}))
)
}
@@ -11,7 +11,8 @@ import cryptoRandomString from 'crypto-random-string'
import { noErrors } from '@/test/helpers'
import {
createStreamFactory,
getStreamFactory
getStreamFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import { db } from '@/db/knex'
import {
@@ -21,7 +22,9 @@ import {
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import {
deleteInvitesByTargetFactory,
deleteServerOnlyInvitesFactory,
findInviteFactory,
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
updateAllInviteTargetsFactory
@@ -47,7 +50,10 @@ import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repos
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { sendEmail } from '@/modules/emails/services/sending'
import { createTokenFactory } from '@/modules/core/services/tokens'
import {
@@ -57,11 +63,66 @@ import {
} from '@/modules/core/repositories/tokens'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import { TIME_MS } from '@speckle/shared'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
const getUsers = getUsersFactory({ db })
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -80,7 +141,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -1,7 +1,8 @@
import cryptoRandomString from 'crypto-random-string'
import {
createStreamFactory,
getStreamFactory
getStreamFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import { db } from '@/db/knex'
import {
@@ -11,7 +12,9 @@ import {
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import {
deleteInvitesByTargetFactory,
deleteServerOnlyInvitesFactory,
findInviteFactory,
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
updateAllInviteTargetsFactory
@@ -40,7 +43,10 @@ import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repos
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { sendEmail } from '@/modules/emails/services/sending'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import { manageFileImportExpiryFactory } from '@/modules/fileuploads/services/tasks'
@@ -59,11 +65,66 @@ import { sleep } from '@/test/helpers'
import { expect } from 'chai'
import { FileUploadConvertedStatus } from '@/modules/fileuploads/helpers/types'
import { TIME } from '@speckle/shared'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
const getUsers = getUsersFactory({ db })
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -82,7 +143,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -18,6 +18,11 @@ export type ServerInvitesEventsPayloads = {
invite: ServerInviteRecord
finalizerUserId: string
accept: boolean
/**
* finalizerUserId will always be the invite target. This field will be the actual person triggering the action,
* which in auto-accept flows will be the initial inviter. Use this for reporting.
*/
trueFinalizerUserId: string
}
[ServerInvitesEvents.Canceled]: {
invite: ServerInviteRecord
@@ -35,6 +35,12 @@ export type PrimaryInviteResourceTarget<
* If invite also has secondary resource targets, you can specify the expected roles here
*/
secondaryResourceRoles?: Partial<ResourceTargetTypeRoleTypeMap>
/**
* Whether the invite should be auto accepted or not. If this is true, no invite is actually created or email sent,
* and the accept process is done automatically without user involvement.
*/
autoAccept?: boolean
}
export type ServerInviteResourceTarget = InviteResourceTarget<
@@ -110,6 +110,35 @@ const buildCollectAndValidateResourceTargets = () =>
getStream
})
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaborator
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification
}),
collectAndValidateResourceTargets: buildCollectAndValidateResourceTargets(),
getUser,
getServerInfo
})
const buildCreateAndSendServerOrProjectInvite = () =>
createAndSendInviteFactory({
findUserByTarget: findUserByTargetFactory({ db }),
@@ -124,7 +153,8 @@ const buildCreateAndSendServerOrProjectInvite = () =>
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
})
export = {
@@ -375,33 +405,7 @@ export = {
streamId: projectId //legacy
})
const useProjectInvite = useProjectInviteAndNotifyFactory({
finalizeInvite: finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaborator
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification
}),
collectAndValidateResourceTargets: buildCollectAndValidateResourceTargets(),
getUser,
getServerInfo
})
finalizeInvite: buildFinalizeProjectInvite()
})
await withOperationLogging(
@@ -617,33 +621,7 @@ export = {
async use(_parent, args, ctx) {
const logger = ctx.log
const useProjectInvite = useProjectInviteAndNotifyFactory({
finalizeInvite: finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaborator
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification
}),
collectAndValidateResourceTargets: buildCollectAndValidateResourceTargets(),
getUser,
getServerInfo
})
finalizeInvite: buildFinalizeProjectInvite()
})
await withOperationLogging(
@@ -69,6 +69,10 @@ export const isProjectResourceTarget = (
): target is ProjectInviteResourceTarget =>
target.resourceType === ProjectInviteResourceType
export const isPrimaryResourceTarget = (
target: InviteResourceTarget
): target is PrimaryInviteResourceTarget => 'primary' in target && !!target.primary
export interface ResourceTargetTypeRoleTypeMap {
[ServerInviteResourceType]: ServerRoles
[ProjectInviteResourceType]: StreamRoles
@@ -19,6 +19,7 @@ import {
BuildInviteEmailContents,
CollectAndValidateResourceTargets,
CreateAndSendInvite,
FinalizeInvite,
ResendInviteEmail
} from '@/modules/serverinvites/services/operations'
import { renderEmail } from '@/modules/emails/services/emailRendering'
@@ -91,7 +92,8 @@ export const createAndSendInviteFactory =
buildInviteEmailContents,
emitEvent,
getUser,
getServerInfo
getServerInfo,
finalizeInvite
}: {
findUserByTarget: FindUserByTarget
insertInviteAndDeleteOld: InsertInviteAndDeleteOld
@@ -100,6 +102,7 @@ export const createAndSendInviteFactory =
emitEvent: EventBusEmit
getUser: GetUser
getServerInfo: GetServerInfo
finalizeInvite: FinalizeInvite
}): CreateAndSendInvite =>
async (params, inviterResourceAccessLimits?) => {
const sendInviteEmail = sendInviteEmailFactory({ buildInviteEmailContents })
@@ -165,6 +168,19 @@ export const createAndSendInviteFactory =
targetUser ? [targetUser.email, buildUserTarget(targetUser.id)!] : []
)
const autoAccept = finalPrimaryResource.autoAccept
if (autoAccept && targetUser?.id) {
await finalizeInvite({
finalizerUserId: targetUser.id,
finalizerResourceAccessLimits: inviterResourceAccessLimits,
accept: true,
token: invite.token,
resourceType: finalPrimaryResource.resourceType,
trueFinalizerId: inviterId
})
return
}
// generate and send email
await sendInviteEmail({
invite: finalInvite,
@@ -180,11 +196,6 @@ export const createAndSendInviteFactory =
invite: finalInvite
}
})
return {
inviteId: invite.id,
token: invite.token
}
}
/**
@@ -21,7 +21,7 @@ export type InviteResult = {
export type CreateAndSendInvite = (
params: CreateInviteParams,
inviterResourceAccessLimits: MaybeNullOrUndefined<TokenResourceIdentifier[]>
) => Promise<InviteResult>
) => Promise<void>
export type FinalizeInvite = (params: {
finalizerUserId: string
@@ -35,6 +35,11 @@ export type FinalizeInvite = (params: {
* If the invite is accepted, the email will be attached to the user account as well in a verified state.
*/
allowAttachingNewEmail?: boolean
/**
* Allow someone else besides the target user to finalize the invite. Used in auto-accept flows. The finalizerUserId
* must be the target of the invite, but this different one will be used in reporting/activityStream actions
*/
trueFinalizerId?: string
}) => Promise<void>
export type ResendInviteEmail = (params: {
@@ -80,6 +85,9 @@ export enum InviteFinalizationAction {
*/
export type ValidateResourceInviteBeforeFinalization = (params: {
invite: ServerInviteRecord
/**
* Not necessarily the invite target, can also be the inviter in case of auto-accept
*/
finalizerUserId: string
finalizerResourceAccessLimits: MaybeNullOrUndefined<TokenResourceIdentifier[]>
action: InviteFinalizationAction
@@ -202,7 +202,8 @@ export const finalizeResourceInviteFactory =
token,
resourceType,
finalizerResourceAccessLimits,
allowAttachingNewEmail
allowAttachingNewEmail,
trueFinalizerId
} = params
const finalizerUserTarget = buildUserTarget(finalizerUserId)
@@ -318,7 +319,8 @@ export const finalizeResourceInviteFactory =
payload: {
invite,
accept,
finalizerUserId
finalizerUserId,
trueFinalizerUserId: trueFinalizerId || finalizerUserId
}
})
}
@@ -490,14 +490,12 @@ describe('[Stream & Server Invites]', () => {
})
// Creating some invites
await Promise.all(
invites.map((i) =>
createInviteDirectly(i, me.id).then((o) => {
i.inviteId = o.inviteId
i.token = o.token
})
)
)
for (const invite of invites) {
await createInviteDirectly(invite, me.id).then((o) => {
invite.inviteId = o.id
invite.token = o.token
})
}
})
it('they can resend pre-existing invites irregardless of type', async () => {
@@ -568,14 +566,12 @@ describe('[Stream & Server Invites]', () => {
}
]
await Promise.all(
deletableInvites.map((i) =>
createInviteDirectly(i, me.id).then((o) => {
i.inviteId = o.inviteId
i.token = o.token
})
)
)
for (const deletableInvite of deletableInvites) {
await createInviteDirectly(deletableInvite, me.id).then((o) => {
deletableInvite.inviteId = o.id
deletableInvite.token = o.token
})
}
// Delete all invites
for (const invite of deletableInvites) {
@@ -695,7 +691,7 @@ describe('[Stream & Server Invites]', () => {
// Create an invite before each test so that we can mutate them
// in each test as needed
await createInviteDirectly(inviteFromOtherGuy, otherGuy.id).then((o) => {
inviteFromOtherGuy.inviteId = o.inviteId
inviteFromOtherGuy.inviteId = o.id
inviteFromOtherGuy.token = o.token
})
})
@@ -804,23 +800,21 @@ describe('[Stream & Server Invites]', () => {
])
// Create a couple of static invites that shouldn't be mutated in tests
await Promise.all([
createInviteDirectly(myInvite, me.id).then((o) => {
myInvite.inviteId = o.inviteId
myInvite.token = o.token
}),
createInviteDirectly(otherGuysInvite, otherGuy.id).then((o) => {
otherGuysInvite.inviteId = o.inviteId
otherGuysInvite.token = o.token
})
])
await createInviteDirectly(myInvite, me.id).then((o) => {
myInvite.inviteId = o.id
myInvite.token = o.token
})
await createInviteDirectly(otherGuysInvite, otherGuy.id).then((o) => {
otherGuysInvite.inviteId = o.id
otherGuysInvite.token = o.token
})
})
beforeEach(async () => {
// Create an invite before each test so that we can mutate them
// in each test as needed
await createInviteDirectly(dynamicInvite, me.id).then((o) => {
dynamicInvite.inviteId = o.inviteId
dynamicInvite.inviteId = o.id
dynamicInvite.token = o.token
})
})
@@ -895,24 +889,22 @@ describe('[Stream & Server Invites]', () => {
await createTestUser(ownInvitesGuy)
// Invite him to a few streams
await Promise.all([
createInviteDirectly(
{
stream: myPrivateStream,
// SPecifically w/ email
email: ownInvitesGuy.email
},
me.id
),
createInviteDirectly(
{
// Specifically w/ id
userId: ownInvitesGuy.id,
stream: otherGuysStream
},
otherGuy.id
)
])
await createInviteDirectly(
{
stream: myPrivateStream,
// SPecifically w/ email
email: ownInvitesGuy.email
},
me.id
)
await createInviteDirectly(
{
// Specifically w/ id
userId: ownInvitesGuy.id,
stream: otherGuysStream
},
otherGuy.id
)
// Build authenticated apollo instance
apollo = await testApolloServer({ authUserId: ownInvitesGuy.id })
@@ -27,6 +27,7 @@ import {
import {
createStreamFactory,
getStreamFactory,
grantStreamPermissionsFactory,
markCommitStreamUpdatedFactory
} from '@/modules/core/repositories/streams'
import {
@@ -40,7 +41,9 @@ import {
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import {
deleteInvitesByTargetFactory,
deleteServerOnlyInvitesFactory,
findInviteFactory,
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
updateAllInviteTargetsFactory
@@ -66,7 +69,10 @@ import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { createPersonalAccessTokenFactory } from '@/modules/core/services/tokens'
import {
storeApiTokenFactory,
@@ -76,9 +82,19 @@ import {
} from '@/modules/core/repositories/tokens'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import { createObjectsFactory } from '@/modules/core/services/objects/management'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
const getServerInfo = getServerInfoFactory({ db })
const getUsers = getUsersFactory({ db })
const getUser = getUserFactory({ db })
const markCommitStreamUpdated = markCommitStreamUpdatedFactory({ db })
const getObject = getObjectFactory({ db })
const createCommitByBranchId = createCommitByBranchIdFactory({
@@ -99,6 +115,50 @@ const createCommitByBranchName = createCommitByBranchNameFactory({
})
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -117,7 +177,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser: getUserFactory({ db }),
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -124,6 +124,7 @@ export const dispatchStreamEventFactory =
stream?: StreamWithOptionalRole
userId?: string | null
user?: Partial<UserWithOptionalRole> | null
test?: string
}
}) => {
const payload: typeof eventPayload & {
@@ -7,7 +7,8 @@ import { createBranchFactory } from '@/modules/core/repositories/branches'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import {
createStreamFactory,
getStreamFactory
getStreamFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import {
createUserEmailFactory,
@@ -32,7 +33,9 @@ import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import {
deleteInvitesByTargetFactory,
deleteServerOnlyInvitesFactory,
findInviteFactory,
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
updateAllInviteTargetsFactory
@@ -40,13 +43,25 @@ import {
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { truncateTables } from '@/test/hooks'
import { expect } from 'chai'
import crs from 'crypto-random-string'
import { cleanOrphanedWebhookConfigsFactory } from '@/modules/webhooks/repositories/cleanup'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
const WEBHOOKS_CONFIG_TABLE = 'webhooks_config'
const WEBHOOKS_EVENTS_TABLE = 'webhooks_events'
@@ -59,6 +74,52 @@ const getServerInfo = getServerInfoFactory({ db })
const getUsers = getUsersFactory({ db })
const getUser = getUserFactory({ db })
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -77,7 +138,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -1,11 +1,11 @@
/* istanbul ignore file */
const expect = require('chai').expect
const assert = require('assert')
import { expect } from 'chai'
import assert from 'assert'
const { beforeEachContext, initializeTestServer } = require('@/test/hooks')
const { noErrors } = require('@/test/helpers')
const { Scopes, Roles } = require('@speckle/shared')
const {
import { beforeEachContext, initializeTestServer } from '@/test/hooks'
import { noErrors } from '@/test/helpers'
import { Scopes, Roles, ensureError } from '@speckle/shared'
import {
createWebhookConfigFactory,
countWebhooksByStreamIdFactory,
getWebhookByIdFactory,
@@ -14,78 +14,77 @@ const {
getStreamWebhooksFactory,
createWebhookEventFactory,
getLastWebhookEventsFactory
} = require('@/modules/webhooks/repositories/webhooks')
const { db } = require('@/db/knex')
const {
} from '@/modules/webhooks/repositories/webhooks'
import { db } from '@/db/knex'
import {
createWebhookFactory,
updateWebhookFactory,
deleteWebhookFactory,
dispatchStreamEventFactory
} = require('@/modules/webhooks/services/webhooks')
const {
} from '@/modules/webhooks/services/webhooks'
import {
getStreamFactory,
createStreamFactory,
grantStreamPermissionsFactory
} = require('@/modules/core/repositories/streams')
const {
} from '@/modules/core/repositories/streams'
import {
legacyCreateStreamFactory,
createStreamReturnRecordFactory
} = require('@/modules/core/services/streams/management')
const {
inviteUsersToProjectFactory
} = require('@/modules/serverinvites/services/projectInviteManagement')
const {
createAndSendInviteFactory
} = require('@/modules/serverinvites/services/creation')
const {
} from '@/modules/core/services/streams/management'
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import {
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory
} = require('@/modules/serverinvites/repositories/serverInvites')
const {
collectAndValidateCoreTargetsFactory
} = require('@/modules/serverinvites/services/coreResourceCollection')
const {
buildCoreInviteEmailContentsFactory
} = require('@/modules/serverinvites/services/coreEmailContents')
const { getEventBus } = require('@/modules/shared/services/eventBus')
const { createBranchFactory } = require('@/modules/core/repositories/branches')
const {
updateAllInviteTargetsFactory,
findInviteFactory,
deleteInvitesByTargetFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { createBranchFactory } from '@/modules/core/repositories/branches'
import {
getUserFactory,
getUsersFactory,
storeUserFactory,
countAdminUsersFactory,
storeUserAclFactory
} = require('@/modules/core/repositories/users')
const {
} from '@/modules/core/repositories/users'
import {
findEmailFactory,
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory
} = require('@/modules/core/repositories/userEmails')
const {
requestNewEmailVerificationFactory
} = require('@/modules/emails/services/verification/request')
const {
deleteOldAndInsertNewVerificationFactory
} = require('@/modules/emails/repositories')
const { renderEmail } = require('@/modules/emails/services/emailRendering')
const { sendEmail } = require('@/modules/emails/services/sending')
const { createUserFactory } = require('@/modules/core/services/users/management')
const {
validateAndCreateUserEmailFactory
} = require('@/modules/core/services/userEmails')
const {
finalizeInvitedServerRegistrationFactory
} = require('@/modules/serverinvites/services/processing')
const { createPersonalAccessTokenFactory } = require('@/modules/core/services/tokens')
const {
} from '@/modules/core/repositories/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { createPersonalAccessTokenFactory } from '@/modules/core/services/tokens'
import {
storeApiTokenFactory,
storeTokenScopesFactory,
storeTokenResourceAccessDefinitionsFactory,
storePersonalApiTokenFactory
} = require('@/modules/core/repositories/tokens')
const { getServerInfoFactory } = require('@/modules/core/repositories/server')
} from '@/modules/core/repositories/tokens'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
import { omit } from 'lodash'
const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
@@ -95,6 +94,52 @@ const updateWebhook = updateWebhookFactory({
updateWebhookConfig: updateWebhookConfigFactory({ db })
})
const getStreamWebhooks = getStreamWebhooksFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -113,7 +158,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
@@ -161,27 +207,32 @@ const createPersonalAccessToken = createPersonalAccessTokenFactory({
describe('Webhooks @webhooks', () => {
const getWebhook = getWebhookByIdFactory({ db })
let sendRequest
let sendRequest: Awaited<ReturnType<typeof initializeTestServer>>['sendRequest']
const userOne = {
name: 'User',
email: 'user@example.org',
password: 'jdsadjsadasfdsa'
password: 'jdsadjsadasfdsa',
id: '',
token: ''
}
const streamOne = {
name: 'streamOne',
description: 'stream',
isPublic: true
isPublic: true,
ownerId: '',
id: ''
}
const webhookOne = {
streamId: null, // filled in `before`
streamId: '', // filled in `before`
url: 'http://localhost:42/non-existent',
description: 'test wh',
secret: 'secret',
enabled: true,
triggers: ['commit_create', 'commit_update']
triggers: ['commit_create', 'commit_update'],
id: ''
}
before(async () => {
@@ -209,7 +260,7 @@ describe('Webhooks @webhooks', () => {
const webhook = await getWebhook({ id: webhookOne.id })
expect(webhook).to.not.be.null
expect(webhook).to.have.property('url')
expect(webhook.url).to.equal(webhookOne.url)
expect(webhook!.url).to.equal(webhookOne.url)
})
it('Should update a webhook', async () => {
@@ -223,7 +274,7 @@ describe('Webhooks @webhooks', () => {
const webhook = await getWebhook({ id: webhookId })
expect(webhook).to.not.be.null
expect(webhook).to.have.property('url')
expect(webhook.url).to.equal(newUrl)
expect(webhook!.url).to.equal(newUrl)
})
it('Should delete a webhook', async () => {
@@ -240,7 +291,8 @@ describe('Webhooks @webhooks', () => {
description: 'test wh',
secret: 'secret',
enabled: true,
triggers: ['commit_create', 'commit_update']
triggers: ['commit_create', 'commit_update'],
id: ''
}
webhook.id = await createWebhookFactory({
createWebhookConfig: createWebhookConfigFactory({ db }),
@@ -307,7 +359,6 @@ describe('Webhooks @webhooks', () => {
countWebhooksByStreamId: countWebhooksByStreamIdFactory({ db })
})(webhook)
await dispatchStreamEventFactory({
db,
getServerInfo,
getStream,
createWebhookEvent: createWebhookEventFactory({ db }),
@@ -328,22 +379,27 @@ describe('Webhooks @webhooks', () => {
const userTwo = {
name: 'User2',
email: 'user2@example.org',
password: 'jdsadjsadasfdsa'
password: 'jdsadjsadasfdsa',
id: '',
token: ''
}
const webhookTwo = {
streamId: null,
streamId: '',
url: 'http://localhost:42/non-existent-two',
description: 'test wh no 2',
secret: 'secret',
enabled: true,
triggers: ['commit_create', 'commit_update']
triggers: ['commit_create', 'commit_update'],
id: ''
}
const streamTwo = {
name: 'streamTwo',
description: 'stream',
isPublic: true
isPublic: true,
ownerId: '',
id: ''
}
before(async () => {
@@ -373,7 +429,7 @@ describe('Webhooks @webhooks', () => {
const res = await sendRequest(userTwo.token, {
query:
'mutation createWebhook($webhook: WebhookCreateInput!) { webhookCreate( webhook: $webhook ) }',
variables: { webhook: webhookTwo }
variables: { webhook: omit(webhookTwo, ['id']) }
})
expect(noErrors(res))
expect(res.body.data.webhookCreate).to.not.be.null
@@ -382,7 +438,6 @@ describe('Webhooks @webhooks', () => {
it('Should get stream webhooks and the previous events', async () => {
await dispatchStreamEventFactory({
db,
getServerInfo,
getStream,
getStreamWebhooks: getStreamWebhooksFactory({ db }),
@@ -420,9 +475,9 @@ describe('Webhooks @webhooks', () => {
})
const webhook = await getWebhook({ id: webhookTwo.id })
expect(noErrors(res))
expect(res.body.data.webhookUpdate).to.equal(webhook.id)
expect(webhook.description).to.equal('updated webhook')
expect(webhook.enabled).to.equal(false)
expect(res.body.data.webhookUpdate).to.equal(webhook!.id)
expect(webhook!.description).to.equal('updated webhook')
expect(webhook!.enabled).to.equal(false)
})
it('Should *not* update or delete a webhook if the stream id and webhook id do not match', async () => {
@@ -461,7 +516,8 @@ describe('Webhooks @webhooks', () => {
description: 'test wh',
secret: 'secret',
enabled: true,
triggers: ['commit_create', 'commit_update']
triggers: ['commit_create', 'commit_update'],
id: ''
}
webhook.id = await createWebhookFactory({
createWebhookConfig: createWebhookConfigFactory({ db }),
@@ -475,11 +531,11 @@ describe('Webhooks @webhooks', () => {
})
it('Should *not* create a webhook if user is not a stream owner', async () => {
delete webhookTwo.id
webhookTwo.id = ''
const res = await sendRequest(userOne.token, {
query:
'mutation createWebhook($webhook: WebhookCreateInput!) { webhookCreate( webhook: $webhook ) }',
variables: { webhook: webhookTwo }
variables: { webhook: omit(webhookTwo, ['id']) }
})
expect(res.body.errors).to.exist
expect(res.body.errors[0].extensions.code).to.equal('FORBIDDEN')
@@ -525,7 +581,7 @@ describe('Webhooks @webhooks', () => {
countWebhooksByStreamId: countWebhooksByStreamIdFactory({ db })
})(webhook)
} catch (err) {
if (err.toString().indexOf('Maximum') > -1) return
if (ensureError(err).toString().indexOf('Maximum') > -1) return
}
assert.fail('Configured more webhooks than the limit')
@@ -217,6 +217,10 @@ import {
throwIfAuthNotOk
} from '@/modules/shared/helpers/errorHelper'
import { withOperationLogging } from '@/observability/domain/businessLogging'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import { WorkspaceInvitesLimit } from '@/modules/workspaces/domain/constants'
const eventBus = getEventBus()
@@ -255,6 +259,90 @@ const buildCollectAndValidateResourceTargets = () =>
})
})
const buildFinalizeWorkspaceInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({
db,
filterQuery: workspaceInviteValidityFilter
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: ({ eventName, payload }) =>
getEventBus().emit({
eventName,
payload
}),
validateInvite: validateWorkspaceInviteBeforeFinalizationFactory({
getWorkspace: getWorkspaceFactory({ db })
}),
processInvite: processFinalizedWorkspaceInviteFactory({
getWorkspace: getWorkspaceFactory({ db }),
updateWorkspaceRole: updateWorkspaceRoleFactory({
getWorkspaceWithDomains: getWorkspaceWithDomainsFactory({ db }),
findVerifiedEmailsByUserId: findVerifiedEmailsByUserIdFactory({ db }),
getWorkspaceRoles: getWorkspaceRolesFactory({ db }),
upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db }),
emitWorkspaceEvent: getEventBus().emit,
ensureValidWorkspaceRoleSeat: ensureValidWorkspaceRoleSeatFactory({
createWorkspaceSeat: createWorkspaceSeatFactory({ db }),
getWorkspaceUserSeat: getWorkspaceUserSeatFactory({ db }),
eventEmit: getEventBus().emit
})
})
}),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification
}),
collectAndValidateResourceTargets: buildCollectAndValidateResourceTargets(),
getUser,
getServerInfo
})
const validateStreamAccess = validateStreamAccessFactory({ authorizeResolver })
const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({
validateStreamAccess,
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaborator
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification
}),
collectAndValidateResourceTargets: buildCollectAndValidateResourceTargets(),
getUser,
getServerInfo
})
const buildCreateAndSendServerOrProjectInvite = () =>
createAndSendInviteFactory({
findUserByTarget: findUserByTargetFactory({ db }),
@@ -269,7 +357,8 @@ const buildCreateAndSendServerOrProjectInvite = () =>
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
})
const buildCreateAndSendWorkspaceInvite = () =>
@@ -287,9 +376,9 @@ const buildCreateAndSendWorkspaceInvite = () =>
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeWorkspaceInvite()
})
const validateStreamAccess = validateStreamAccessFactory({ authorizeResolver })
const isStreamCollaborator = isStreamCollaboratorFactory({
getStream
})
@@ -1226,52 +1315,7 @@ export = FF_WORKSPACES_MODULE_ENABLED
use: async (_parent, args, ctx) => {
const logger = ctx.log
const finalizeInvite = finalizeResourceInviteFactory({
findInvite: findInviteFactory({
db,
filterQuery: workspaceInviteValidityFilter
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: ({ eventName, payload }) =>
getEventBus().emit({
eventName,
payload
}),
validateInvite: validateWorkspaceInviteBeforeFinalizationFactory({
getWorkspace: getWorkspaceFactory({ db })
}),
processInvite: processFinalizedWorkspaceInviteFactory({
getWorkspace: getWorkspaceFactory({ db }),
updateWorkspaceRole: updateWorkspaceRoleFactory({
getWorkspaceWithDomains: getWorkspaceWithDomainsFactory({ db }),
findVerifiedEmailsByUserId: findVerifiedEmailsByUserIdFactory({ db }),
getWorkspaceRoles: getWorkspaceRolesFactory({ db }),
upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db }),
emitWorkspaceEvent: getEventBus().emit,
ensureValidWorkspaceRoleSeat: ensureValidWorkspaceRoleSeatFactory({
createWorkspaceSeat: createWorkspaceSeatFactory({ db }),
getWorkspaceUserSeat: getWorkspaceUserSeatFactory({ db }),
eventEmit: getEventBus().emit
})
})
}),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification
}),
collectAndValidateResourceTargets: buildCollectAndValidateResourceTargets(),
getUser,
getServerInfo
})
const finalizeInvite = buildFinalizeWorkspaceInvite()
await withOperationLogging(
async () =>
await finalizeInvite({
@@ -30,6 +30,7 @@ import {
} from '@/modules/serverinvites/errors'
import {
buildUserTarget,
isPrimaryResourceTarget,
isProjectResourceTarget,
resolveInviteTargetTitle,
resolveTarget
@@ -225,6 +226,11 @@ export const collectAndValidateWorkspaceTargetsFactory =
userId: targetUser.id,
projectRole
})
// If project target is primary and user target is already a workspace member, mark invite as auto-acceptable
if (isPrimaryResourceTarget(projectTarget) && workspaceRole) {
projectTarget.autoAccept = true
}
}
// Do further validation only if we're actually planning to invite to a workspace
@@ -1,11 +1,18 @@
import { db } from '@/db/knex'
import {
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory,
findEmailFactory,
findEmailsByUserIdFactory,
findVerifiedEmailsByUserIdFactory
} from '@/modules/core/repositories/userEmails'
import {
deleteInvitesByTargetFactory,
deleteServerOnlyInvitesFactory,
findInviteFactory,
findUserByTargetFactory,
insertInviteAndDeleteOldFactory
insertInviteAndDeleteOldFactory,
updateAllInviteTargetsFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import { getEventBus } from '@/modules/shared/services/eventBus'
@@ -19,12 +26,15 @@ import {
getWorkspaceDomainsFactory,
storeWorkspaceDomainFactory,
getWorkspaceBySlugFactory,
getWorkspaceRoleForUserFactory
getWorkspaceRoleForUserFactory,
workspaceInviteValidityFilter
} from '@/modules/workspaces/repositories/workspaces'
import {
buildWorkspaceInviteEmailContentsFactory,
collectAndValidateWorkspaceTargetsFactory,
createWorkspaceInviteFactory
createWorkspaceInviteFactory,
processFinalizedWorkspaceInviteFactory,
validateWorkspaceInviteBeforeFinalizationFactory
} from '@/modules/workspaces/services/invites'
import {
createWorkspaceFactory,
@@ -92,6 +102,16 @@ import {
getWorkspaceSeatTypeToProjectRoleMappingFactory,
validateWorkspaceMemberProjectRoleFactory
} from '@/modules/workspaces/services/projects'
import { captureCreatedInvite } from '@/test/speckle-helpers/inviteHelper'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
@@ -378,10 +398,9 @@ export const createWorkspaceInviteDirectly = async (
const getServerInfo = getServerInfoFactory({ db })
const getStream = getStreamFactory({ db })
const getUser = getUserFactory({ db })
const createAndSendInvite = createAndSendInviteFactory({
findUserByTarget: findUserByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
collectAndValidateResourceTargets: collectAndValidateWorkspaceTargetsFactory({
const buildCollectAndValidateResourceTargets = () =>
collectAndValidateWorkspaceTargetsFactory({
getStream,
getWorkspace: getWorkspaceFactory({ db }),
getWorkspaceDomains: getWorkspaceDomainsFactory({ db }),
@@ -400,7 +419,68 @@ export const createWorkspaceInviteDirectly = async (
getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db })
})
})
}),
})
const buildFinalizeWorkspaceInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({
db,
filterQuery: workspaceInviteValidityFilter
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: ({ eventName, payload }) =>
getEventBus().emit({
eventName,
payload
}),
validateInvite: validateWorkspaceInviteBeforeFinalizationFactory({
getWorkspace: getWorkspaceFactory({ db })
}),
processInvite: processFinalizedWorkspaceInviteFactory({
getWorkspace: getWorkspaceFactory({ db }),
updateWorkspaceRole: updateWorkspaceRoleFactory({
getWorkspaceWithDomains: getWorkspaceWithDomainsFactory({ db }),
findVerifiedEmailsByUserId: findVerifiedEmailsByUserIdFactory({ db }),
getWorkspaceRoles: getWorkspaceRolesFactory({ db }),
upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db }),
emitWorkspaceEvent: getEventBus().emit,
ensureValidWorkspaceRoleSeat: ensureValidWorkspaceRoleSeatFactory({
createWorkspaceSeat: createWorkspaceSeatFactory({ db }),
getWorkspaceUserSeat: getWorkspaceUserSeatFactory({ db }),
eventEmit: getEventBus().emit
})
})
}),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: buildCollectAndValidateResourceTargets(),
getUser,
getServerInfo
})
const createAndSendInvite = createAndSendInviteFactory({
findUserByTarget: findUserByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
collectAndValidateResourceTargets: buildCollectAndValidateResourceTargets(),
buildInviteEmailContents: buildWorkspaceInviteEmailContentsFactory({
getStream,
getWorkspace: getWorkspaceFactory({ db })
@@ -411,18 +491,22 @@ export const createWorkspaceInviteDirectly = async (
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeWorkspaceInvite()
})
const createInvite = createWorkspaceInviteFactory({
createAndSendInvite
})
return await createInvite({
...args,
inviterId,
inviterResourceAccessRules: null
})
return await captureCreatedInvite(
async () =>
await createInvite({
...args,
inviterId,
inviterResourceAccessRules: null
})
)
}
export const createTestOidcProvider = async (
@@ -559,7 +559,9 @@ describe('Workspaces Invites GQL', () => {
Roles.Stream.Owner,
me.id
)
})
beforeEach(async () => {
// Remove all project access from workspaceMemberWithNoProjectAccess
await Promise.all([
leaveStream(
@@ -630,6 +632,38 @@ describe('Workspaces Invites GQL', () => {
expect(res.data?.projectMutations.invites.createForWorkspace.id).to.not.be.ok
})
it('can invite to workspace project as admin, even if target doesnt belong to workspace', async () => {
const sendEmailInvocations = EmailSendingServiceMock.hijackFunction(
'sendEmail',
async () => true
)
const res = await gqlHelpers.createWorkspaceProjectInvite({
projectId: myProjectInviteTargetWorkspaceProject.id,
inputs: [
{
userId: otherGuy.id,
role: Roles.Stream.Reviewer
}
]
})
expect(res).to.not.haveGraphQLErrors()
expect(res.data?.projectMutations.invites.createForWorkspace.id).to.be.ok
// no auto-accept, since target is not a workspace member
expect(sendEmailInvocations.args).to.have.lengthOf(1)
const emailParams = sendEmailInvocations.args[0][0]
await validateInviteExistanceFromEmail(emailParams)
await gqlHelpers.validateResourceAccess({
shouldHaveAccess: false,
userId: otherGuy.id,
workspaceId: myProjectInviteTargetWorkspace.id,
streamId: myProjectInviteTargetWorkspaceProject.id
})
})
it('can invite to workspace project even if not workspace admin, if target already belongs to workspace', async () => {
const res = await gqlHelpers.createWorkspaceProjectInvite(
{
@@ -652,6 +686,38 @@ describe('Workspaces Invites GQL', () => {
expect(res.data?.projectMutations.invites.createForWorkspace.id).to.be.ok
})
it('invite auto-accepted if both users already belong to the workspace', async () => {
const sendEmailInvocations = EmailSendingServiceMock.hijackFunction(
'sendEmail',
async () => true
)
const res = await gqlHelpers.createWorkspaceProjectInvite({
projectId: myProjectInviteTargetWorkspaceProject.id,
inputs: [
{
userId: workspaceMemberWithNoProjectAccess.id,
role: Roles.Stream.Reviewer
}
]
})
expect(res).to.not.haveGraphQLErrors()
expect(res.data?.projectMutations.invites.createForWorkspace.id).to.be.ok
// No invite email should be sent out, due to auto-accept
expect(sendEmailInvocations.length()).to.eq(0)
// Should have project role
await gqlHelpers.validateResourceAccess({
shouldHaveAccess: true,
userId: workspaceMemberWithNoProjectAccess.id,
workspaceId: myProjectInviteTargetWorkspace.id,
streamId: myProjectInviteTargetWorkspaceProject.id,
expectedProjectRole: Roles.Stream.Reviewer
})
})
it("can't invite a workspace guest to be a workspace project owner", async () => {
const res = await gqlHelpers.createWorkspaceProjectInvite({
projectId: myProjectInviteTargetWorkspaceProject.id,
@@ -1097,7 +1163,7 @@ describe('Workspaces Invites GQL', () => {
},
me.id
)
expect(brokenInvite.inviteId).to.be.ok
expect(brokenInvite.id).to.be.ok
// Db query directly, cause this isn't a supported use case
await Workspaces.knex()
@@ -1569,8 +1635,7 @@ describe('Workspaces Invites GQL', () => {
expect(res).to.not.haveGraphQLErrors()
expect(res.data?.workspaceMutations.invites.use).to.be.ok
expect(await findInviteFactory({ db })({ inviteId: invite.inviteId })).to.be.not
.ok
expect(await findInviteFactory({ db })({ inviteId: invite.id })).to.be.not.ok
await gqlHelpers.validateResourceAccess({
shouldHaveAccess: true,
-152
View File
@@ -1,152 +0,0 @@
require('../bootstrap')
const { createManyObjects } = require('@/test/helpers')
const { fetch } = require('undici')
const { init } = require(`@/app`)
const request = require('supertest')
const { exit } = require('yargs')
const { logger } = require('@/observability/logging')
const { Scopes } = require('@speckle/shared')
const {
getStreamFactory,
createStreamFactory
} = require('@/modules/core/repositories/streams')
const { db } = require('@/db/knex')
const {
legacyCreateStreamFactory,
createStreamReturnRecordFactory
} = require('@/modules/core/services/streams/management')
const {
inviteUsersToProjectFactory
} = require('@/modules/serverinvites/services/projectInviteManagement')
const {
createAndSendInviteFactory
} = require('@/modules/serverinvites/services/creation')
const {
findUserByTargetFactory,
insertInviteAndDeleteOldFactory
} = require('@/modules/serverinvites/repositories/serverInvites')
const {
collectAndValidateCoreTargetsFactory
} = require('@/modules/serverinvites/services/coreResourceCollection')
const {
buildCoreInviteEmailContentsFactory
} = require('@/modules/serverinvites/services/coreEmailContents')
const { getEventBus } = require('@/modules/shared/services/eventBus')
const { createBranchFactory } = require('@/modules/core/repositories/branches')
const {
getUsersFactory,
getUserFactory,
legacyGetUserByEmailFactory
} = require('@/modules/core/repositories/users')
const { createPersonalAccessTokenFactory } = require('@/modules/core/services/tokens')
const {
storeApiTokenFactory,
storeTokenScopesFactory,
storeTokenResourceAccessDefinitionsFactory,
storePersonalApiTokenFactory
} = require('@/modules/core/repositories/tokens')
const { getServerInfoFactory } = require('@/modules/core/repositories/server')
const getServerInfo = getServerInfoFactory({ db })
const getUsers = getUsersFactory({ db })
const getUser = getUserFactory({ db })
const getStream = getStreamFactory({ db })
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
createAndSendInvite: createAndSendInviteFactory({
findUserByTarget: findUserByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
buildInviteEmailContents: buildCoreInviteEmailContentsFactory({
getStream
}),
emitEvent: ({ eventName, payload }) =>
getEventBus().emit({
eventName,
payload
}),
getUser,
getServerInfo
}),
getUsers
}),
createStream: createStreamFactory({ db }),
createBranch: createBranchFactory({ db }),
emitEvent: getEventBus().emit
})
})
const getUserByEmail = legacyGetUserByEmailFactory({ db })
const createPersonalAccessToken = createPersonalAccessTokenFactory({
storeApiToken: storeApiTokenFactory({ db }),
storeTokenScopes: storeTokenScopesFactory({ db }),
storeTokenResourceAccessDefinitions: storeTokenResourceAccessDefinitionsFactory({
db
}),
storePersonalApiToken: storePersonalApiTokenFactory({ db })
})
const main = async () => {
const testStream = {
name: 'Test Stream 01',
description: 'wonderful test stream'
}
// const userA = {
// name: 'd1',
// email: 'd.1@speckle.systems',
// password: 'wowwow8charsplease'
// }
// userA.id = await createUser(userA)
const userA = await getUserByEmail({
email: 'd.1@speckle.systems'
})
userA.token = `Bearer ${await createPersonalAccessToken(
userA.id,
'test token user A',
[
Scopes.Streams.Read,
Scopes.Streams.Write,
Scopes.Users.Read,
Scopes.Users.Email,
Scopes.Tokens.Write,
Scopes.Tokens.Read,
Scopes.Profile.Read,
Scopes.Profile.Email
]
)}`
testStream.id = await createStream({ ...testStream, ownerId: userA.id })
const { app } = await init()
const numObjs = 5000
const objBatch = createManyObjects(numObjs)
const uploadRes = await request(app)
.post(`/objects/${testStream.id}`)
.set('Authorization', userA.token)
.set('Content-type', 'multipart/form-data')
.attach('batch1', Buffer.from(JSON.stringify(objBatch), 'utf8'))
logger.info(uploadRes.status)
const objectIds = objBatch.map((obj) => obj.id)
const res = await fetch(`http://127.0.0.1:3000/api/getobjects/${testStream.id}`, {
method: 'POST',
headers: {
Authorization: userA.token,
'Content-Type': 'application/json',
Accept: 'text/plain'
},
body: JSON.stringify({ objects: JSON.stringify(objectIds) })
})
const data = await res.body.getReader().read()
logger.info(data)
exit(0)
}
main().then(logger.info('created')).catch(logger.error('failed'))
+226
View File
@@ -0,0 +1,226 @@
// eslint-disable-next-line no-restricted-imports
import '../bootstrap'
import { createManyObjects } from '@/test/helpers'
import { fetch } from 'undici'
import { init } from '@/app'
import request from 'supertest'
import { logger } from '@/observability/logging'
import { Scopes } from '@speckle/shared'
import {
getStreamFactory,
createStreamFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import { db } from '@/db/knex'
import {
legacyCreateStreamFactory,
createStreamReturnRecordFactory
} from '@/modules/core/services/streams/management'
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import {
deleteInvitesByTargetFactory,
deleteServerOnlyInvitesFactory,
findInviteFactory,
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
updateAllInviteTargetsFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { createBranchFactory } from '@/modules/core/repositories/branches'
import {
getUsersFactory,
getUserFactory,
legacyGetUserByEmailFactory
} from '@/modules/core/repositories/users'
import { createPersonalAccessTokenFactory } from '@/modules/core/services/tokens'
import {
storeApiTokenFactory,
storeTokenScopesFactory,
storeTokenResourceAccessDefinitionsFactory,
storePersonalApiTokenFactory
} from '@/modules/core/repositories/tokens'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
import {
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory,
findEmailFactory
} from '@/modules/core/repositories/userEmails'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
const getServerInfo = getServerInfoFactory({ db })
const getUsers = getUsersFactory({ db })
const getUser = getUserFactory({ db })
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
createAndSendInvite: createAndSendInviteFactory({
findUserByTarget: findUserByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
buildInviteEmailContents: buildCoreInviteEmailContentsFactory({
getStream
}),
emitEvent: ({ eventName, payload }) =>
getEventBus().emit({
eventName,
payload
}),
getUser,
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),
createStream: createStreamFactory({ db }),
createBranch: createBranchFactory({ db }),
emitEvent: getEventBus().emit
})
})
const getUserByEmail = legacyGetUserByEmailFactory({ db })
const createPersonalAccessToken = createPersonalAccessTokenFactory({
storeApiToken: storeApiTokenFactory({ db }),
storeTokenScopes: storeTokenScopesFactory({ db }),
storeTokenResourceAccessDefinitions: storeTokenResourceAccessDefinitionsFactory({
db
}),
storePersonalApiToken: storePersonalApiTokenFactory({ db })
})
const main = async () => {
const testStream = {
name: 'Test Stream 01',
description: 'wonderful test stream',
id: ''
}
// const userA = {
// name: 'd1',
// email: 'd.1@speckle.systems',
// password: 'wowwow8charsplease'
// }
// userA.id = await createUser(userA)
const userA = {
...(await getUserByEmail({
email: 'd.1@speckle.systems'
}))!,
token: ''
}
userA.token = `Bearer ${await createPersonalAccessToken(
userA.id,
'test token user A',
[
Scopes.Streams.Read,
Scopes.Streams.Write,
Scopes.Users.Read,
Scopes.Users.Email,
Scopes.Tokens.Write,
Scopes.Tokens.Read,
Scopes.Profile.Read,
Scopes.Profile.Email
]
)}`
testStream.id = await createStream({ ...testStream, ownerId: userA.id })
const { app } = await init()
const numObjs = 5000
const objBatch = createManyObjects(numObjs)
const uploadRes = await request(app)
.post(`/objects/${testStream.id}`)
.set('Authorization', userA.token)
.set('Content-type', 'multipart/form-data')
.attach('batch1', Buffer.from(JSON.stringify(objBatch), 'utf8'))
logger.info(uploadRes.status)
const objectIds = objBatch.map((obj) => obj.id)
const res = await fetch(`http://127.0.0.1:3000/api/getobjects/${testStream.id}`, {
method: 'POST',
headers: {
Authorization: userA.token,
'Content-Type': 'application/json',
Accept: 'text/plain'
},
body: JSON.stringify({ objects: JSON.stringify(objectIds) })
})
const data = await res.body!.getReader().read()
logger.info(data)
process.exit(0)
}
main()
.then(() => logger.info('created'))
.catch((err) => logger.error('failed', err))
@@ -1,11 +1,14 @@
import { MaybeAsync, Roles, StreamRoles } from '@speckle/shared'
import { buildUserTarget } from '@/modules/serverinvites/helpers/core'
import { InviteResult } from '@/modules/serverinvites/services/operations'
import {
deleteInvitesByTargetFactory,
deleteServerOnlyInvitesFactory,
findInviteByTokenFactory,
findInviteFactory,
findUserByTargetFactory,
insertInviteAndDeleteOldFactory
insertInviteAndDeleteOldFactory,
updateAllInviteTargetsFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import { BasicTestUser } from '@/test/authHelper'
@@ -17,21 +20,93 @@ import {
ProjectInviteResourceType,
ServerInviteResourceType
} from '@/modules/serverinvites/domain/constants'
import { SendEmailParams } from '@/modules/emails/services/sending'
import { sendEmail, SendEmailParams } from '@/modules/emails/services/sending'
import { db } from '@/db/knex'
import { expect } from 'chai'
import {
PrimaryInviteResourceTarget,
ServerInviteRecord,
ServerInviteResourceTarget
} from '@/modules/serverinvites/domain/types'
import { EmailSendingServiceMock } from '@/test/mocks/global'
import { getStreamFactory } from '@/modules/core/repositories/streams'
import {
getStreamFactory,
grantStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import { getUserFactory } from '@/modules/core/repositories/users'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import {
addOrUpdateStreamCollaboratorFactory,
validateStreamAccessFactory
} from '@/modules/core/services/streams/access'
import { authorizeResolver } from '@/modules/shared'
import {
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory,
findEmailFactory
} from '@/modules/core/repositories/userEmails'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createAndSendInvite = createAndSendInviteFactory({
findUserByTarget: findUserByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
@@ -47,7 +122,8 @@ const createAndSendInvite = createAndSendInviteFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
})
export const createServerInviteDirectly = async (
@@ -89,7 +165,7 @@ export const createStreamInviteDirectly = async (
role?: StreamRoles
},
creatorId: string
): Promise<InviteResult> => {
): Promise<ServerInviteRecord> => {
const userId = invite.userId || invite.user?.id || null
const email = invite.email || null
if (!userId && !email) throw new Error('Either user/userId or email must be set')
@@ -100,19 +176,24 @@ export const createStreamInviteDirectly = async (
const target = email || buildUserTarget(userId!)
if (!target) throw new Error('Cannot create invite without a target')
return await createAndSendInvite(
{
target,
inviterId: creatorId,
message: invite.message,
primaryResourceTarget: {
resourceType: streamId ? ProjectInviteResourceType : ServerInviteResourceType,
resourceId: streamId || '',
role: streamId ? streamRole : Roles.Server.User,
primary: true
}
},
null
return await captureCreatedInvite(
async () =>
await createAndSendInvite(
{
target,
inviterId: creatorId,
message: invite.message,
primaryResourceTarget: {
resourceType: streamId
? ProjectInviteResourceType
: ServerInviteResourceType,
resourceId: streamId || '',
role: streamId ? streamRole : Roles.Server.User,
primary: true
}
},
null
)
)
}
@@ -10,6 +10,11 @@ import {
grantStreamPermissionsFactory,
revokeStreamPermissionsFactory
} from '@/modules/core/repositories/streams'
import {
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory,
findEmailFactory
} from '@/modules/core/repositories/userEmails'
import { getUserFactory, getUsersFactory } from '@/modules/core/repositories/users'
import {
addOrUpdateStreamCollaboratorFactory,
@@ -21,13 +26,30 @@ import {
createStreamReturnRecordFactory,
legacyCreateStreamFactory
} from '@/modules/core/services/streams/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import {
deleteInvitesByTargetFactory,
deleteServerOnlyInvitesFactory,
findInviteFactory,
findUserByTargetFactory,
insertInviteAndDeleteOldFactory
insertInviteAndDeleteOldFactory,
updateAllInviteTargetsFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
import {
processFinalizedProjectInviteFactory,
validateProjectInviteBeforeFinalizationFactory
} from '@/modules/serverinvites/services/coreFinalization'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import {
finalizeInvitedServerRegistrationFactory,
finalizeResourceInviteFactory
} from '@/modules/serverinvites/services/processing'
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { authorizeResolver } from '@/modules/shared'
import { Nullable } from '@/modules/shared/helpers/typeHelper'
@@ -44,6 +66,52 @@ const getServerInfo = getServerInfoFactory({ db })
const getUsers = getUsersFactory({ db })
const getUser = getUserFactory({ db })
const getStream = getStreamFactory({ db })
const buildFinalizeProjectInvite = () =>
finalizeResourceInviteFactory({
findInvite: findInviteFactory({ db }),
validateInvite: validateProjectInviteBeforeFinalizationFactory({
getProject: getStream
}),
processInvite: processFinalizedProjectInviteFactory({
getProject: getStream,
addProjectRole: addOrUpdateStreamCollaboratorFactory({
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver }),
getUser,
grantStreamPermissions: grantStreamPermissionsFactory({ db }),
emitEvent: getEventBus().emit
})
}),
deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }),
insertInviteAndDeleteOld: insertInviteAndDeleteOldFactory({ db }),
emitEvent: (...args) => getEventBus().emit(...args),
findEmail: findEmailFactory({ db }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
findEmail: findEmailFactory({ db }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
findEmail: findEmailFactory({ db }),
getUser,
getServerInfo,
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({
db
}),
renderEmail,
sendEmail
})
}),
collectAndValidateResourceTargets: collectAndValidateCoreTargetsFactory({
getStream
}),
getUser,
getServerInfo
})
const createStream = legacyCreateStreamFactory({
createStreamReturnRecord: createStreamReturnRecordFactory({
inviteUsersToProject: inviteUsersToProjectFactory({
@@ -62,7 +130,8 @@ const createStream = legacyCreateStreamFactory({
payload
}),
getUser,
getServerInfo
getServerInfo,
finalizeInvite: buildFinalizeProjectInvite()
}),
getUsers
}),