Skip to content

Commit 84f0c3b

Browse files
authored
feat: prevent duplicates [CM-2320] (#3282)
1 parent 3b4d13d commit 84f0c3b

File tree

5 files changed

+99
-6
lines changed

5 files changed

+99
-6
lines changed

backend/src/database/migrations/U1753110221__create_segment_repos_table.sql

Whitespace-only changes.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- 1. Create the table to map repositories to segments
2+
CREATE TABLE IF NOT EXISTS "segmentRepositories" (
3+
"repository" TEXT NOT NULL UNIQUE,
4+
"segmentId" UUID NOT NULL REFERENCES "segments"(id) ON DELETE CASCADE,
5+
"insightsProjectId" UUID REFERENCES "insightsProjects"(id) ON DELETE CASCADE,
6+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
7+
"excluded" BOOLEAN NOT NULL DEFAULT FALSE,
8+
"archived" BOOLEAN NOT NULL DEFAULT FALSE,
9+
10+
PRIMARY KEY ("repository", "segmentId")
11+
);

backend/src/database/repositories/segmentRepository.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,7 @@ class SegmentRepository extends RepositoryBase<
933933
"githubRepos" r
934934
where r."segmentId" = :segmentId
935935
and r."tenantId" = :tenantId
936+
and r."deletedAt" is null
936937
order by r.url
937938
`,
938939
{

backend/src/services/collectionService.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
createInsightsProject,
1515
deleteCollection,
1616
deleteInsightsProject,
17+
deleteMissingSegmentRepositories,
1718
disconnectProjectsAndCollections,
1819
findCollectionProjectConnections,
1920
queryCollectionById,
@@ -22,6 +23,7 @@ import {
2223
queryInsightsProjects,
2324
updateCollection,
2425
updateInsightsProject,
26+
upsertSegmentRepositories,
2527
} from '@crowd/data-access-layer/src/collections'
2628
import { fetchIntegrationsForSegment } from '@crowd/data-access-layer/src/integrations'
2729
import { OrganizationField, findOrgById, queryOrgs } from '@crowd/data-access-layer/src/orgs'
@@ -363,7 +365,17 @@ export class CollectionService extends LoggerBase {
363365
}
364366
}
365367

366-
async updateInsightsProject(id: string, project: Partial<ICreateInsightsProject>) {
368+
static normalizeRepositories(
369+
repositories?: string[] | { platform: string; url: string }[],
370+
): string[] {
371+
if (!repositories || repositories.length === 0) return []
372+
373+
return typeof repositories[0] === 'string'
374+
? (repositories as string[])
375+
: (repositories as { platform: string; url: string }[]).map((r) => r.url)
376+
}
377+
378+
async updateInsightsProject(insightsProjectId: string, project: Partial<ICreateInsightsProject>) {
367379
return SequelizeRepository.withTx(this.options, async (tx) => {
368380
const qx = SequelizeRepository.getQueryExecutor(this.options, tx)
369381

@@ -373,15 +385,24 @@ export class CollectionService extends LoggerBase {
373385
project.isLF = segment?.isLF ?? false
374386
}
375387

376-
await updateInsightsProject(qx, id, project)
388+
const { segmentId } = await updateInsightsProject(qx, insightsProjectId, project)
389+
390+
const repositories = CollectionService.normalizeRepositories(project.repositories)
391+
392+
await upsertSegmentRepositories(qx, { insightsProjectId, repositories, segmentId })
393+
394+
await deleteMissingSegmentRepositories(qx, {
395+
repositories,
396+
segmentId,
397+
})
377398

378399
if (project.collections) {
379-
await disconnectProjectsAndCollections(qx, { insightsProjectId: id })
400+
await disconnectProjectsAndCollections(qx, { insightsProjectId })
380401
await connectProjectsAndCollections(
381402
qx,
382403
project.collections.map((c) => ({
383-
insightsProjectId: id,
384404
collectionId: c,
405+
insightsProjectId,
385406
starred: project.starred ?? true,
386407
})),
387408
)
@@ -391,7 +412,7 @@ export class CollectionService extends LoggerBase {
391412
...this.options,
392413
transaction: tx,
393414
})
394-
return txSvc.findInsightsProjectById(id)
415+
return txSvc.findInsightsProjectById(insightsProjectId)
395416
})
396417
}
397418

services/libs/data-access-layer/src/collections/index.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,13 +271,21 @@ export async function updateInsightsProject(
271271
id: string,
272272
project: Partial<ICreateInsightsProject>,
273273
) {
274-
return updateTableById(
274+
const result = await updateTableById(
275275
qx,
276276
'insightsProjects',
277277
id,
278278
Object.values(InsightsProjectField),
279279
prepareProject(project),
280280
)
281+
282+
const updated = result?.rows?.[0]
283+
284+
if (!updated) {
285+
throw new Error(`Update failed or project with id ${id} not found`)
286+
}
287+
288+
return updated as IInsightsProject
281289
}
282290

283291
function prepareProject(project: Partial<ICreateInsightsProject>) {
@@ -296,3 +304,55 @@ export async function findBySlug(qx: QueryExecutor, slug: string) {
296304
})
297305
return collections
298306
}
307+
308+
export async function upsertSegmentRepositories(
309+
qx: QueryExecutor,
310+
{
311+
insightsProjectId,
312+
repositories,
313+
segmentId,
314+
}: {
315+
insightsProjectId?: string
316+
repositories: string[]
317+
segmentId: string
318+
},
319+
) {
320+
if (repositories.length === 0) {
321+
return null
322+
}
323+
324+
const data = repositories.map((repo) => ({
325+
insightsProjectId,
326+
repository: repo,
327+
segmentId,
328+
}))
329+
330+
return qx.result(
331+
prepareBulkInsert(
332+
'segmentRepositories',
333+
['insightsProjectId', 'repository', 'segmentId'],
334+
data,
335+
'("repository", "segmentId") DO NOTHING',
336+
),
337+
)
338+
}
339+
340+
export async function deleteMissingSegmentRepositories(
341+
qx: QueryExecutor,
342+
{
343+
repositories,
344+
segmentId,
345+
}: {
346+
repositories: string[]
347+
segmentId: string
348+
},
349+
) {
350+
return qx.result(
351+
`
352+
DELETE FROM "segmentRepositories"
353+
WHERE "segmentId" = '${segmentId}'
354+
AND ${repositories.length > 0 ? `"repository" != ALL(ARRAY[${repositories.map((repo) => `'${repo}'`).join(', ')}])` : 'TRUE'};
355+
`,
356+
{ segmentId, repositories },
357+
)
358+
}

0 commit comments

Comments
 (0)