11import { Context } from '@temporalio/activity'
22
3- import { logAuditFieldChanges } from '@crowd/data-access-layer/src/packages'
3+ import {
4+ getOrCreateRepoByUrl ,
5+ logAuditFieldChanges ,
6+ upsertPackageRepo ,
7+ } from '@crowd/data-access-layer/src/packages'
48import type { QueryExecutor } from '@crowd/data-access-layer/src/queryExecutor'
59import { getServiceChildLogger } from '@crowd/logging'
610
711import { getGoConfig } from '../config'
812import { getPackagesDb } from '../db'
13+ import { canonicalizeRepoUrl } from '../utils/canonicalizeRepoUrl'
914
1015import { fetchStatus } from './pkgGoDevClient'
1116import { fetchLatest } from './proxyClient'
@@ -21,7 +26,7 @@ export interface GoScanCursor {
2126 after : string
2227}
2328
24- type GoRow = { purl : string ; name : string }
29+ type GoRow = { id : string ; purl : string ; name : string ; declaredRepositoryUrl : string | null }
2530
2631// Two independent purl-keyset cursors — one for critical packages, one for everything else —
2732// each ordered/paginated purely by purl so WHERE and ORDER BY always match (no gaps, no
@@ -37,7 +42,7 @@ async function getGoBatch(
3742) : Promise < GoRow [ ] > {
3843 if ( batchSize <= 0 ) return [ ]
3944 return qx . select (
40- `SELECT purl, name FROM packages
45+ `SELECT id::text AS id, purl, name, declared_repository_url AS "declaredRepositoryUrl" FROM packages
4146 WHERE ecosystem = 'go' AND is_critical = $(isCritical) AND purl > $(after)
4247 ORDER BY purl ASC
4348 LIMIT $(limit)` ,
@@ -75,7 +80,7 @@ export async function enrichGoVersionsBatch(
7580
7681 const { fetchTimeoutMs, proxyConcurrency } = getGoConfig ( )
7782
78- const enrichOne = async ( row : { purl : string ; name : string } ) : Promise < void > => {
83+ const enrichOne = async ( row : GoRow ) : Promise < void > => {
7984 Context . current ( ) . heartbeat ( row . purl )
8085 const result = await fetchLatest ( row . name , fetchTimeoutMs )
8186 if ( isFetchError ( result ) ) {
@@ -85,37 +90,60 @@ export async function enrichGoVersionsBatch(
8590 )
8691 return
8792 }
88- const changed = await qx . selectOne (
89- `WITH old AS (
90- SELECT latest_version AS v, latest_release_at AS t, repository_url AS r
91- FROM packages WHERE purl = $(purl)
92- ),
93- upd AS (
94- UPDATE packages p SET
95- latest_version = $(version),
96- latest_release_at = $(releaseAt),
97- repository_url = COALESCE($(repoUrl), p.repository_url),
98- last_synced_at = NOW()
99- WHERE p.purl = $(purl)
100- RETURNING latest_version AS v, latest_release_at AS t, repository_url AS r
101- )
102- SELECT
103- (SELECT v FROM old) IS DISTINCT FROM (SELECT v FROM upd) AS v_changed,
104- (SELECT t FROM old) IS DISTINCT FROM (SELECT t FROM upd) AS t_changed,
105- (SELECT r FROM old) IS DISTINCT FROM (SELECT r FROM upd) AS r_changed` ,
106- {
107- version : result . version ,
108- releaseAt : result . releaseAt ,
109- repoUrl : result . repoUrl ,
110- purl : row . purl ,
111- } ,
112- )
113- const changedFields = [
114- changed ?. v_changed ? 'packages.latest_version' : null ,
115- changed ?. t_changed ? 'packages.latest_release_at' : null ,
116- changed ?. r_changed ? 'packages.repository_url' : null ,
117- ] . filter ( Boolean ) as string [ ]
118- await logAuditFieldChanges ( qx , PROXY_SOURCE , row . purl , changedFields )
93+ const repo = result . repoUrl ? canonicalizeRepoUrl ( result . repoUrl ) : null
94+ const declaredRepo = repo
95+ ? null
96+ : row . declaredRepositoryUrl
97+ ? canonicalizeRepoUrl ( row . declaredRepositoryUrl )
98+ : null
99+
100+ await qx . tx ( async ( t ) => {
101+ const changed = await t . selectOne (
102+ `WITH old AS (
103+ SELECT latest_version AS v, latest_release_at AS t, repository_url AS r
104+ FROM packages WHERE purl = $(purl)
105+ ),
106+ upd AS (
107+ UPDATE packages p SET
108+ latest_version = $(version),
109+ latest_release_at = $(releaseAt),
110+ repository_url = COALESCE($(repoUrl), p.repository_url),
111+ last_synced_at = NOW()
112+ WHERE p.purl = $(purl)
113+ RETURNING latest_version AS v, latest_release_at AS t, repository_url AS r
114+ )
115+ SELECT
116+ (SELECT v FROM old) IS DISTINCT FROM (SELECT v FROM upd) AS v_changed,
117+ (SELECT t FROM old) IS DISTINCT FROM (SELECT t FROM upd) AS t_changed,
118+ (SELECT r FROM old) IS DISTINCT FROM (SELECT r FROM upd) AS r_changed` ,
119+ {
120+ version : result . version ,
121+ releaseAt : result . releaseAt ,
122+ repoUrl : repo ?. url ?? declaredRepo ?. url ?? null ,
123+ purl : row . purl ,
124+ } ,
125+ )
126+ const changedFields = [
127+ changed ?. v_changed ? 'packages.latest_version' : null ,
128+ changed ?. t_changed ? 'packages.latest_release_at' : null ,
129+ changed ?. r_changed ? 'packages.repository_url' : null ,
130+ ] . filter ( Boolean ) as string [ ]
131+
132+ const repoToLink = repo ?? declaredRepo
133+ if ( repoToLink ) {
134+ const { id : repoId , changedFields : repoChanged } = await getOrCreateRepoByUrl (
135+ t ,
136+ repoToLink . url ,
137+ repoToLink . host ,
138+ )
139+ changedFields . push ( ...repoChanged )
140+
141+ const linkChanged = await upsertPackageRepo ( t , row . id , repoId , 'declared' , 0.8 )
142+ changedFields . push ( ...linkChanged )
143+ }
144+
145+ await logAuditFieldChanges ( t , PROXY_SOURCE , row . purl , changedFields )
146+ } )
119147 }
120148
121149 for ( let i = 0 ; i < rows . length ; i += proxyConcurrency ) {
0 commit comments