fix(acc): getter gql and more

This commit is contained in:
Charles Driesler
2025-08-04 23:05:39 +01:00
parent 2ab81f201d
commit 4b96a9faeb
29 changed files with 318 additions and 235 deletions
@@ -44,7 +44,7 @@
<script setup lang="ts">
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/20/solid'
import type { AccItem } from '~/lib/acc/types'
import type { AccItem } from '@speckle/shared/acc'
defineProps<{
folderContent: AccItem
@@ -17,7 +17,7 @@
</template>
<script setup lang="ts">
import type { AccItem } from '~/lib/acc/types'
import type { AccItem } from '@speckle/shared/acc'
import type { ProjectAccSyncItemFragment } from '~/lib/common/generated/gql/graphql'
const props = defineProps<{
@@ -25,7 +25,7 @@
<script setup lang="ts">
import { isArray } from 'lodash-es'
import type { AccHub } from '~/lib/acc/types'
import type { AccHub } from '@speckle/shared/acc'
const props = defineProps<{
hubs: AccHub[]
@@ -27,7 +27,7 @@
<script setup lang="ts">
import { isArray } from 'lodash-es'
import type { AccProject } from '~/lib/acc/types'
import type { AccProject } from '@speckle/shared/acc'
const props = defineProps<{
hubId: string
@@ -19,19 +19,19 @@ const runStatusClasses = (run: AccSyncItemStatus) => {
const classParts = ['w-24 justify-center']
switch (run) {
case 'SYNCING':
case 'syncing':
classParts.push('bg-info-lighter')
break
case 'PENDING':
case 'pending':
classParts.push('bg-warning-lighter')
break
case 'PAUSED':
case 'paused':
classParts.push('bg-warning-lighter')
break
case 'FAILED':
case 'failed':
classParts.push('bg-danger-lighter')
break
case 'SUCCEEDED':
case 'succeeded':
classParts.push('bg-success-lighter')
break
}
@@ -38,8 +38,8 @@
<FormButton
hide-text
color="outline"
:icon-left="item.status === 'PAUSED' ? PlayIcon : PauseIcon"
@click="handleStatusSyncItem(item.id, item.status === 'PAUSED')"
:icon-left="item.status === 'paused' ? PlayIcon : PauseIcon"
@click="handleStatusSyncItem(item.id, item.status === 'paused')"
/>
<FormButton
hide-text
@@ -144,7 +144,7 @@
</template>
<script setup lang="ts">
import type { AccTokens, AccHub, AccProject, AccItem } from '~/lib/acc/types'
import type { AccTokens, AccHub, AccProject, AccItem } from '@speckle/shared/acc'
import { ref, computed } from 'vue'
import type {
ProjectLatestModelsPaginationQueryVariables,
@@ -169,8 +169,6 @@ const props = defineProps<{
isLoggedIn: boolean
}>()
// TODO ACC: Need to think about data residency from "ACC > Speckle" and warn users accordingly
const step = ref(0)
const showNewSyncDialog = ref(false)
@@ -481,7 +479,7 @@ const handleStatusSyncItem = async (id: string, isPaused: boolean) => {
input: {
projectId: props.projectId,
id,
status: isPaused ? 'PENDING' : 'PAUSED'
status: isPaused ? 'pending' : 'paused'
}
})
} catch (error) {
@@ -51,7 +51,7 @@
</template>
<script setup lang="ts">
import type { AccTokens, AccUserInfo } from '~/lib/acc/types'
import type { AccTokens, AccUserInfo } from '@speckle/shared/acc'
// import { DocumentDuplicateIcon } from '@heroicons/vue/24/outline'
const props = defineProps<{ projectId: string }>()
+8 -44
View File
@@ -1,47 +1,6 @@
export type AccTokens = {
access_token: string
refresh_token: string
token_type: string
id_token: string
expires_in: number
}
export type AccUserInfo = {
userId: string
userName: string
emailId: string
firstName: string
lastName: string
}
export type AccHub = {
id: string
attributes: { name: string; region: string; extension: Record<string, unknown> }
}
export type AccProject = {
id: string
attributes: { name: string; lastModifiedTime: string }
relationships: Record<string, unknown>
}
export type AccItem = {
id: string
type?: string
latestVersionId?: string // we mutate on the way
fileExtension: string
storageUrn?: string // we mutate on the way
attributes: {
name: string
displayName: string
createTime?: string
extension?: Record<string, unknown>
versionNumber: number
}
}
// TODO: looks stale, we can consider to move this types into @shared
import type { AccHub, AccItem } from '@speckle/shared/acc'
// TODO ACC: Replace with type information inferred from gql queries, if possible
export type AccSyncItem = {
id: string
accHub: AccHub
@@ -55,4 +14,9 @@ export type AccSyncItem = {
status: AccSyncItemStatus
}
export type AccSyncItemStatus = 'sync' | 'syncing' | 'paused' | 'failed'
export type AccSyncItemStatus =
| 'pending'
| 'syncing'
| 'paused'
| 'failed'
| 'succeeded'
@@ -74,11 +74,11 @@ export type AccSyncItemMutationsUpdateArgs = {
};
export const AccSyncItemStatus = {
Failed: 'FAILED',
Paused: 'PAUSED',
Pending: 'PENDING',
Succeeded: 'SUCCEEDED',
Syncing: 'SYNCING'
Failed: 'failed',
Paused: 'paused',
Pending: 'pending',
Succeeded: 'succeeded',
Syncing: 'syncing'
} as const;
export type AccSyncItemStatus = typeof AccSyncItemStatus[keyof typeof AccSyncItemStatus];
@@ -31,11 +31,11 @@ type AccSyncItem {
}
enum AccSyncItemStatus {
PENDING
SYNCING
FAILED
SUCCEEDED
PAUSED
pending
syncing
failed
succeeded
paused
}
input DeleteAccSyncItemInput {
@@ -1,5 +1,6 @@
/* eslint-disable camelcase */
import type { AccTokens } from '@speckle/shared/acc'
import type { AccRegion } from '@/modules/acc/domain/constants'
import { AccRegions } from '@/modules/acc/domain/constants'
import type { ModelDerivativeServiceDesignManifest } from '@/modules/acc/domain/types'
@@ -10,6 +11,7 @@ import {
import { logger } from '@/observability/logging'
import { isObjectLike } from 'lodash-es'
import { z } from 'zod'
import crypto from 'crypto'
const invokeJsonRequest = async <T>(params: {
url: string
@@ -41,12 +43,121 @@ const invokeJsonRequest = async <T>(params: {
return (await response.json()) as T
}
interface BuildAuthorizeUrlOptions {
clientId: string
redirectUri: string
codeChallenge: string
scopes: string[]
}
interface ExchangeCodeOptions {
code: string
codeVerifier: string
clientId: string
clientSecret: string
redirectUri: string
}
const AccTokens = z.object({
access_token: z.string(),
refresh_token: z.string(),
token_type: z.string(),
id_token: z.string(),
expires_in: z.number()
})
export const generateCodeVerifier = () => {
const codeVerifier = crypto.randomBytes(32).toString('base64url')
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url')
return { codeVerifier, codeChallenge }
}
export const buildAuthorizeUrl = ({
clientId,
redirectUri,
codeChallenge,
scopes
}: BuildAuthorizeUrlOptions) => {
const params = new URLSearchParams({
response_type: 'code',
client_id: clientId,
redirect_uri: redirectUri,
scope: scopes.join(' '),
code_challenge: codeChallenge,
code_challenge_method: 'S256'
})
return `https://developer.api.autodesk.com/authentication/v2/authorize?${params.toString()}`
}
export const exchangeCodeForTokens = async ({
code,
codeVerifier,
clientId,
clientSecret,
redirectUri
}: ExchangeCodeOptions): Promise<AccTokens> => {
const params = new URLSearchParams({
grant_type: 'authorization_code',
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri,
code,
code_verifier: codeVerifier
})
const response = await fetch(
'https://developer.api.autodesk.com/authentication/v2/token',
{
method: 'POST',
body: params,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
)
const data = await response.json()
return AccTokens.parse(data)
}
export const exchangeRefreshTokenForTokens = async (args: {
refresh_token: string
}): Promise<AccTokens> => {
const params = new URLSearchParams({
grant_type: 'refresh_token',
client_id: getAutodeskIntegrationClientId(),
client_secret: getAutodeskIntegrationClientSecret(),
refresh_token: args.refresh_token
})
const response = await fetch(
'https://developer.api.autodesk.com/authentication/v2/token',
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params
}
)
const data = await response.json()
return AccTokens.parse(data)
}
type AutodeskIntegrationTokenData = {
access_token: string
token_type: string
expires_in: number
}
/**
* Fetch a valid token for server-side operations as our custom integration
*/
export const getToken = async (): Promise<AutodeskIntegrationTokenData> => {
const clientId = getAutodeskIntegrationClientId()
const clientSecret = getAutodeskIntegrationClientSecret()
@@ -7,12 +7,12 @@ export const ImporterAutomateFunctions = {
export const AccSyncItemStatuses = {
// A new file version had been detected, and we are awaiting a processable file.
pending: 'PENDING',
pending: 'pending',
// We are actively processing the new file version. (The Automate function has been triggered.)
syncing: 'SYNCING',
failed: 'FAILED',
paused: 'PAUSED',
succeeded: 'SUCCEEDED'
syncing: 'syncing',
failed: 'failed',
paused: 'paused',
succeeded: 'succeeded'
} as const
export type AccSyncItemStatus =
(typeof AccSyncItemStatuses)[keyof typeof AccSyncItemStatuses]
@@ -1,7 +1,10 @@
import type { AccSyncItemStatus } from '@/modules/acc/domain/constants'
import type { AccSyncItem } from '@/modules/acc/domain/types'
import type { Exact } from 'type-fest'
export type UpsertAccSyncItem = (item: AccSyncItem) => Promise<void>
export type UpsertAccSyncItem = <Item extends Exact<AccSyncItem, Item>>(
item: Item
) => Promise<void>
export type UpdateAccSyncItemStatus = (args: {
id: string
@@ -10,6 +13,8 @@ export type UpdateAccSyncItemStatus = (args: {
export type GetAccSyncItemById = (args: { id: string }) => Promise<AccSyncItem | null>
export type GetAccSyncItemsById = (args: { ids: string[] }) => Promise<AccSyncItem[]>
export type ListAccSyncItems = (args: {
projectId: string
filter?: {
@@ -1,5 +1,11 @@
import { BaseError } from '@/modules/shared/errors/base'
export class AccModuleDisabledError extends BaseError {
static defaultMessage = 'ACC integration module is disabled'
static code = 'ACC_MODULE_DISABLED'
static statusCode = 423
}
export class DuplicateSyncItemError extends BaseError {
static defaultMessage = 'A sync item with this lineage urn already exists.'
static code = 'ACC_DUPLICATE_SYNC_ITEM_LINEAGE_URN'
@@ -14,9 +20,11 @@ export class DuplicateSyncItemError extends BaseError {
export class SyncItemNotFoundError extends BaseError {
static defaultMessage = 'Sync item not found'
static code = 'ACC_SYNC_ITEM_NOT_FOUND'
static statusCode = 404
}
export class SyncItemAutomationTriggerError extends BaseError {
static defaultMessage = 'Failed to trigger automation associated with sync item'
static code = 'ACC_SYNC_ITEM_AUTOMATION_TRIGGER_ERROR'
static statusCode = 422
}
@@ -9,7 +9,6 @@ import {
import {
createAccSyncItemFactory,
deleteAccSyncItemFactory,
getAccSyncItemFactory,
getPaginatedAccSyncItemsFactory,
updateAccSyncItemFactory
} from '@/modules/acc/services/management'
@@ -40,7 +39,6 @@ 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 { getUserFactory } from '@/modules/core/repositories/users'
import { validateStreamAccessFactory } from '@/modules/core/services/streams/access'
import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector'
import { authorizeResolver } from '@/modules/shared'
@@ -61,6 +59,12 @@ import {
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'
const { FF_ACC_INTEGRATION_ENABLED, FF_AUTOMATE_MODULE_ENABLED } = getFeatureFlags()
const enableAcc = FF_ACC_INTEGRATION_ENABLED && FF_AUTOMATE_MODULE_ENABLED
const resolvers: Resolvers = {
Mutation: {
@@ -164,8 +168,8 @@ const resolvers: Resolvers = {
}
},
AccSyncItem: {
author: async (parent) => {
return await getUserFactory({ db })(parent.authorId)
author: async (parent, _args, context) => {
return await context.loaders.users.getUser.load(parent.authorId)
}
},
Project: {
@@ -198,9 +202,13 @@ const resolvers: Resolvers = {
resourceType: TokenResourceIdentifierType.Project
})
return await getAccSyncItemFactory({
getAccSyncItemById: getAccSyncItemByIdFactory({ db })
})({ id })
const syncItem = await ctx.loaders.acc.getAccSyncItem.load(id)
if (!syncItem) {
throw new SyncItemNotFoundError()
}
return syncItem
}
},
Subscription: {
@@ -231,4 +239,37 @@ const resolvers: Resolvers = {
}
}
export default resolvers
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
@@ -1,88 +0,0 @@
/* eslint-disable camelcase */
import crypto from 'crypto'
import { z } from 'zod'
interface BuildAuthorizeUrlOptions {
clientId: string
redirectUri: string
codeChallenge: string
scopes: string[]
}
interface ExchangeCodeOptions {
code: string
codeVerifier: string
clientId: string
clientSecret: string
redirectUri: string
}
const AccTokens = z.object({
access_token: z.string(),
refresh_token: z.string(),
token_type: z.string(),
id_token: z.string(),
expires_in: z.number()
})
export type AccTokens = z.infer<typeof AccTokens>
export const generateCodeVerifier = () => {
const codeVerifier = crypto.randomBytes(32).toString('base64url')
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url')
return { codeVerifier, codeChallenge }
}
export const buildAuthorizeUrl = ({
clientId,
redirectUri,
codeChallenge,
scopes
}: BuildAuthorizeUrlOptions) => {
const params = new URLSearchParams({
response_type: 'code',
client_id: clientId,
redirect_uri: redirectUri,
scope: scopes.join(' '),
code_challenge: codeChallenge,
code_challenge_method: 'S256'
})
return `https://developer.api.autodesk.com/authentication/v2/authorize?${params.toString()}`
}
export const exchangeCodeForTokens = async ({
code,
codeVerifier,
clientId,
clientSecret,
redirectUri
}: ExchangeCodeOptions): Promise<AccTokens> => {
const params = new URLSearchParams({
grant_type: 'authorization_code',
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri,
code,
code_verifier: codeVerifier
})
const response = await fetch(
'https://developer.api.autodesk.com/authentication/v2/token',
{
method: 'POST',
body: params,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
)
const data = await response.json()
return AccTokens.parse(data)
}
+1 -1
View File
@@ -1,4 +1,4 @@
import type { AccTokens } from '@/modules/acc/helpers/oidcHelper'
import type { AccTokens } from '@speckle/shared/acc'
import type { Session, SessionData } from 'express-session'
declare module 'express-session' {
+9 -10
View File
@@ -8,14 +8,14 @@ import {
} from '@/modules/core/repositories/scheduledTasks'
import type { ScheduleExecution } from '@/modules/core/domain/scheduledTasks/operations'
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
import { accOidc } from '@/modules/acc/rest/oidc'
import { accWebhooks } from '@/modules/acc/rest/webhooks'
import { setupAccOidcEndpoints } from '@/modules/acc/rest/oidc'
import { setupAccWebhookEndpoints } from '@/modules/acc/rest/webhooks'
import { schedulePendingSyncItemsCheck } from '@/modules/acc/services/cron'
import { reportSubscriptionEventsFactory } from '@/modules/acc/events/eventListeners'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { publish } from '@/modules/shared/utils/subscriptions'
const { FF_ACC_INTEGRATION_ENABLED } = getFeatureFlags()
const { FF_ACC_INTEGRATION_ENABLED, FF_AUTOMATE_MODULE_ENABLED } = getFeatureFlags()
const scheduleExecution = scheduleExecutionFactory({
acquireTaskLock: acquireTaskLockFactory({ db }),
@@ -27,24 +27,23 @@ let scheduledTask: ReturnType<ScheduleExecution> | null = null
const accModule: SpeckleModule = {
init: async ({ app, isInitial }) => {
if (!FF_ACC_INTEGRATION_ENABLED) return
if (!FF_ACC_INTEGRATION_ENABLED || !FF_AUTOMATE_MODULE_ENABLED) return
moduleLogger.info('🖕 Init acc module')
moduleLogger.info('🖕 Init ACC module')
if (isInitial) {
accOidc(app)
accWebhooks(app)
setupAccOidcEndpoints(app)
setupAccWebhookEndpoints(app)
quitListeners = reportSubscriptionEventsFactory({
eventListen: getEventBus().listen,
publish
})
})()
scheduledTask = schedulePendingSyncItemsCheck({ scheduleExecution })
}
},
shutdown: () => {
if (!FF_ACC_INTEGRATION_ENABLED) return
quitListeners?.()
scheduledTask?.stop()
scheduledTask?.stop?.()
},
finalize: () => {}
}
@@ -3,6 +3,7 @@ import type {
CountAccSyncItems,
DeleteAccSyncItemById,
GetAccSyncItemById,
GetAccSyncItemsById,
ListAccSyncItems,
QueryAllAccSyncItems,
UpdateAccSyncItemStatus,
@@ -23,12 +24,20 @@ export const getAccSyncItemByIdFactory =
return (
(await tables
.accSyncItems(deps.db)
.select('*')
.select()
.where(AccSyncItems.col.id, id)
.first()) ?? null
)
}
export const getAccSyncItemsByIdFactory =
(deps: { db: Knex }): GetAccSyncItemsById =>
async ({ ids }) => {
if (!ids.length) return []
return await tables.accSyncItems(deps.db).select().whereIn(AccSyncItems.col.id, ids)
}
export const upsertAccSyncItemFactory =
(deps: { db: Knex }): UpsertAccSyncItem =>
async (item) => {
+4 -25
View File
@@ -3,8 +3,9 @@
import {
buildAuthorizeUrl,
exchangeCodeForTokens,
exchangeRefreshTokenForTokens,
generateCodeVerifier
} from '@/modules/acc/helpers/oidcHelper'
} from '@/modules/acc/clients/autodesk'
import { sessionMiddlewareFactory } from '@/modules/auth/middleware'
import { corsMiddlewareFactory } from '@/modules/core/configs/cors'
import {
@@ -15,7 +16,7 @@ import {
} from '@/modules/shared/helpers/envHelper'
import type { Express } from 'express'
export const accOidc = (app: Express) => {
export const setupAccOidcEndpoints = (app: Express) => {
const corsMiddleware = corsMiddlewareFactory({
corsConfig: {
origin: [getServerOrigin(), getFrontendOrigin()],
@@ -101,30 +102,8 @@ export const accOidc = (app: Express) => {
}
try {
const params = new URLSearchParams({
grant_type: 'refresh_token',
client_id: getAutodeskIntegrationClientId(),
client_secret: getAutodeskIntegrationClientSecret(),
refresh_token
})
const response = await fetch(
'https://developer.api.autodesk.com/authentication/v2/token',
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params
}
)
if (!response.ok) {
console.error(await response.text())
return res.status(500).json({ error: 'Failed to refresh token' })
}
const newTokens = await response.json()
const newTokens = await exchangeRefreshTokenForTokens({ refresh_token })
req.session.accTokens = newTokens
res.json(newTokens)
} catch (error) {
console.error('Error refreshing token:', error)
+1 -1
View File
@@ -9,7 +9,7 @@ import type { Express } from 'express'
import { z } from 'zod'
import { db } from '@/db/knex'
export const accWebhooks = (app: Express) => {
export const setupAccWebhookEndpoints = (app: Express) => {
const sessionMiddleware = sessionMiddlewareFactory()
app.post('/api/v1/acc/webhook/callback', sessionMiddleware, async (req, res) => {
logger.info({ hook: req.body?.hook, payload: req.body?.payload })
+1 -1
View File
@@ -5,7 +5,7 @@ import {
import type { ScheduleExecution } from '@/modules/core/domain/scheduledTasks/operations'
import { db } from '@/db/knex'
import { getManifestByUrn, getToken } from '@/modules/acc/clients/autodesk'
import { isReadyForImport } from '@/modules/acc/domain/logic'
import { isReadyForImport } from '@/modules/acc/helpers/svfUtils'
import type { Logger } from '@/observability/logging'
import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector'
import {
@@ -3,9 +3,12 @@ import {
getToken,
tryRegisterAccWebhook
} from '@/modules/acc/clients/autodesk'
import { ImporterAutomateFunctions } from '@/modules/acc/domain/constants'
import {
AccSyncItemStatuses,
ImporterAutomateFunctions
} from '@/modules/acc/domain/constants'
import { AccSyncItemEvents } from '@/modules/acc/domain/events'
import { isReadyForImport } from '@/modules/acc/domain/logic'
import { isReadyForImport } from '@/modules/acc/helpers/svfUtils'
import type {
CountAccSyncItems,
DeleteAccSyncItemById,
@@ -27,6 +30,7 @@ import {
import { getServerOrigin } from '@/modules/shared/helpers/envHelper'
import type { EventBusEmit } from '@/modules/shared/services/eventBus'
import cryptoRandomString from 'crypto-random-string'
import type { Exact } from 'type-fest'
export type CreateAccSyncItem = (params: {
syncItem: Pick<
@@ -101,7 +105,7 @@ export const createAccSyncItemFactory =
...syncItem,
id: cryptoRandomString({ length: 10 }),
automationId: automation.id,
status: 'PENDING',
status: AccSyncItemStatuses.pending,
authorId: creatorUserId,
accWebhookId: webhookId ?? undefined,
createdAt: new Date(),
@@ -136,20 +140,6 @@ export const createAccSyncItemFactory =
return await deps.triggerSyncItemAutomation({ id: newSyncItem.id })
}
export type GetAccSyncItem = (params: { id: string }) => Promise<AccSyncItem>
export const getAccSyncItemFactory =
(deps: { getAccSyncItemById: GetAccSyncItemById }): GetAccSyncItem =>
async ({ id }) => {
const syncItem = await deps.getAccSyncItemById({ id })
if (!syncItem) {
throw new SyncItemNotFoundError()
}
return syncItem
}
export type GetPaginatedAccSyncItems = (params: {
projectId: string
filter?: {
@@ -190,8 +180,10 @@ export const getPaginatedAccSyncItemsFactory =
}
}
export type UpdateAccSyncItem = (params: {
syncItem: Partial<AccSyncItem> & Pick<AccSyncItem, 'id'>
export type UpdateAccSyncItem = <
Item extends Exact<Partial<AccSyncItem> & Pick<AccSyncItem, 'id'>, Item>
>(params: {
syncItem: Item
}) => Promise<AccSyncItem>
export const updateAccSyncItemFactory =
@@ -92,6 +92,8 @@ import type {
import { logger } from '@/observability/logging'
import { getLastVersionsByProjectIdFactory } from '@/modules/core/repositories/versions'
import type { StreamRoles } from '@speckle/shared'
import { getAccSyncItemsByIdFactory } from '@/modules/acc/repositories/accSyncItems'
import type { AccSyncItem } from '@/modules/acc/domain/types'
declare module '@/modules/core/loaders' {
interface ModularizedDataLoaders extends ReturnType<typeof dataLoadersDefinition> {}
@@ -139,6 +141,7 @@ const dataLoadersDefinition = defineRequestDataloaders(
const getStreamsCollaboratorCounts = getStreamsCollaboratorCountsFactory({
db
})
const getAccSyncItemsById = getAccSyncItemsByIdFactory({ db })
return {
streams: {
@@ -548,6 +551,15 @@ const dataLoadersDefinition = defineRequestDataloaders(
return appIds.map((i) => results[i] || [])
})
},
acc: {
getAccSyncItem: createLoader<string, Nullable<AccSyncItem>>(async (ids) => {
const results = keyBy(
await getAccSyncItemsById({ ids: ids.slice() }),
(i) => i.id
)
return ids.map((i) => results[i] || null)
})
},
automations: {
getAutomation: createLoader<string, Nullable<AutomationRecord>>(async (ids) => {
const results = keyBy(
@@ -94,11 +94,11 @@ export type AccSyncItemMutationsUpdateArgs = {
};
export const AccSyncItemStatus = {
Failed: 'FAILED',
Paused: 'PAUSED',
Pending: 'PENDING',
Succeeded: 'SUCCEEDED',
Syncing: 'SYNCING'
Failed: 'failed',
Paused: 'paused',
Pending: 'pending',
Succeeded: 'succeeded',
Syncing: 'syncing'
} as const;
export type AccSyncItemStatus = typeof AccSyncItemStatus[keyof typeof AccSyncItemStatus];
+11
View File
@@ -104,6 +104,7 @@
"./viewer/route": "./src/viewer/helpers/route.ts",
"./viewer/state": "./src/viewer/helpers/state.ts",
"./automate": "./src/automate/index.ts",
"./acc": "./src/acc/index.ts",
"./dist/*": "./dist/*"
},
"exclude": [
@@ -310,6 +311,16 @@
"default": "./dist/commonjs/automate/index.js"
}
},
"./acc": {
"import": {
"types": "./dist/esm/acc/index.d.ts",
"default": "./dist/esm/acc/index.js"
},
"require": {
"types": "./dist/commonjs/acc/index.d.ts",
"default": "./dist/commonjs/acc/index.js"
}
},
"./dist/*": "./dist/*"
}
}
+41
View File
@@ -0,0 +1,41 @@
export type AccTokens = {
access_token: string
refresh_token: string
token_type: string
id_token: string
expires_in: number
}
export type AccUserInfo = {
userId: string
userName: string
emailId: string
firstName: string
lastName: string
}
export type AccHub = {
id: string
attributes: { name: string; region: string; extension: Record<string, unknown> }
}
export type AccProject = {
id: string
attributes: { name: string; lastModifiedTime: string }
relationships: Record<string, unknown>
}
export type AccItem = {
id: string
type?: string
latestVersionId?: string // we mutate on the way
fileExtension: string
storageUrn?: string // we mutate on the way
attributes: {
name: string
displayName: string
createTime?: string
extension?: Record<string, unknown>
versionNumber: number
}
}
+1
View File
@@ -0,0 +1 @@
export * from './helpers/types.js'