UBERF-12445: Fix adding second Github integration for same user (#9531)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2025-07-13 22:04:08 +07:00
committed by GitHub
parent 6c242660c5
commit fa6fcc12aa
5 changed files with 135 additions and 62 deletions
+120 -52
View File
@@ -58,6 +58,10 @@ export interface InstallationRecord {
suspended: boolean
}
interface IntegrationDataValue {
installationId: number | number[]
}
export class PlatformWorker {
private readonly clients = new Map<WorkspaceUuid, GithubWorker>()
@@ -123,12 +127,12 @@ export class PlatformWorker {
if (i.workspaceUuid == null) {
continue
}
const installationId = i.data?.installationId
const installationId = (i.data as IntegrationDataValue)?.installationId
if (installationId !== undefined) {
this.integrations.push({
accountId: i.socialId,
workspace: i.workspaceUuid,
installationId
installationId: Array.isArray(installationId) ? installationId : [installationId]
})
}
}
@@ -137,19 +141,32 @@ export class PlatformWorker {
for (const integr of [...this.integrations]) {
// We need to check and remove integrations without a real integration's
if (!this.installations.has(integr.installationId)) {
ctx.warn('Installation was deleted during service shutdown', {
installationId: integr.installationId,
const ids = integr.installationId
const missing = ids.filter((id) => !this.installations.has(id))
if (missing.length > 0) {
const has = ids.filter((id) => this.installations.has(id))
ctx.warn('Few Installation was deleted during service shutdown', {
installationId: missing,
workspace: integr.workspace
})
await accountsClient.deleteIntegration({
kind: 'github',
workspaceUuid: integr.workspace,
socialId: integr.accountId
})
this.integrations = this.integrations.filter((it) => it.installationId !== integr.installationId)
if (has.length > 0) {
await accountsClient.updateIntegration({
kind: 'github',
workspaceUuid: integr.workspace,
socialId: integr.accountId,
data: { installationId: has } satisfies IntegrationDataValue
})
} else {
await accountsClient.deleteIntegration({
kind: 'github',
workspaceUuid: integr.workspace,
socialId: integr.accountId
})
}
}
}
this.integrations = this.integrations.filter((it) => it.installationId.length > 0)
void this.doSyncWorkspaces().catch((err) => {
ctx.error('error during sync workspaces', { err })
@@ -227,29 +244,56 @@ export class PlatformWorker {
const sysToken = generateToken(systemAccountUuid, undefined, { service: 'github' })
const accountsClient = getAccountClient(config.AccountsURL, sysToken)
const oldInstallation = this.integrations.find((it) => it.installationId === installationId)
if (oldInstallation != null) {
ctx.info('update integration', { workspace, installationId, accountId })
const oldInstallation = this.integrations.filter((it) => it.installationId.includes(installationId))
if (oldInstallation.length > 0) {
ctx.info('update integrations', { workspace, installationId, accountId })
// What to do with installation in different workspace?
// Let's remove it and sync to new one.
if (oldInstallation.workspace !== workspace) {
//
const oldWorkspace = oldInstallation.workspace
const oldWorkspaces = oldInstallation.filter((it) => it.workspace !== workspace)
if (oldWorkspaces.length > 0) {
const oldWorkspace = oldWorkspaces[0].workspace
await accountsClient.createIntegration({
kind: 'github',
workspaceUuid: workspace,
socialId: accountId,
data: { installationId: oldInstallation.installationId }
})
for (const oldInstallation of oldWorkspaces) {
const has = oldInstallation.installationId.filter((it) => it !== installationId)
if (has.length > 0) {
oldInstallation.installationId = has
await accountsClient.updateIntegration({
kind: 'github',
workspaceUuid: oldWorkspace,
socialId: accountId
})
} else {
await accountsClient.deleteIntegration({
kind: 'github',
workspaceUuid: oldWorkspace,
socialId: accountId
})
}
}
await accountsClient.deleteIntegration({
kind: 'github',
workspaceUuid: oldWorkspace,
socialId: accountId
})
oldInstallation.workspace = workspace
// We need new integeration to be added to new workspace
const existingRecord = this.integrations.find((it) => it.workspace === workspace && it.accountId === accountId)
if (existingRecord !== undefined) {
existingRecord.installationId.push(installationId)
await accountsClient.updateIntegration({
kind: 'github',
workspaceUuid: workspace,
socialId: accountId,
data: { installationId: existingRecord.installationId } satisfies IntegrationDataValue
})
} else {
await accountsClient.createIntegration({
kind: 'github',
workspaceUuid: workspace,
socialId: accountId,
data: { installationId } satisfies IntegrationDataValue
})
this.integrations.push({
workspace,
installationId: [installationId],
accountId
})
}
const oldWorker = this.clients.get(oldWorkspace) as GithubWorker
if (oldWorker !== undefined) {
@@ -276,25 +320,35 @@ export class PlatformWorker {
this.triggerCheckWorkspaces()
return
}
const record: GithubIntegrationRecord = {
workspace,
installationId,
accountId
}
ctx.info('add integration', { workspace, installationId, accountId })
await ctx.with(
'add integration',
{},
async (ctx) => {
await accountsClient.createIntegration({
kind: 'github',
workspaceUuid: record.workspace,
socialId: record.accountId,
data: { installationId: record.installationId }
})
this.integrations.push(record)
const existing = this.integrations.find((it) => it.workspace === workspace && it.accountId === accountId)
if (existing !== undefined) {
existing.installationId.push(installationId)
await accountsClient.updateIntegration({
kind: 'github',
workspaceUuid: existing.workspace,
socialId: existing.accountId,
data: { installationId: existing.installationId } satisfies IntegrationDataValue
})
} else {
const record: GithubIntegrationRecord = {
workspace,
installationId: [installationId],
accountId
}
await accountsClient.createIntegration({
kind: 'github',
workspaceUuid: record.workspace,
socialId: record.accountId,
data: { installationId: record.installationId }
})
this.integrations.push(record)
}
},
{ workspace, installationId, accountId }
)
@@ -782,10 +836,10 @@ export class PlatformWorker {
this.installations.delete(installId)
this.ctx.info('handle integration delete', { installId, name: existing?.installationName })
const interg = this.integrations.find((it) => it.installationId === installId)
const interg = this.integrations.filter((it) => it.installationId.includes(installId))
// We already have, worker we need to update it.
const worker = this.getWorker(installId) ?? (interg !== undefined ? this.clients.get(interg.workspace) : undefined)
const worker = this.getWorker(installId) ?? (interg.length > 0 ? this.clients.get(interg[0].workspace) : undefined)
if (worker !== undefined) {
const integeration = worker.integrations.get(installId)
if (integeration !== undefined) {
@@ -802,16 +856,30 @@ export class PlatformWorker {
this.ctx.info('No worker for removed installation', { installId, name: existing?.installationName })
// No worker
}
this.integrations = this.integrations.filter((it) => it.installationId !== installId)
if (interg !== undefined) {
if (interg.length > 0) {
const sysToken = generateToken(systemAccountUuid, undefined, { service: 'github' })
const sysAccountClient = getAccountClient(config.AccountsURL, sysToken)
await sysAccountClient.deleteIntegration({
kind: 'github',
workspaceUuid: interg.workspace,
socialId: interg.accountId
})
for (const intgr of interg) {
const has = intgr.installationId.filter((it) => it !== installId)
if (has.length > 0) {
await sysAccountClient.updateIntegration({
kind: 'github',
workspaceUuid: intgr.workspace,
socialId: intgr.accountId,
data: { installationId: has } satisfies IntegrationDataValue
})
} else {
intgr.installationId = []
await sysAccountClient.deleteIntegration({
kind: 'github',
workspaceUuid: intgr.workspace,
socialId: intgr.accountId
})
}
}
}
this.integrations = this.integrations.filter((it) => it.installationId.length > 0)
this.triggerCheckWorkspaces()
}
+2 -1
View File
@@ -97,7 +97,8 @@ export async function start (ctx: MeasureContext, brandingMap: BrandingMap): Pro
ctx.error('failed to map-installation', {
workspace: tok.workspace,
installationid: payloadData.installationId,
email: tok?.account
email: tok?.account,
error: err.message
})
res.status(401)
res.json({ error: err.message })
+1 -1
View File
@@ -188,7 +188,7 @@ export interface DocSyncManager {
* @public
*/
export interface GithubIntegrationRecord {
installationId: number
installationId: number[]
workspace: WorkspaceUuid
accountId: PersonId
}
+11 -5
View File
@@ -1330,18 +1330,24 @@ export class GithubWorker implements IntegrationManager {
}
async checkMapping (): Promise<void> {
for (const intgr of this.platform.integrations.filter((it) => it.workspace === this.workspace.uuid)) {
const installations = new Map<number, PersonId>()
for (const integeration of this.platform.integrations.filter((it) => it.workspace === this.workspace.uuid)) {
for (const installationId of integeration.installationId) {
installations.set(installationId, integeration.accountId)
}
}
for (const [installationId, accountId] of installations.entries()) {
const integration = await this._client.findOne(github.class.GithubIntegration, {
installationId: intgr.installationId
installationId
})
const installation = this.installations.get(intgr.installationId) as InstallationRecord
const installation = this.installations.get(installationId) as InstallationRecord
if (integration === undefined && installation !== undefined) {
await this._client.createDoc(
github.class.GithubIntegration,
core.space.Configuration,
{
alive: !installation.suspended,
installationId: intgr.installationId,
installationId,
clientId: config.ClientID,
name: installation.installationName,
nodeId: installation.loginNodeId,
@@ -1349,7 +1355,7 @@ export class GithubWorker implements IntegrationManager {
},
generateId(),
Date.now(),
intgr.accountId
accountId
)
this.triggerUpdate()
} else if (integration !== undefined) {