Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-- Backup settings before any modifications
CREATE TABLE integration.integrations_settings_backup_02_28_2026 AS
SELECT id, settings FROM integrations;

-- Strip repos from orgs in settings for github and github-nango integrations
-- Repos now live in public.repositories table and are populated into API responses
-- via the compatibility layer in integrationRepository._populateRelations
UPDATE integrations
SET settings = jsonb_set(
settings,
'{orgs}',
(
SELECT coalesce(jsonb_agg(
org - 'repos'
), '[]'::jsonb)
FROM jsonb_array_elements(settings->'orgs') org
)
)
WHERE platform IN ('github', 'github-nango')
AND settings->'orgs' IS NOT NULL
AND "deletedAt" IS NULL;

-- Also clean up top-level repos/unavailableRepos if present
UPDATE integrations
SET settings = settings - 'repos' - 'unavailableRepos'
WHERE platform IN ('github', 'github-nango')
AND (settings ? 'repos' OR settings ? 'unavailableRepos')
AND "deletedAt" IS NULL;
65 changes: 60 additions & 5 deletions backend/src/database/repositories/integrationRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,26 +585,81 @@ class IntegrationRepository {

const output = record.get({ plain: true })

// For github-nango integrations, populate settings.nangoMapping from the dedicated table
// so the API contract remains unchanged for frontend consumers
// For github-nango integrations, populate settings.nangoMapping from dedicated table
if (output.platform === PlatformType.GITHUB_NANGO) {
const rows = await record.sequelize.query(
const nangoRows = await record.sequelize.query(
`SELECT "connectionId", owner, "repoName" FROM integration.nango_mapping WHERE "integrationId" = :integrationId`,
{
replacements: { integrationId: output.id },
type: QueryTypes.SELECT,
},
)

if (rows.length > 0) {
if (nangoRows.length > 0) {
const nangoMapping: Record<string, { owner: string; repoName: string }> = {}
for (const row of rows as { connectionId: string; owner: string; repoName: string }[]) {
for (const row of nangoRows as {
connectionId: string
owner: string
repoName: string
}[]) {
nangoMapping[row.connectionId] = { owner: row.owner, repoName: row.repoName }
}
output.settings = { ...output.settings, nangoMapping }
}
}

// For both github and github-nango, populate orgs[].repos from repositories table
if (
(output.platform === PlatformType.GITHUB || output.platform === PlatformType.GITHUB_NANGO) &&
output.settings?.orgs?.length > 0
) {
const repoRows = (await record.sequelize.query(
`SELECT url, split_part(url, '/', -1) as name, split_part(url, '/', -2) as owner, "forkedFrom", "updatedAt"
FROM public.repositories
WHERE "sourceIntegrationId" = :integrationId AND "deletedAt" IS NULL
ORDER BY url`,
{
replacements: { integrationId: output.id },
type: QueryTypes.SELECT,
},
)) as {
url: string
name: string
owner: string
forkedFrom: string | null
updatedAt: string
}[]

// Only overwrite orgs[].repos from the repositories table if there are rows.
// During the 'mapping' phase (legacy github connect), repos live in settings
// before being written to the repositories table via mapGithubRepos.
if (repoRows.length > 0) {
const reposByOwner: Record<string, typeof repoRows> = {}
for (const repo of repoRows) {
if (!reposByOwner[repo.owner]) reposByOwner[repo.owner] = []
reposByOwner[repo.owner].push(repo)
}

output.settings = {
...output.settings,
orgs: output.settings.orgs.map((org) => ({
...org,
repos: (reposByOwner[org.name] || []).map((r) => ({
url: r.url,
name: r.name,
owner: r.owner,
forkedFrom: r.forkedFrom,
updatedAt: r.updatedAt,
})),
})),
}
}

// Strip legacy top-level keys that may still exist in the DB column
delete output.settings.repos
delete output.settings.unavailableRepos
}

return output
}
}
Expand Down
30 changes: 11 additions & 19 deletions backend/src/services/collectionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
} from '@crowd/data-access-layer/src/collections'
import { fetchIntegrationsForSegment } from '@crowd/data-access-layer/src/integrations'
import { QueryFilter } from '@crowd/data-access-layer/src/query'
import { getReposForGithubIntegration } from '@crowd/data-access-layer/src/repositories'
import {
ICreateRepositoryGroup,
IRepositoryGroup,
Expand Down Expand Up @@ -533,13 +534,8 @@ export class CollectionService extends LoggerBase {
return listRepositoryGroups(qx, { insightsProjectId })
}

static isSingleRepoOrg(orgs: GithubIntegrationSettings['orgs']): boolean {
return (
Array.isArray(orgs) &&
orgs.length === 1 &&
Array.isArray(orgs[0]?.repos) &&
orgs[0].repos.length === 1
)
static isSingleRepoOrg(orgs: GithubIntegrationSettings['orgs'], repoCount: number): boolean {
return Array.isArray(orgs) && orgs.length === 1 && repoCount === 1
}

/**
Expand Down Expand Up @@ -613,13 +609,12 @@ export class CollectionService extends LoggerBase {
const settings = githubIntegration.settings as GithubIntegrationSettings

// The orgs must have at least one repo
if (
!settings?.orgs ||
!Array.isArray(settings.orgs) ||
settings.orgs.length === 0 ||
!Array.isArray(settings.orgs[0].repos) ||
settings.orgs[0].repos.length === 0
) {
if (!settings?.orgs || !Array.isArray(settings.orgs) || settings.orgs.length === 0) {
return null
}

const repos = await getReposForGithubIntegration(qx, githubIntegration.id)
if (repos.length === 0) {
return null
}

Expand All @@ -633,11 +628,8 @@ export class CollectionService extends LoggerBase {
return null
}

const details = CollectionService.isSingleRepoOrg(settings.orgs)
? await GithubIntegrationService.findRepoDetails(
mainOrg.name,
settings.orgs[0].repos[0].name,
)
const details = CollectionService.isSingleRepoOrg(settings.orgs, repos.length)
? await GithubIntegrationService.findRepoDetails(mainOrg.name, repos[0].name)
: {
...(await GithubIntegrationService.findOrgDetails(mainOrg.name)),
topics: mainOrg.topics,
Expand Down
Loading