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 }
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 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,54 @@ 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+
95+ await qx . tx ( async ( t ) => {
96+ const changed = await t . selectOne (
97+ `WITH old AS (
98+ SELECT latest_version AS v, latest_release_at AS t, repository_url AS r
99+ FROM packages WHERE purl = $(purl)
100+ ),
101+ upd AS (
102+ UPDATE packages p SET
103+ latest_version = $(version),
104+ latest_release_at = $(releaseAt),
105+ repository_url = COALESCE($(repoUrl), p.repository_url),
106+ last_synced_at = NOW()
107+ WHERE p.purl = $(purl)
108+ RETURNING latest_version AS v, latest_release_at AS t, repository_url AS r
109+ )
110+ SELECT
111+ (SELECT v FROM old) IS DISTINCT FROM (SELECT v FROM upd) AS v_changed,
112+ (SELECT t FROM old) IS DISTINCT FROM (SELECT t FROM upd) AS t_changed,
113+ (SELECT r FROM old) IS DISTINCT FROM (SELECT r FROM upd) AS r_changed` ,
114+ {
115+ version : result . version ,
116+ releaseAt : result . releaseAt ,
117+ repoUrl : repo ?. url ?? null ,
118+ purl : row . purl ,
119+ } ,
120+ )
121+ const changedFields = [
122+ changed ?. v_changed ? 'packages.latest_version' : null ,
123+ changed ?. t_changed ? 'packages.latest_release_at' : null ,
124+ changed ?. r_changed ? 'packages.repository_url' : null ,
125+ ] . filter ( Boolean ) as string [ ]
126+
127+ if ( repo ) {
128+ const { id : repoId , changedFields : repoChanged } = await getOrCreateRepoByUrl (
129+ t ,
130+ repo . url ,
131+ repo . host ,
132+ )
133+ changedFields . push ( ...repoChanged )
134+
135+ const linkChanged = await upsertPackageRepo ( t , row . id , repoId , 'declared' , 0.8 )
136+ changedFields . push ( ...linkChanged )
137+ }
138+
139+ await logAuditFieldChanges ( t , PROXY_SOURCE , row . purl , changedFields )
140+ } )
119141 }
120142
121143 for ( let i = 0 ; i < rows . length ; i += proxyConcurrency ) {
0 commit comments