Files
speckle-server/packages/server/modules/workspaces/repositories/projectRegions.ts
T
Chuck Driesler 86c113b29b feat(regions): move project automations (#3925)
* feat(regions): repo functions for copying project branches and commits

* chore(regions): wire up move to resolver

* chore(regions): successful basic test of project region change

* fix(regions): sabrina carpenter please please please

* fix(regions): repair multiregion test setup

* chore(regions): appease ts

* chore(multiregion): update test multiregion config

* chore(multiregion): fix test docker config and test

* chore(multiregion): use transaction

* chore(multiregion): maybe this will work

* fix(multiregion): drop subs synchronously

* chore(multiregion): desperate test logs

* chore(multiregion): somehow that worked?

* chore(multiregion): add load-bearing log statement

* chore(multiregion): move services

* fix(multiregion): test drop waits

* chore(regions): fix import

* chore(regions): make test a bit more thorough for good measure

* fix(regions): move project objects

* chore(regions): add tests for object move

* feat(regions): move project automations

* chore(regions): add tests for moving automations

* chore(regions): more tests for moving automate data

* fix(regions): speed up inserts

* fix(regions): simplify postgres usage

* chore(regions): repair build

* fix(regions): improve queries

* chore(regions): again
2025-02-18 15:48:00 +00:00

462 lines
15 KiB
TypeScript

import {
AutomationFunctionRuns,
AutomationRevisionFunctions,
AutomationRevisions,
AutomationRuns,
AutomationRunTriggers,
Automations,
AutomationTokens,
AutomationTriggers,
BranchCommits,
Branches,
Commits,
Objects,
StreamCommits,
StreamFavorites,
Streams,
StreamsMeta
} from '@/modules/core/dbSchema'
import { Branch } from '@/modules/core/domain/branches/types'
import { Commit } from '@/modules/core/domain/commits/types'
import { Stream } from '@/modules/core/domain/streams/types'
import {
BranchCommitRecord,
ObjectRecord,
CommitRecord,
StreamCommitRecord,
StreamFavoriteRecord,
StreamRecord
} from '@/modules/core/helpers/types'
import { executeBatchedSelect } from '@/modules/shared/helpers/dbHelper'
import {
CopyProjectAutomations,
CopyProjectModels,
CopyProjectObjects,
CopyProjects,
CopyProjectVersions,
CopyWorkspace
} from '@/modules/workspaces/domain/operations'
import { WorkspaceNotFoundError } from '@/modules/workspaces/errors/workspace'
import { Knex } from 'knex'
import { Workspace } from '@/modules/workspacesCore/domain/types'
import { Workspaces } from '@/modules/workspacesCore/helpers/db'
import { ObjectPreview } from '@/modules/previews/domain/types'
import {
AutomationFunctionRunRecord,
AutomationRecord,
AutomationRevisionFunctionRecord,
AutomationRevisionRecord,
AutomationRunRecord,
AutomationRunTriggerRecord,
AutomationTokenRecord,
AutomationTriggerDefinitionRecord
} from '@/modules/automate/helpers/types'
const tables = {
workspaces: (db: Knex) => db<Workspace>(Workspaces.name),
projects: (db: Knex) => db<Stream>(Streams.name),
models: (db: Knex) => db<Branch>(Branches.name),
versions: (db: Knex) => db<Commit>(Commits.name),
branchCommits: (db: Knex) => db<BranchCommitRecord>(BranchCommits.name),
streamCommits: (db: Knex) => db<StreamCommitRecord>(StreamCommits.name),
streamFavorites: (db: Knex) => db<StreamFavoriteRecord>(StreamFavorites.name),
streamsMeta: (db: Knex) => db(StreamsMeta.name),
objects: (db: Knex) => db<ObjectRecord>(Objects.name),
objectPreviews: (db: Knex) => db<ObjectPreview>('object_preview'),
automations: (db: Knex) => db<AutomationRecord>(Automations.name),
automationTokens: (db: Knex) => db<AutomationTokenRecord>(AutomationTokens.name),
automationRevisions: (db: Knex) =>
db<AutomationRevisionRecord>(AutomationRevisions.name),
automationTriggers: (db: Knex) =>
db<AutomationTriggerDefinitionRecord>(AutomationTriggers.name),
automationRevisionFunctions: (db: Knex) =>
db<AutomationRevisionFunctionRecord>(AutomationRevisionFunctions.name),
automationRuns: (db: Knex) => db<AutomationRunRecord>(AutomationRuns.name),
automationRunTriggers: (db: Knex) =>
db<AutomationRunTriggerRecord>(AutomationRunTriggers.name),
automationFunctionRuns: (db: Knex) =>
db<AutomationFunctionRunRecord>(AutomationFunctionRuns.name)
}
/**
* Copies rows from the following tables:
* - workspaces
*/
export const copyWorkspaceFactory =
(deps: { sourceDb: Knex; targetDb: Knex }): CopyWorkspace =>
async ({ workspaceId }) => {
const workspace = await tables
.workspaces(deps.sourceDb)
.select('*')
.where({ id: workspaceId })
if (!workspace) {
throw new WorkspaceNotFoundError()
}
await tables
.workspaces(deps.targetDb)
.insert(workspace)
.onConflict(Workspaces.withoutTablePrefix.col.id)
.ignore()
return workspaceId
}
/**
* Copies rows from the following tables:
* - streams
* - streams_meta
* - stream_favorites
*/
export const copyProjectsFactory =
(deps: { sourceDb: Knex; targetDb: Knex }): CopyProjects =>
async ({ projectIds }) => {
const selectProjects = tables
.projects(deps.sourceDb)
.select('*')
.whereIn(Streams.col.id, projectIds)
const copiedProjectIds: string[] = []
// Copy project record
for await (const projects of executeBatchedSelect(selectProjects)) {
const projectIds = projects.map((project) => project.id)
copiedProjectIds.push(...projectIds)
// Copy `streams` rows to target db
await tables
.projects(deps.targetDb)
.insert(projects)
.onConflict(Streams.withoutTablePrefix.col.id)
.merge(Streams.withoutTablePrefix.cols as (keyof StreamRecord)[])
// Fetch `stream_favorites` rows for projects in batch
const selectStreamFavorites = tables
.streamFavorites(deps.sourceDb)
.select('*')
.whereIn(StreamFavorites.col.streamId, projectIds)
for await (const streamFavorites of executeBatchedSelect(selectStreamFavorites)) {
// Copy `stream_favorites` rows to target db
await tables
.streamFavorites(deps.targetDb)
.insert(streamFavorites)
.onConflict()
.ignore()
}
// Fetch `streams_meta` rows for projects in batch
const selectStreamsMetadata = tables
.streamsMeta(deps.sourceDb)
.select('*')
.whereIn(StreamsMeta.col.streamId, projectIds)
for await (const streamsMetadataBatch of executeBatchedSelect(
selectStreamsMetadata
)) {
// Copy `streams_meta` rows to target db
await tables
.streamsMeta(deps.targetDb)
.insert(streamsMetadataBatch)
.onConflict()
.ignore()
}
}
return copiedProjectIds
}
/**
* Copies rows from the following tables:
* - branches
*/
export const copyProjectModelsFactory =
(deps: { sourceDb: Knex; targetDb: Knex }): CopyProjectModels =>
async ({ projectIds }) => {
const copiedModelCountByProjectId: Record<string, number> = {}
// Fetch `branches` rows for projects in batch
const selectModels = tables
.models(deps.sourceDb)
.select('*')
.whereIn(Branches.col.streamId, projectIds)
for await (const models of executeBatchedSelect(selectModels)) {
// Copy `branches` rows to target db
await tables.models(deps.targetDb).insert(models).onConflict().ignore()
for (const model of models) {
copiedModelCountByProjectId[model.streamId] ??= 0
copiedModelCountByProjectId[model.streamId]++
}
}
return copiedModelCountByProjectId
}
/**
* Copies rows from the following tables:
* - commits
* - branch_commits
* - stream_commits
*/
export const copyProjectVersionsFactory =
(deps: { sourceDb: Knex; targetDb: Knex }): CopyProjectVersions =>
async ({ projectIds }) => {
const copiedVersionCountByProjectId: Record<string, number> = {}
const selectVersions = tables
.streamCommits(deps.sourceDb)
.select('*')
.join<StreamCommitRecord & Commit>(
Commits.name,
Commits.col.id,
StreamCommits.col.commitId
)
.whereIn(StreamCommits.col.streamId, projectIds)
for await (const versions of executeBatchedSelect(selectVersions)) {
const { commitIds, commits } = versions.reduce(
(all, version) => {
const { commitId, streamId, ...commit } = version
all.commitIds.push(commitId)
all.streamIds.push(streamId)
all.commits.push(commit)
return all
},
{ commitIds: [], streamIds: [], commits: [] } as {
commitIds: string[]
streamIds: string[]
commits: CommitRecord[]
}
)
// Copy `commits` rows to target db
await tables.versions(deps.targetDb).insert(commits).onConflict().ignore()
for (const version of versions) {
copiedVersionCountByProjectId[version.streamId] ??= 0
copiedVersionCountByProjectId[version.streamId]++
}
// Fetch `branch_commits` rows for versions in batch
const selectBranchCommits = tables
.branchCommits(deps.sourceDb)
.select('*')
.whereIn(BranchCommits.col.commitId, commitIds)
for await (const branchCommits of executeBatchedSelect(selectBranchCommits)) {
// Copy `branch_commits` row to target db
await tables
.branchCommits(deps.targetDb)
.insert(branchCommits)
.onConflict()
.ignore()
}
// Fetch `stream_commits` rows for versions in batch
const selectStreamCommits = tables
.streamCommits(deps.sourceDb)
.select('*')
.whereIn(StreamCommits.col.commitId, commitIds)
for await (const streamCommits of executeBatchedSelect(selectStreamCommits)) {
// Copy `stream_commits` row to target db
await tables
.streamCommits(deps.targetDb)
.insert(streamCommits)
.onConflict()
.ignore()
}
}
return copiedVersionCountByProjectId
}
/**
* Copies rows from the following tables:
* - objects
* - object_preview
*/
export const copyProjectObjectsFactory =
(deps: { sourceDb: Knex; targetDb: Knex }): CopyProjectObjects =>
async ({ projectIds }) => {
const copiedObjectCountByProjectId: Record<string, number> = {}
// Copy `objects` table rows in batches
const selectObjects = tables
.objects(deps.sourceDb)
.select<ObjectRecord[]>('*')
.whereIn(Objects.col.streamId, projectIds)
.orderBy(Objects.col.id)
for await (const objects of executeBatchedSelect(selectObjects)) {
// Write `objects` table rows to target db
await tables.objects(deps.targetDb).insert(objects).onConflict().ignore()
for (const object of objects) {
copiedObjectCountByProjectId[object.streamId] ??= 0
copiedObjectCountByProjectId[object.streamId]++
}
}
// Copy `object_preview` rows in batches
const selectObjectPreviews = tables
.objectPreviews(deps.sourceDb)
.select<ObjectPreview[]>('*')
.whereIn('streamId', projectIds)
for await (const previews of executeBatchedSelect(selectObjectPreviews)) {
// Write `object_preview` rows to target db
await tables.objectPreviews(deps.targetDb).insert(previews).onConflict().ignore()
}
return copiedObjectCountByProjectId
}
/**
* Copies rows from the following tables:
* - automations
* - automation_tokens
* - automation_revisions
* - automation_triggers
* - automation_revision_functions
* - automation_runs
* - automation_run_triggers
* - automation_function_runs
*/
export const copyProjectAutomationsFactory =
(deps: { sourceDb: Knex; targetDb: Knex }): CopyProjectAutomations =>
async ({ projectIds }) => {
const copiedAutomationCountByProjectId: Record<string, number> = {}
// Copy `automations` table rows in batches
const selectAutomations = tables
.automations(deps.sourceDb)
.select('*')
.whereIn(Automations.col.projectId, projectIds)
for await (const automations of executeBatchedSelect(selectAutomations)) {
const automationIds = automations.map((automation) => automation.id)
// Write `automations` table rows to target db
await tables
.automations(deps.targetDb)
// Cast ignores unexpected behavior in how knex handles object union types
.insert(automations as unknown as AutomationRecord)
.onConflict()
.ignore()
for (const automation of automations) {
copiedAutomationCountByProjectId[automation.projectId] ??= 0
copiedAutomationCountByProjectId[automation.projectId]++
}
// Copy `automation_tokens` rows for automation
const selectAutomationTokens = tables
.automationTokens(deps.sourceDb)
.select('*')
.whereIn(AutomationTokens.col.automationId, automationIds)
for await (const tokens of executeBatchedSelect(selectAutomationTokens)) {
// Write `automation_tokens` row to target db
await tables
.automationTokens(deps.targetDb)
.insert(tokens)
.onConflict()
.ignore()
}
// Copy `automation_revisions` rows for automation
const selectAutomationRevisions = tables
.automationRevisions(deps.sourceDb)
.select('*')
.whereIn(AutomationRevisions.col.automationId, automationIds)
for await (const automationRevisions of executeBatchedSelect(
selectAutomationRevisions
)) {
const automationRevisionIds = automationRevisions.map((revision) => revision.id)
// Write `automation_revisions` rows to target db
await tables
.automationRevisions(deps.targetDb)
.insert(automationRevisions)
.onConflict()
.ignore()
// Copy `automation_triggers` rows for automation revisions
const automationTriggers = await tables
.automationTriggers(deps.sourceDb)
.select('*')
.whereIn(AutomationTriggers.col.automationRevisionId, automationRevisionIds)
await tables
.automationTriggers(deps.targetDb)
.insert(automationTriggers)
.onConflict()
.ignore()
// Copy `automation_revision_functions` rows for automation revisions
const automationRevisionFunctions = await tables
.automationRevisionFunctions(deps.sourceDb)
.select('*')
.whereIn(
AutomationRevisionFunctions.col.automationRevisionId,
automationRevisionIds
)
await tables
.automationRevisionFunctions(deps.targetDb)
.insert(automationRevisionFunctions)
.onConflict()
.ignore()
// Copy `automation_runs` rows for automation revision
const selectAutomationRuns = tables
.automationRuns(deps.sourceDb)
.select('*')
.whereIn(AutomationRuns.col.automationRevisionId, automationRevisionIds)
for await (const automationRuns of executeBatchedSelect(selectAutomationRuns)) {
const automationRunIds = automationRuns.map((run) => run.id)
// Write `automation_runs` row to target db
await tables
.automationRuns(deps.targetDb)
.insert(automationRuns)
.onConflict()
.ignore()
// Copy `automation_run_triggers` rows for automation run
const automationRunTriggers = await tables
.automationRunTriggers(deps.sourceDb)
.select('*')
.whereIn(AutomationRunTriggers.col.automationRunId, automationRunIds)
await tables
.automationRunTriggers(deps.targetDb)
.insert(automationRunTriggers)
.onConflict()
.ignore()
// Copy `automation_function_runs` rows for automation run
const automationFunctionRuns = await tables
.automationFunctionRuns(deps.sourceDb)
.select('*')
.whereIn(AutomationFunctionRuns.col.runId, automationRunIds)
await tables
.automationFunctionRuns(deps.targetDb)
.insert(automationFunctionRuns)
.onConflict()
.ignore()
}
}
}
return copiedAutomationCountByProjectId
}