-
Notifications
You must be signed in to change notification settings - Fork 731
Expand file tree
/
Copy pathwriteContacts.ts
More file actions
96 lines (91 loc) · 3.58 KB
/
Copy pathwriteContacts.ts
File metadata and controls
96 lines (91 loc) · 3.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import { QueryExecutor } from '@crowd/data-access-layer/src/queryExecutor'
import { prepareBulkInsert } from '@crowd/data-access-layer/src/utils'
import { RepoPolicies, ScoredContact } from './types'
// Advances contacts_last_refreshed only, so a failed pass isn't reprocessed this sweep.
export async function markRepoAttempted(qx: QueryExecutor, repoId: string): Promise<void> {
await qx.result('UPDATE repos SET contacts_last_refreshed = NOW() WHERE id = $(repoId)', {
repoId,
})
}
// Assumes at most one writer per repoId at a time (see processBatch.ts/schedule.ts) — no locking.
//
// Soft-delete: mark every active row stale, then upsert this pass's contacts, reviving whatever
// was rediscovered. Readers of this table must filter on deleted_at IS NULL.
export async function writeContacts(
qx: QueryExecutor,
repoId: string,
contacts: ScoredContact[],
policies: Partial<RepoPolicies>,
): Promise<void> {
await qx.tx(async (tx) => {
await tx.result(
'UPDATE security_contacts SET deleted_at = NOW(), updated_at = NOW() WHERE repo_id = $(repoId) AND deleted_at IS NULL',
{ repoId },
)
if (contacts.length > 0) {
await tx.result(
prepareBulkInsert(
'security_contacts',
[
'repo_id',
'channel',
'value',
'role',
'name',
'score',
'confidence',
'provenance',
'reachable',
'reachability_reason',
],
contacts.map((c) => ({
repo_id: repoId,
channel: c.channel,
value: c.value,
role: c.role,
name: c.name ?? null,
score: c.score,
confidence: c.confidence,
provenance: JSON.stringify(c.provenance),
reachable: c.reachable,
reachability_reason: c.reachabilityReason,
})),
`(repo_id, channel, value) DO UPDATE SET
role = EXCLUDED.role,
name = EXCLUDED.name,
score = EXCLUDED.score,
confidence = EXCLUDED.confidence,
provenance = EXCLUDED.provenance,
reachable = EXCLUDED.reachable,
reachability_reason = EXCLUDED.reachability_reason,
last_refreshed = NOW(),
updated_at = NOW(),
deleted_at = NULL`,
),
)
}
await tx.result(
// vulnerability_reporting_url is overwritten only once PVR is authoritatively resolved.
`UPDATE repos SET
security_policy_url = COALESCE($(securityPolicyUrl), security_policy_url),
vulnerability_reporting_url = CASE WHEN $(pvrResolved)
THEN $(vulnerabilityReportingUrl)
ELSE COALESCE($(vulnerabilityReportingUrl), vulnerability_reporting_url)
END,
bug_bounty_url = COALESCE($(bugBountyUrl), bug_bounty_url),
security_txt_url = COALESCE($(securityTxtUrl), security_txt_url),
pvr_enabled = COALESCE($(pvrEnabled), pvr_enabled),
contacts_last_refreshed = NOW()
WHERE id = $(repoId)`,
{
repoId,
securityPolicyUrl: policies.securityPolicyUrl ?? null,
vulnerabilityReportingUrl: policies.vulnerabilityReportingUrl ?? null,
pvrResolved: policies.pvrEnabled !== undefined,
bugBountyUrl: policies.bugBountyUrl ?? null,
securityTxtUrl: policies.securityTxtUrl ?? null,
pvrEnabled: policies.pvrEnabled ?? null,
},
)
})
}