Files
speckle-server/packages/server/modules/acc/graph/resolvers/accSyncItems.ts
T
2025-08-05 13:56:37 +01:00

311 lines
10 KiB
TypeScript

import {
countAccSyncItemsFactory,
deleteAccSyncItemByIdFactory,
getAccSyncItemByIdFactory,
listAccSyncItemsFactory,
updateAccSyncItemStatusFactory,
upsertAccSyncItemFactory
} from '@/modules/acc/repositories/accSyncItems'
import {
createAccSyncItemFactory,
deleteAccSyncItemFactory,
getPaginatedAccSyncItemsFactory,
updateAccSyncItemFactory
} from '@/modules/acc/services/management'
import {
createAutomation,
getFunctionReleaseFactory,
getFunctionReleasesFactory
} from '@/modules/automate/clients/executionEngine'
import {
getAutomationFactory,
getAutomationTokenFactory,
getLatestAutomationRevisionFactory,
storeAutomationFactory,
storeAutomationRevisionFactory,
storeAutomationTokenFactory,
upsertAutomationRunFactory
} from '@/modules/automate/repositories/automations'
import { createStoredAuthCodeFactory } from '@/modules/automate/services/authCode'
import {
createAutomationFactory,
createAutomationRevisionFactory
} from '@/modules/automate/services/automationManagement'
import {
getEncryptionKeyPair,
getFunctionInputDecryptorFactory
} from '@/modules/automate/services/encryption'
import { TokenResourceIdentifierType } from '@/modules/core/domain/tokens/types'
import type { Resolvers } from '@/modules/core/graph/generated/graphql'
import { throwIfResourceAccessNotAllowed } from '@/modules/core/helpers/token'
import { getBranchesByIdsFactory } from '@/modules/core/repositories/branches'
import { validateStreamAccessFactory } from '@/modules/core/services/streams/access'
import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector'
import { authorizeResolver } from '@/modules/shared'
import { getGenericRedis } from '@/modules/shared/redis/redis'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { buildDecryptor } from '@/modules/shared/utils/libsodium'
import { db } from '@/db/knex'
import { triggerSyncItemAutomationFactory } from '@/modules/acc/services/automate'
import { createAppTokenFactory } from '@/modules/core/services/tokens'
import {
storeApiTokenFactory,
storeTokenScopesFactory,
storeTokenResourceAccessDefinitionsFactory,
storeUserServerAppTokenFactory
} from '@/modules/core/repositories/tokens'
import {
filteredSubscribe,
ProjectSubscriptions
} from '@/modules/shared/utils/subscriptions'
import { throwIfAuthNotOk } from '@/modules/shared/helpers/errorHelper'
import { AccModuleDisabledError, SyncItemNotFoundError } from '@/modules/acc/errors/acc'
import { getFeatureFlags } from '@speckle/shared/environment'
import type { AccRegion } from '@/modules/acc/domain/constants'
const { FF_ACC_INTEGRATION_ENABLED, FF_AUTOMATE_MODULE_ENABLED } = getFeatureFlags()
const enableAcc = FF_ACC_INTEGRATION_ENABLED && FF_AUTOMATE_MODULE_ENABLED
const resolvers: Resolvers = {
Mutation: {
accSyncItemMutations: () => ({})
},
AccSyncItemMutations: {
async create(_parent, args, ctx) {
const { input } = args
const authResult = await ctx.authPolicies.project.canUpdateAccIntegrationSettings(
{
userId: ctx.userId,
projectId: input.projectId
}
)
throwIfAuthNotOk(authResult)
throwIfResourceAccessNotAllowed({
resourceId: input.projectId,
resourceAccessRules: ctx.resourceAccessRules,
resourceType: TokenResourceIdentifierType.Project
})
const projectDb = await getProjectDbClient({ projectId: input.projectId })
return await createAccSyncItemFactory({
upsertAccSyncItem: upsertAccSyncItemFactory({ db }),
createAutomation: createAutomationFactory({
createAuthCode: createStoredAuthCodeFactory({ redis: getGenericRedis() }),
automateCreateAutomation: createAutomation,
storeAutomation: storeAutomationFactory({ db: projectDb }),
storeAutomationToken: storeAutomationTokenFactory({ db: projectDb }),
eventEmit: getEventBus().emit
}),
createAutomationRevision: createAutomationRevisionFactory({
getAutomation: getAutomationFactory({ db: projectDb }),
storeAutomationRevision: storeAutomationRevisionFactory({ db: projectDb }),
getBranchesByIds: getBranchesByIdsFactory({ db: projectDb }),
getFunctionRelease: getFunctionReleaseFactory({ logger: ctx.log }),
getEncryptionKeyPair,
getFunctionInputDecryptor: getFunctionInputDecryptorFactory({
buildDecryptor
}),
getFunctionReleases: getFunctionReleasesFactory({ logger: ctx.log }),
eventEmit: getEventBus().emit,
validateStreamAccess: validateStreamAccessFactory({ authorizeResolver })
}),
triggerSyncItemAutomation: triggerSyncItemAutomationFactory({
updateAccSyncItemStatus: updateAccSyncItemStatusFactory({ db }),
getAutomation: getAutomationFactory({ db: projectDb }),
getLatestAutomationRevision: getLatestAutomationRevisionFactory({
db: projectDb
}),
upsertAutomationRun: upsertAutomationRunFactory({ db: projectDb }),
createAppToken: createAppTokenFactory({
storeApiToken: storeApiTokenFactory({ db }),
storeTokenScopes: storeTokenScopesFactory({ db }),
storeTokenResourceAccessDefinitions:
storeTokenResourceAccessDefinitionsFactory({
db
}),
storeUserServerAppToken: storeUserServerAppTokenFactory({ db })
}),
getAutomationToken: getAutomationTokenFactory({
db: projectDb
})
}),
eventEmit: getEventBus().emit
})({
syncItem: {
...input,
accRegion: input.accRegion as AccRegion
},
creatorUserId: ctx.userId!
})
},
async update(_parent, args, ctx) {
const { input } = args
const authResult = await ctx.authPolicies.project.canUpdateAccIntegrationSettings(
{
userId: ctx.userId,
projectId: input.projectId
}
)
throwIfAuthNotOk(authResult)
throwIfResourceAccessNotAllowed({
resourceId: input.projectId,
resourceAccessRules: ctx.resourceAccessRules,
resourceType: TokenResourceIdentifierType.Project
})
return await updateAccSyncItemFactory({
getAccSyncItemById: getAccSyncItemByIdFactory({ db }),
upsertAccSyncItem: upsertAccSyncItemFactory({ db }),
eventEmit: getEventBus().emit
})({
syncItem: input
})
},
async delete(_parent, args, ctx) {
const { input } = args
const authResult = await ctx.authPolicies.project.canUpdateAccIntegrationSettings(
{
userId: ctx.userId,
projectId: input.projectId
}
)
throwIfAuthNotOk(authResult)
throwIfResourceAccessNotAllowed({
resourceId: input.projectId,
resourceAccessRules: ctx.resourceAccessRules,
resourceType: TokenResourceIdentifierType.Project
})
await deleteAccSyncItemFactory({
deleteAccSyncItemById: deleteAccSyncItemByIdFactory({ db }),
eventEmit: getEventBus().emit
})({
id: input.id,
projectId: input.projectId
})
return true
}
},
AccSyncItem: {
author: async (parent, _args, context) => {
return await context.loaders.users.getUser.load(parent.authorId)
}
},
Project: {
async accSyncItems(parent, args, ctx) {
const { cursor = null, limit = null } = args
const authResult = await ctx.authPolicies.project.canReadAccIntegrationSettings({
userId: ctx.userId,
projectId: parent.id
})
throwIfAuthNotOk(authResult)
throwIfResourceAccessNotAllowed({
resourceId: parent.id,
resourceAccessRules: ctx.resourceAccessRules,
resourceType: TokenResourceIdentifierType.Project
})
return await getPaginatedAccSyncItemsFactory({
listAccSyncItems: listAccSyncItemsFactory({ db }),
countAccSyncItems: countAccSyncItemsFactory({ db })
})({
projectId: parent.id,
filter: {
cursor,
limit
}
})
},
async accSyncItem(parent, args, ctx) {
const { id } = args
const authResult = await ctx.authPolicies.project.canReadAccIntegrationSettings({
userId: ctx.userId,
projectId: parent.id
})
throwIfAuthNotOk(authResult)
throwIfResourceAccessNotAllowed({
resourceId: parent.id,
resourceAccessRules: ctx.resourceAccessRules,
resourceType: TokenResourceIdentifierType.Project
})
const syncItem = await ctx.loaders.acc.getAccSyncItem.load(id)
if (!syncItem) {
throw new SyncItemNotFoundError()
}
return syncItem
}
},
Subscription: {
projectAccSyncItemsUpdated: {
subscribe: filteredSubscribe(
ProjectSubscriptions.ProjectAccSyncItemUpdated,
async (payload, args, ctx) => {
const { id: projectId, itemIds } = args
if (payload.projectId !== projectId) return false
throwIfResourceAccessNotAllowed({
resourceAccessRules: ctx.resourceAccessRules,
resourceId: projectId,
resourceType: TokenResourceIdentifierType.Project
})
const canReadProject = await ctx.authPolicies.project.canRead({
userId: ctx.userId,
projectId
})
throwIfAuthNotOk(canReadProject)
if (!itemIds?.length) return true
return itemIds.includes(payload.projectAccSyncItemsUpdated.id)
}
)
}
}
}
const disabledResolvers: Resolvers = {
Mutation: {
accSyncItemMutations: () => ({})
},
AccSyncItemMutations: {
async create() {
throw new AccModuleDisabledError()
},
async update() {
throw new AccModuleDisabledError()
},
async delete() {
throw new AccModuleDisabledError()
}
},
Project: {
async accSyncItem() {
throw new AccModuleDisabledError()
},
async accSyncItems() {
throw new AccModuleDisabledError()
}
},
Subscription: {
projectAccSyncItemsUpdated: {
subscribe: filteredSubscribe(
ProjectSubscriptions.ProjectAccSyncItemUpdated,
async () => false
)
}
}
}
export default enableAcc ? resolvers : disabledResolvers