Files
huly-platform/dev/tool/src/restoreGithub.ts
T
Artyom SavchenkoandGitHub 35cd8a3d86 Fix admin integrations (#10076)
* Get your own integrations for admin

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Remove admin check

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Clean up

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Allow to add social id by admin

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Use service token for integrations test

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Update version

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Fix tokens

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Update allowed services

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Use workspace service

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Fix service client

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Get rid of admin service name

Signed-off-by: Artem Savchenko <armisav@gmail.com>

---------

Signed-off-by: Artem Savchenko <armisav@gmail.com>
2025-10-14 17:01:10 +07:00

161 lines
5.9 KiB
TypeScript

import { type IntegrationKind, type PersonId, type WorkspaceUuid, systemAccountUuid } from '@hcengineering/core'
import { getAccountClient } from '@hcengineering/server-client'
import { generateToken } from '@hcengineering/server-token'
import { type Integration } from '@hcengineering/account-client'
import { getDBClient } from '@hcengineering/postgres'
const GITHUB_INTEGRATION: IntegrationKind = 'github' as any
interface IntegrationSetting {
workspaceId: WorkspaceUuid
createdBy: PersonId
installationIds: number[]
}
export async function restoreGithubIntegrations (dbUrl: string, dryrun: boolean): Promise<void> {
try {
const pg = getDBClient(dbUrl)
const pgClient = await pg.getClient()
const token = generateToken(systemAccountUuid, undefined, { service: 'tool', admin: 'true' })
const accountClient = getAccountClient(token)
const integrationSettings = await pgClient<
{ workspaceId: WorkspaceUuid, createdBy: PersonId, installationId: number }[]
>`
SELECT "workspaceId", "createdBy", data -> 'installationId' "installationId"
FROM github
WHERE _class='github:class:GithubIntegration'
`
// Group by workspace and createdBy, collecting installationIds
const uniqueSettings = groupIntegrationSettings(integrationSettings)
console.info('Start restoring GitHub installation IDs v3, count: ', uniqueSettings.length)
let createdCount = 0
let updatedCount = 0
for (const setting of uniqueSettings) {
try {
const isMigratedSocialId = !isNaN(parseInt(setting.createdBy, 10))
if (!isMigratedSocialId) {
// Integration should not be affected in this case, since in corrupted migration socialIds were also used to search integrations
console.info('Skip non-migrated integration:', setting.createdBy, setting.workspaceId)
continue
}
const existingIntegration = await accountClient.getIntegration({
workspaceUuid: setting.workspaceId,
socialId: setting.createdBy,
kind: GITHUB_INTEGRATION
})
let installationId: number | number[]
if (existingIntegration != null) {
const existingInstallationId = existingIntegration?.data?.installationId
// Merge existing and new installation IDs
const allInstallationIds = new Set<number>()
if (existingInstallationId != null) {
if (Array.isArray(existingInstallationId)) {
existingInstallationId.filter(isValidInstallationId).forEach((id) => allInstallationIds.add(id))
} else if (isValidInstallationId(existingInstallationId)) {
allInstallationIds.add(existingInstallationId)
}
}
setting.installationIds.forEach((id) => allInstallationIds.add(id))
const mergedIds = Array.from(allInstallationIds)
installationId = mergedIds.length === 1 ? mergedIds[0] : mergedIds
// Check if the merged result is the same as existing
const isSame = Array.isArray(installationId)
? Array.isArray(existingInstallationId) &&
installationId.length === existingInstallationId.length &&
installationId.every((id) => existingInstallationId.includes(id))
: existingInstallationId === installationId
if (isSame) {
if (dryrun) {
console.info('Dry run: skip existing integration', existingIntegration)
}
continue
}
const updatedData = {
...(existingIntegration.data ?? {}),
installationId
}
const updatedIntegration: Integration = {
...existingIntegration,
data: updatedData
}
if (dryrun) {
console.info('Dry run: would update integration', existingIntegration, updatedIntegration)
updatedCount++
continue
}
await accountClient.updateIntegration(updatedIntegration)
updatedCount++
} else {
installationId = setting.installationIds.length === 1 ? setting.installationIds[0] : setting.installationIds
const integration: Integration = {
workspaceUuid: setting.workspaceId,
socialId: setting.createdBy,
kind: GITHUB_INTEGRATION,
data: {
installationId
}
}
if (dryrun) {
console.info('Dry run: would create integration', integration)
createdCount++
continue
}
await accountClient.createIntegration(integration)
createdCount++
}
} catch (e: any) {
console.error('Error restoring GitHub integration', setting.createdBy, setting.workspaceId, e)
}
}
console.info(`Finished restoring GitHub integrations, updated: ${updatedCount}, created: ${createdCount}`)
} catch (e) {
console.error('Failed to restore GitHub integrations', e)
}
}
function groupIntegrationSettings (
settings: Array<{ workspaceId: WorkspaceUuid, createdBy: PersonId, installationId: number }>
): IntegrationSetting[] {
const groupedSettings = new Map<string, IntegrationSetting>()
for (const setting of settings) {
if (setting.installationId == null) continue
const key = `${setting.workspaceId}-${setting.createdBy}`
const existing = groupedSettings.get(key)
if (existing != null) {
if (!existing.installationIds.includes(setting.installationId)) {
existing.installationIds.push(setting.installationId)
}
} else {
groupedSettings.set(key, {
workspaceId: setting.workspaceId,
createdBy: setting.createdBy,
installationIds: [setting.installationId]
})
}
}
return Array.from(groupedSettings.values())
}
function isValidInstallationId (id: any): boolean {
if (typeof id === 'number' && !isNaN(id) && id > 0) {
return true
}
const parsed = parseInt(String(id), 10)
return !isNaN(parsed) && parsed > 0
}