feat(multiregion): apply prepared transactions to projects (#5322)
* feat(multiregion): replace user replication * chore(multiregion): optimise replication * maybe it's this * postgres is fun * once more * chore(multiregion): only replicate test user creation during multiregion tests * feat: improved replicate_query logic * fix: minor * fix: starting issue * feat: included user create and delete specs to multiregion * feat: removed console logs * fix: user defaults * fix: multiregion test helper * fix: update scenarios for users * refactor(multiregion): swap replicateQuery concept to asMultiregionOperation (#5301) feat(multiregion): introduced asMultregionOperator, refactor test to user builder classes * chore: renamings * fix: remove comments * feat: remove user replication * refactor: simplified spec usages * chore: comments * chore: branches and favs * chore: more tests * chore: more tests * fix linting * fix tests * feat: dropping replication * refactor: moved project delete to service * fix: comment * feat: updateStreamFactory and updateProjectFacotry * deleteProjectFactory + replicateFactory * deleteWorkspaceFactory * fix: selector * fix: tests * fix tests, finished createStreamFactory * feat: simplify changes * fix: remove comment * fix: minor strucutres * fix: moveProjectToRegion * fix: moved branch creation outside of multiregion scope * fix: branch creation * fix: tests * fix: ci tests * fix: removed log form test * fix: on specs, no random regionKeys * review fixes * fix: mr comments * feat: removed test --------- Co-authored-by: Charles Driesler <chuck@speckle.systems>
This commit is contained in:
committed by
GitHub
parent
6692fdf4aa
commit
399c998fd7
@@ -58,12 +58,14 @@ import { chunk, isEmpty, omit } from 'lodash-es'
|
||||
import { userEmailsCompliantWithWorkspaceDomains } from '@/modules/workspaces/domain/logic'
|
||||
import { workspaceRoles as workspaceRoleDefinitions } from '@/modules/workspaces/roles'
|
||||
import { blockedDomains } from '@speckle/shared'
|
||||
import type { DeleteStreamRecord } from '@/modules/core/domain/streams/operations'
|
||||
import type {
|
||||
DeleteSsoProvider,
|
||||
GetWorkspaceSsoProviderRecord
|
||||
} from '@/modules/workspaces/domain/sso/operations'
|
||||
import type { QueryAllProjects } from '@/modules/core/domain/projects/operations'
|
||||
import type {
|
||||
DeleteProjectAndCommits,
|
||||
QueryAllProjects
|
||||
} from '@/modules/core/domain/projects/operations'
|
||||
|
||||
type WorkspaceCreateArgs = {
|
||||
userId: string
|
||||
@@ -288,14 +290,14 @@ type WorkspaceDeleteArgs = {
|
||||
export const deleteWorkspaceFactory =
|
||||
({
|
||||
deleteWorkspace,
|
||||
deleteProject,
|
||||
deleteProjectAndCommits,
|
||||
queryAllProjects,
|
||||
deleteAllResourceInvites,
|
||||
deleteSsoProvider,
|
||||
emitWorkspaceEvent
|
||||
}: {
|
||||
deleteWorkspace: DeleteWorkspace
|
||||
deleteProject: DeleteStreamRecord
|
||||
deleteProjectAndCommits: DeleteProjectAndCommits
|
||||
queryAllProjects: QueryAllProjects
|
||||
deleteAllResourceInvites: DeleteAllResourceInvites
|
||||
deleteSsoProvider: DeleteSsoProvider
|
||||
@@ -328,7 +330,9 @@ export const deleteWorkspaceFactory =
|
||||
// Workspace delete cascades-deletes stream table rows, but some manual cleanup is required
|
||||
// We re-use `deleteStream` (and re-delete the project) to DRY this manual cleanup
|
||||
for (const projectIdsChunk of chunk(projectIds, 25)) {
|
||||
await Promise.all(projectIdsChunk.map((projectId) => deleteProject(projectId)))
|
||||
await Promise.all(
|
||||
projectIdsChunk.map((projectId) => deleteProjectAndCommits({ projectId }))
|
||||
)
|
||||
}
|
||||
await emitWorkspaceEvent({
|
||||
eventName: WorkspaceEvents.Deleted,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { GetProject } from '@/modules/core/domain/projects/operations'
|
||||
import type { UpdateProjectRegionKey } from '@/modules/multiregion/services/projectRegion'
|
||||
import type {
|
||||
CopyProjectAutomations,
|
||||
CopyProjectBlobs,
|
||||
@@ -17,13 +16,13 @@ import type {
|
||||
CountProjectVersions,
|
||||
CountProjectWebhooks,
|
||||
GetAvailableRegions,
|
||||
UpdateProjectRegion,
|
||||
MoveProjectToRegion,
|
||||
ValidateProjectRegionCopy
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import { ProjectRegionAssignmentError } from '@/modules/workspaces/errors/regions'
|
||||
import { logger } from '@/observability/logging'
|
||||
|
||||
export const updateProjectRegionFactory =
|
||||
export const moveProjectToRegionFactory =
|
||||
(deps: {
|
||||
getProject: GetProject
|
||||
getAvailableRegions: GetAvailableRegions
|
||||
@@ -37,8 +36,7 @@ export const updateProjectRegionFactory =
|
||||
copyProjectWebhooks: CopyProjectWebhooks
|
||||
copyProjectBlobs: CopyProjectBlobs
|
||||
validateProjectRegionCopy: ValidateProjectRegionCopy
|
||||
updateProjectRegionKey: UpdateProjectRegionKey
|
||||
}): UpdateProjectRegion =>
|
||||
}): MoveProjectToRegion =>
|
||||
async (params) => {
|
||||
const { projectId, regionKey } = params
|
||||
|
||||
@@ -120,9 +118,6 @@ export const updateProjectRegionFactory =
|
||||
'Missing data from source project in target region copy after move.'
|
||||
)
|
||||
}
|
||||
|
||||
// Update project region in db and update relevant caches
|
||||
return await deps.updateProjectRegionKey({ projectId, regionKey })
|
||||
}
|
||||
|
||||
export const validateProjectRegionCopyFactory =
|
||||
|
||||
@@ -30,21 +30,15 @@ import { ProjectNotFoundError } from '@/modules/core/errors/projects'
|
||||
import type { WorkspaceProjectCreateInput } from '@/modules/core/graph/generated/graphql'
|
||||
import {
|
||||
getDb,
|
||||
getReplicationDbs,
|
||||
getValidDefaultProjectRegionKey
|
||||
} from '@/modules/multiregion/utils/dbSelector'
|
||||
import { createNewProjectFactory } from '@/modules/core/services/projects'
|
||||
import {
|
||||
createNewProjectFactory,
|
||||
waitForRegionProjectFactory
|
||||
} from '@/modules/core/services/projects'
|
||||
import {
|
||||
deleteProjectFactory,
|
||||
getProjectFactory,
|
||||
storeProjectFactory,
|
||||
storeProjectRoleFactory
|
||||
} from '@/modules/core/repositories/projects'
|
||||
import { mainDb } from '@/db/knex'
|
||||
import { storeModelFactory } from '@/modules/core/repositories/models'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import {
|
||||
getWorkspaceFactory,
|
||||
upsertWorkspaceFactory
|
||||
@@ -59,6 +53,8 @@ import type { FindEmailsByUserId } from '@/modules/core/domain/userEmails/operat
|
||||
import { userEmailsCompliantWithWorkspaceDomains } from '@/modules/workspaces/domain/logic'
|
||||
import type { CreateWorkspaceSeat } from '@/modules/gatekeeper/domain/operations'
|
||||
import type { WorkspaceAcl } from '@/modules/workspacesCore/domain/types'
|
||||
import { asMultiregionalOperation, replicateFactory } from '@/modules/shared/command'
|
||||
import { logger } from '@/observability/logging'
|
||||
|
||||
type MoveProjectToWorkspaceArgs = {
|
||||
projectId: string
|
||||
@@ -308,6 +304,7 @@ export const validateWorkspaceMemberProjectRoleFactory =
|
||||
}
|
||||
}
|
||||
|
||||
// This factory uses the command factory to create a new project in transactional (cross region) so it cannot be wrapped in another transaction
|
||||
export const createWorkspaceProjectFactory =
|
||||
(deps: { getDefaultRegion: GetDefaultRegion }) =>
|
||||
async (params: { input: WorkspaceProjectCreateInput; ownerId: string }) => {
|
||||
@@ -334,26 +331,28 @@ export const createWorkspaceProjectFactory =
|
||||
if (!workspace) throw new WorkspaceNotFoundError()
|
||||
await upsertWorkspaceFactory({ db: projectDb })({ workspace })
|
||||
}
|
||||
const project = await asMultiregionalOperation(
|
||||
async ({ allDbs, mainDb, emit }) => {
|
||||
const createNewProject = createNewProjectFactory({
|
||||
// TODO: this goes as event emmits outside (default model)
|
||||
storeProject: replicateFactory(allDbs, storeProjectFactory),
|
||||
// THIS MUST GO TO THE MAIN DB
|
||||
storeProjectRole: storeProjectRoleFactory({ db: mainDb }),
|
||||
emitEvent: emit
|
||||
})
|
||||
|
||||
// todo, use the command factory here, but for that, we need to migrate to the event bus
|
||||
// deps not injected to ensure proper DB injection
|
||||
const createNewProject = createNewProjectFactory({
|
||||
storeProject: storeProjectFactory({ db: projectDb }),
|
||||
storeModel: storeModelFactory({ db: projectDb }),
|
||||
// THIS MUST GO TO THE MAIN DB
|
||||
storeProjectRole: storeProjectRoleFactory({ db }),
|
||||
waitForRegionProject: waitForRegionProjectFactory({
|
||||
getProject: getProjectFactory({ db }),
|
||||
deleteProject: deleteProjectFactory({ db: projectDb })
|
||||
}),
|
||||
emitEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
const project = await createNewProject({
|
||||
...input,
|
||||
regionKey,
|
||||
ownerId
|
||||
})
|
||||
return createNewProject({
|
||||
...input,
|
||||
regionKey,
|
||||
ownerId
|
||||
})
|
||||
},
|
||||
{
|
||||
dbs: await getReplicationDbs({ regionKey }),
|
||||
name: 'Create project workspace',
|
||||
logger
|
||||
}
|
||||
)
|
||||
|
||||
return project
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user