Skip to content

Commit 4901723

Browse files
authored
chore: refactor integrations.settings column and extract nango mappings (CM-963) (#3866)
Signed-off-by: Uroš Marolt <uros@marolt.me>
1 parent 61d1a44 commit 4901723

45 files changed

Lines changed: 535 additions & 408 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/src/api/integration/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export default (app) => {
2121
app.post(`/integration/query`, safeWrap(require('./integrationQuery').default))
2222
app.post(`/integration`, safeWrap(require('./integrationCreate').default))
2323
app.put(`/integration/:id`, safeWrap(require('./integrationUpdate').default))
24-
app.post(`/integration/import`, safeWrap(require('./integrationImport').default))
2524
app.delete(`/integration`, safeWrap(require('./integrationDestroy').default))
2625
app.get(`/integration/autocomplete`, safeWrap(require('./integrationAutocomplete').default))
2726
app.get(`/integration/global`, safeWrap(require('./integrationGlobal').default))

backend/src/api/integration/integrationCreate.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import PermissionChecker from '../../services/user/permissionChecker'
55
export default async (req, res) => {
66
new PermissionChecker(req).validateHas(Permissions.values.integrationCreate)
77

8-
new PermissionChecker(req).validateIntegrationsProtectedFields(req.body)
9-
108
const payload = await new IntegrationService(req).create(req.body)
119

1210
await req.responseHandler.success(req, res, payload)

backend/src/api/integration/integrationImport.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

backend/src/api/integration/integrationUpdate.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import PermissionChecker from '../../services/user/permissionChecker'
44

55
export default async (req, res) => {
66
new PermissionChecker(req).validateHas(Permissions.values.integrationEdit)
7-
new PermissionChecker(req).validateIntegrationsProtectedFields(req.body)
87

98
const payload = await new IntegrationService(req).update(req.params.id, req.body)
109

backend/src/database/migrations/U1771839722__refactorIntegrationsTable.sql

Whitespace-only changes.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
-- Backup full integration rows (including deprecated columns and nangoMapping in settings) before any modifications
2+
CREATE TABLE integration.integrations_backup_02_24_2026 AS
3+
SELECT id, settings, "limitCount", "limitLastResetAt", "emailSentAt", "importHash" FROM integrations;
4+
5+
-- Part A: Drop deprecated columns from integrations table
6+
ALTER TABLE public.integrations
7+
DROP COLUMN IF EXISTS "limitCount",
8+
DROP COLUMN IF EXISTS "limitLastResetAt",
9+
DROP COLUMN IF EXISTS "emailSentAt",
10+
DROP COLUMN IF EXISTS "importHash";
11+
12+
-- Drop same columns from integrationsBackup table (created as SELECT * FROM integrations WHERE 1=2)
13+
ALTER TABLE public."integrationsBackup"
14+
DROP COLUMN IF EXISTS "limitCount",
15+
DROP COLUMN IF EXISTS "limitLastResetAt",
16+
DROP COLUMN IF EXISTS "emailSentAt",
17+
DROP COLUMN IF EXISTS "importHash";
18+
19+
-- Part B: Create nango_mapping table and migrate data from settings JSONB
20+
CREATE TABLE integration.nango_mapping (
21+
"integrationId" UUID NOT NULL,
22+
"connectionId" TEXT NOT NULL,
23+
"repositoryId" UUID,
24+
owner TEXT NOT NULL,
25+
"repoName" TEXT NOT NULL,
26+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
27+
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
28+
PRIMARY KEY ("integrationId", "connectionId"),
29+
FOREIGN KEY ("integrationId") REFERENCES integrations(id) ON DELETE CASCADE,
30+
FOREIGN KEY ("repositoryId") REFERENCES repositories(id) ON DELETE SET NULL
31+
);
32+
33+
CREATE UNIQUE INDEX ix_nango_mapping_connectionId ON integration.nango_mapping ("connectionId");
34+
CREATE INDEX ix_nango_mapping_repositoryId ON integration.nango_mapping ("repositoryId");
35+
36+
-- Migrate existing data from settings.nangoMapping
37+
INSERT INTO integration.nango_mapping ("integrationId", "connectionId", "repositoryId", owner, "repoName")
38+
SELECT
39+
i.id,
40+
nm.key,
41+
r.id,
42+
nm.value->>'owner',
43+
nm.value->>'repoName'
44+
FROM integrations i
45+
CROSS JOIN LATERAL jsonb_each(i.settings->'nangoMapping') nm
46+
LEFT JOIN repositories r
47+
ON lower(r.url) = lower('https://github.com/' || (nm.value->>'owner') || '/' || (nm.value->>'repoName'))
48+
AND r."sourceIntegrationId" = i.id
49+
AND r."deletedAt" IS NULL
50+
WHERE i.platform = 'github-nango'
51+
AND i."deletedAt" IS NULL
52+
AND i.settings->'nangoMapping' IS NOT NULL
53+
ON CONFLICT DO NOTHING;
54+
55+
-- Remove nangoMapping from settings (only github-nango integrations ever stored this key)
56+
UPDATE integrations
57+
SET settings = settings - 'nangoMapping'
58+
WHERE platform = 'github-nango'
59+
AND settings->'nangoMapping' IS NOT NULL;

backend/src/database/models/integration.ts

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,6 @@ export default (sequelize) => {
1515
status: {
1616
type: DataTypes.TEXT,
1717
},
18-
limitCount: {
19-
type: DataTypes.INTEGER,
20-
},
21-
limitLastResetAt: {
22-
type: DataTypes.DATE,
23-
},
2418
token: {
2519
type: DataTypes.TEXT,
2620
},
@@ -34,26 +28,9 @@ export default (sequelize) => {
3428
integrationIdentifier: {
3529
type: DataTypes.TEXT,
3630
},
37-
importHash: {
38-
type: DataTypes.STRING(255),
39-
allowNull: true,
40-
validate: {
41-
len: [0, 255],
42-
},
43-
},
44-
emailSentAt: {
45-
type: DataTypes.DATE,
46-
},
4731
},
4832
{
4933
indexes: [
50-
{
51-
unique: true,
52-
fields: ['importHash', 'tenantId'],
53-
where: {
54-
deletedAt: null,
55-
},
56-
},
5734
{
5835
unique: false,
5936
fields: ['integrationIdentifier'],

backend/src/database/repositories/integrationRepository.ts

Lines changed: 20 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,10 @@ class IntegrationRepository {
3333
...lodash.pick(data, [
3434
'platform',
3535
'status',
36-
'limitCount',
37-
'limitLastResetAt',
3836
'token',
3937
'refreshToken',
4038
'settings',
4139
'integrationIdentifier',
42-
'importHash',
43-
'emailSentAt',
4440
]),
4541
segmentId: segment.id,
4642
tenantId: DEFAULT_TENANT_ID,
@@ -84,14 +80,10 @@ class IntegrationRepository {
8480
...lodash.pick(data, [
8581
'platform',
8682
'status',
87-
'limitCount',
88-
'limitLastResetAt',
8983
'token',
9084
'refreshToken',
9185
'settings',
9286
'integrationIdentifier',
93-
'importHash',
94-
'emailSentAt',
9587
]),
9688

9789
updatedById: currentUser.id,
@@ -423,46 +415,6 @@ class IntegrationRepository {
423415
})
424416
}
425417

426-
if (filter.limitCountRange) {
427-
const [start, end] = filter.limitCountRange
428-
429-
if (start !== undefined && start !== null && start !== '') {
430-
advancedFilter.and.push({
431-
limitCount: {
432-
gte: start,
433-
},
434-
})
435-
}
436-
437-
if (end !== undefined && end !== null && end !== '') {
438-
advancedFilter.and.push({
439-
limitCount: {
440-
lte: end,
441-
},
442-
})
443-
}
444-
}
445-
446-
if (filter.limitLastResetAtRange) {
447-
const [start, end] = filter.limitLastResetAtRange
448-
449-
if (start !== undefined && start !== null && start !== '') {
450-
advancedFilter.and.push({
451-
limitLastResetAt: {
452-
gte: start,
453-
},
454-
})
455-
}
456-
457-
if (end !== undefined && end !== null && end !== '') {
458-
advancedFilter.and.push({
459-
limitLastResetAt: {
460-
lte: end,
461-
},
462-
})
463-
}
464-
}
465-
466418
if (filter.integrationIdentifier) {
467419
advancedFilter.and.push({
468420
integrationIdentifier: filter.integrationIdentifier,
@@ -633,6 +585,26 @@ class IntegrationRepository {
633585

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

588+
// For github-nango integrations, populate settings.nangoMapping from the dedicated table
589+
// so the API contract remains unchanged for frontend consumers
590+
if (output.platform === PlatformType.GITHUB_NANGO) {
591+
const rows = await record.sequelize.query(
592+
`SELECT "connectionId", owner, "repoName" FROM integration.nango_mapping WHERE "integrationId" = :integrationId`,
593+
{
594+
replacements: { integrationId: output.id },
595+
type: QueryTypes.SELECT,
596+
},
597+
)
598+
599+
if (rows.length > 0) {
600+
const nangoMapping: Record<string, { owner: string; repoName: string }> = {}
601+
for (const row of rows as { connectionId: string; owner: string; repoName: string }[]) {
602+
nangoMapping[row.connectionId] = { owner: row.owner, repoName: row.repoName }
603+
}
604+
output.settings = { ...output.settings, nangoMapping }
605+
}
606+
}
607+
636608
return output
637609
}
638610
}

backend/src/security/permissions.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,6 @@ class Permissions {
165165
id: 'organizationAutocomplete',
166166
allowedRoles: [roles.admin, roles.projectAdmin, roles.readonly],
167167
},
168-
integrationImport: {
169-
id: 'integrationImport',
170-
allowedRoles: [roles.admin, roles.projectAdmin],
171-
},
172-
integrationControlLimit: {
173-
id: 'integrationControlLimit',
174-
allowedRoles: [],
175-
},
176168
integrationCreate: {
177169
id: 'integrationCreate',
178170
allowedRoles: [roles.admin, roles.projectAdmin],

backend/src/services/integrationService.ts

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -556,46 +556,6 @@ export default class IntegrationService {
556556
return result
557557
}
558558

559-
async import(data, importHash) {
560-
const transaction = await SequelizeRepository.createTransaction(this.options)
561-
562-
try {
563-
if (!importHash) {
564-
throw new Error400(this.options.language, 'importer.errors.importHashRequired')
565-
}
566-
567-
if (await this._isImportHashExistent(importHash)) {
568-
throw new Error400(this.options.language, 'importer.errors.importHashExistent')
569-
}
570-
571-
const dataToCreate = {
572-
...data,
573-
importHash,
574-
}
575-
576-
const result = this.create(dataToCreate, transaction)
577-
578-
await SequelizeRepository.commitTransaction(transaction)
579-
580-
return await result
581-
} catch (err) {
582-
await SequelizeRepository.rollbackTransaction(transaction)
583-
584-
throw err
585-
}
586-
}
587-
588-
async _isImportHashExistent(importHash) {
589-
const count = await IntegrationRepository.count(
590-
{
591-
importHash,
592-
},
593-
this.options,
594-
)
595-
596-
return count > 0
597-
}
598-
599559
/**
600560
* Returns installation access token for a Github App installation
601561
* @param installId Install id of the Github app
@@ -867,14 +827,7 @@ export default class IntegrationService {
867827
{
868828
id: integrationId,
869829
platform: PlatformType.GITHUB_NANGO,
870-
settings: {
871-
...settings,
872-
...(integration.settings.nangoMapping
873-
? {
874-
nangoMapping: integration.settings.nangoMapping,
875-
}
876-
: {}),
877-
},
830+
settings,
878831
},
879832
transaction,
880833
)
@@ -1925,8 +1878,6 @@ export default class IntegrationService {
19251878
integrationIdentifier: profileId,
19261879
token,
19271880
refreshToken,
1928-
limitCount: 0,
1929-
limitLastResetAt: moment().format('YYYY-MM-DD HH:mm:ss'),
19301881
status: 'in-progress',
19311882
settings: {
19321883
followers: [],

0 commit comments

Comments
 (0)