Rename vendors to third parties

Renames the user-facing 'vendor' concept to 'third party' across the
entire codebase. The shared common_third_parties reference table is
unchanged.

Migration. Renames the vendor_category enum, the vendors and
vendor_<entity> tables (contacts, services, compliance_reports,
business_associate_agreements, data_privacy_agreements,
risk_assessments) and their vendor_id columns, the asset_vendors /
data_vendors / processing_activity_vendors junction tables,
generated_documents.vendors_document_id, the webhook_event_type
'vendor:<verb>' values, and the snapshots_type 'VENDORS' value.

Backend. Renames coredata models and SQL queries, probo services,
GraphQL / MCP API surface, console / trust / webhook resolvers and
types, the CLI (prb vendor* -> prb third-party*; pkg/cmd/vendormgmt
-> pkg/cmd/thirdpartymgmt), the document generator, vetting agent
prompts, and the common-third-parties-import command.

Frontend, packages, n8n, e2e. Renames apps/console pages, components,
hooks, routes, dialogs, and tabs; the shared @probo/vendors package
(now @probo/third-parties); the @probo/ui Vendors atoms (now
ThirdParties, VendorLogo -> ThirdPartyLogo); the n8n community node
actions/vendor folder (now actions/thirdParty); and the e2e Go test
suite (console and MCP). Filesystem and URL paths use kebab-case
(third-parties), GraphQL fields and TypeScript identifiers use
camelCase (thirdParty / thirdParties), Go types use PascalCase
(ThirdParty), and human-facing text uses 'third party' with a space.

Co-authored-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-05-13 16:15:33 +02:00
parent 9eed0d71c8
commit eecbe4c46c
281 changed files with 8491 additions and 8425 deletions

View File

@@ -13,7 +13,7 @@
// PERFORMANCE OF THIS SOFTWARE.
// Command common-third-parties-import seeds the common_third_parties table from
// packages/vendors/data.json. It is idempotent: re-running upserts on conflict
// packages/thirdParties/data.json. It is idempotent: re-running upserts on conflict
// (lower(name)) so existing rows keep their id and created_at.
//
// When -fetch-logos is set, the tool inspects each third party's website to
@@ -419,15 +419,15 @@ func loadThirdParties(path string) ([]thirdPartyData, error) {
return thirdParties, nil
}
func parseCategory(tp thirdPartyData) coredata.VendorCategory {
func parseCategory(tp thirdPartyData) coredata.ThirdPartyCategory {
if tp.Category == nil || *tp.Category == "" {
return coredata.VendorCategoryOther
return coredata.ThirdPartyCategoryOther
}
var c coredata.VendorCategory
var c coredata.ThirdPartyCategory
if err := c.Scan(*tp.Category); err != nil {
fmt.Fprintf(os.Stderr, "warning: third party %q has unknown category %q, falling back to OTHER\n", tp.Name, *tp.Category)
return coredata.VendorCategoryOther
return coredata.ThirdPartyCategoryOther
}
return c

View File

@@ -339,41 +339,41 @@ ORDER BY a.name ASC;
return "", err
}
vendorRows, err := tx.Query(
thirdPartyRows, err := tx.Query(
ctx,
`
SELECT
av.asset_id,
v.name
FROM asset_vendors av
JOIN vendors v ON v.id = av.vendor_id
FROM asset_third_parties av
JOIN third_parties v ON v.id = av.third_party_id
WHERE av.snapshot_id = @snapshot_id
ORDER BY v.name ASC;
`,
pgx.NamedArgs{"snapshot_id": snapshotID},
)
if err != nil {
return "", fmt.Errorf("cannot load snapshot asset vendors: %w", err)
return "", fmt.Errorf("cannot load snapshot asset thirdParties: %w", err)
}
defer vendorRows.Close()
defer thirdPartyRows.Close()
vendorsByAsset := make(map[string][]string)
for vendorRows.Next() {
var assetID, vendorName string
if err := vendorRows.Scan(&assetID, &vendorName); err != nil {
return "", fmt.Errorf("cannot scan vendor: %w", err)
thirdPartiesByAsset := make(map[string][]string)
for thirdPartyRows.Next() {
var assetID, thirdPartyName string
if err := thirdPartyRows.Scan(&assetID, &thirdPartyName); err != nil {
return "", fmt.Errorf("cannot scan thirdParty: %w", err)
}
vendorsByAsset[assetID] = append(vendorsByAsset[assetID], vendorName)
thirdPartiesByAsset[assetID] = append(thirdPartiesByAsset[assetID], thirdPartyName)
}
if err := vendorRows.Err(); err != nil {
if err := thirdPartyRows.Err(); err != nil {
return "", err
}
assetRows := make([]docgen.AssetListRow, len(assets))
for i, a := range assets {
vendors := "-"
if v, ok := vendorsByAsset[a.id]; ok && len(v) > 0 {
vendors = strings.Join(v, ", ")
thirdParties := "-"
if v, ok := thirdPartiesByAsset[a.id]; ok && len(v) > 0 {
thirdParties = strings.Join(v, ", ")
}
assetRows[i] = docgen.AssetListRow{
@@ -382,7 +382,7 @@ ORDER BY v.name ASC;
Amount: a.amount,
DataTypesStored: a.dataTypesStored,
Owner: a.ownerName,
Vendors: vendors,
ThirdParties: thirdParties,
}
}

View File

@@ -335,49 +335,49 @@ ORDER BY d.name ASC;
return "", err
}
// Load vendors for each datum in this snapshot.
vendorRows, err := tx.Query(
// Load thirdParties for each datum in this snapshot.
thirdPartyRows, err := tx.Query(
ctx,
`
SELECT
dv.datum_id,
v.name
FROM data_vendors dv
JOIN vendors v ON v.id = dv.vendor_id
FROM data_third_parties dv
JOIN third_parties v ON v.id = dv.third_party_id
WHERE dv.snapshot_id = @snapshot_id
ORDER BY v.name ASC;
`,
pgx.NamedArgs{"snapshot_id": snapshotID},
)
if err != nil {
return "", fmt.Errorf("cannot load snapshot data vendors: %w", err)
return "", fmt.Errorf("cannot load snapshot data thirdParties: %w", err)
}
defer vendorRows.Close()
defer thirdPartyRows.Close()
vendorsByDatum := make(map[string][]string)
for vendorRows.Next() {
var datumID, vendorName string
if err := vendorRows.Scan(&datumID, &vendorName); err != nil {
return "", fmt.Errorf("cannot scan vendor: %w", err)
thirdPartiesByDatum := make(map[string][]string)
for thirdPartyRows.Next() {
var datumID, thirdPartyName string
if err := thirdPartyRows.Scan(&datumID, &thirdPartyName); err != nil {
return "", fmt.Errorf("cannot scan thirdParty: %w", err)
}
vendorsByDatum[datumID] = append(vendorsByDatum[datumID], vendorName)
thirdPartiesByDatum[datumID] = append(thirdPartiesByDatum[datumID], thirdPartyName)
}
if err := vendorRows.Err(); err != nil {
if err := thirdPartyRows.Err(); err != nil {
return "", err
}
dataRows := make([]docgen.DataListRow, len(data))
for i, d := range data {
vendors := "-"
if v, ok := vendorsByDatum[d.id]; ok && len(v) > 0 {
vendors = strings.Join(v, ", ")
thirdParties := "-"
if v, ok := thirdPartiesByDatum[d.id]; ok && len(v) > 0 {
thirdParties = strings.Join(v, ", ")
}
dataRows[i] = docgen.DataListRow{
Name: d.name,
Classification: formatClassificationString(d.classification),
Owner: d.ownerName,
Vendors: vendors,
ThirdParties: thirdParties,
}
}

View File

@@ -403,7 +403,7 @@ ORDER BY pa.name ASC;
return "", 0, nil
}
vendorMap, err := loadVendorsForSnapshot(ctx, tx, snapshotID)
thirdPartyMap, err := loadThirdPartiesForSnapshot(ctx, tx, snapshotID)
if err != nil {
return "", 0, err
}
@@ -415,9 +415,9 @@ ORDER BY pa.name ASC;
dpo = p.dpoName
}
vendors := "None"
if v, ok := vendorMap[p.id]; ok && len(v) > 0 {
vendors = strings.Join(v, ", ")
thirdParties := "None"
if v, ok := thirdPartyMap[p.id]; ok && len(v) > 0 {
thirdParties = strings.Join(v, ", ")
}
listRows[i] = docgen.ProcessingActivityListRow{
@@ -440,7 +440,7 @@ ORDER BY pa.name ASC;
LastReviewDate: formatDateOrNotSpecified(p.lastReviewDate),
NextReviewDate: formatDateOrNotSpecified(p.nextReviewDate),
DataProtectionOfficer: dpo,
Vendors: vendors,
ThirdParties: thirdParties,
}
}
@@ -457,20 +457,20 @@ ORDER BY pa.name ASC;
return content, len(listRows), nil
}
func loadVendorsForSnapshot(ctx context.Context, tx pg.Tx, snapshotID string) (map[gid.GID][]string, error) {
func loadThirdPartiesForSnapshot(ctx context.Context, tx pg.Tx, snapshotID string) (map[gid.GID][]string, error) {
rows, err := tx.Query(
ctx,
`
SELECT pav.processing_activity_id, v.name
FROM processing_activity_vendors pav
INNER JOIN vendors v ON v.id = pav.vendor_id
FROM processing_activity_third_parties pav
INNER JOIN third_parties v ON v.id = pav.third_party_id
WHERE pav.snapshot_id = @snapshot_id
ORDER BY pav.processing_activity_id, v.name;
`,
pgx.NamedArgs{"snapshot_id": snapshotID},
)
if err != nil {
return nil, fmt.Errorf("cannot load snapshot vendors: %w", err)
return nil, fmt.Errorf("cannot load snapshot thirdParties: %w", err)
}
defer rows.Close()
@@ -479,7 +479,7 @@ ORDER BY pav.processing_activity_id, v.name;
var paID gid.GID
var name string
if err := rows.Scan(&paID, &name); err != nil {
return nil, fmt.Errorf("cannot scan vendor row: %w", err)
return nil, fmt.Errorf("cannot scan thirdParty row: %w", err)
}
result[paID] = append(result[paID], name)
}

View File

@@ -12,9 +12,9 @@
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
// Command migrate-vendor-snapshots-to-documents creates documents and document
// versions from existing vendor snapshots. For each organization that has vendor
// snapshots, it generates a vendor list document using the same ProseMirror
// Command migrate-thirdParty-snapshots-to-documents creates documents and document
// versions from existing thirdParty snapshots. For each organization that has thirdParty
// snapshots, it generates a thirdParty list document using the same ProseMirror
// builder as the publish flow.
package main
@@ -72,22 +72,22 @@ func run() error {
return migrate(ctx, pgClient, dryRun)
}
type orgWithVendorSnapshots struct {
type orgWithThirdPartySnapshots struct {
organizationID gid.GID
tenantID gid.TenantID
organizationName string
}
type vendorSnapshot struct {
type thirdPartySnapshot struct {
snapshotID string
publishedAt time.Time
}
func migrate(ctx context.Context, pgClient *pg.Client, dryRun bool) error {
var orgs []orgWithVendorSnapshots
var orgs []orgWithThirdPartySnapshots
err := pgClient.WithConn(ctx, func(ctx context.Context, conn pg.Querier) error {
var err error
orgs, err = loadOrgsWithVendorSnapshots(ctx, conn)
orgs, err = loadOrgsWithThirdPartySnapshots(ctx, conn)
return err
})
if err != nil {
@@ -95,7 +95,7 @@ func migrate(ctx context.Context, pgClient *pg.Client, dryRun bool) error {
}
if len(orgs) == 0 {
fmt.Println("no organizations with vendor snapshots to migrate")
fmt.Println("no organizations with thirdParty snapshots to migrate")
return nil
}
@@ -107,14 +107,14 @@ func migrate(ctx context.Context, pgClient *pg.Client, dryRun bool) error {
if dryRun {
var count int
err := pgClient.WithConn(ctx, func(ctx context.Context, conn pg.Querier) error {
snapshots, err := loadVendorSnapshots(ctx, conn, org.organizationID)
snapshots, err := loadThirdPartySnapshots(ctx, conn, org.organizationID)
count = len(snapshots)
return err
})
if err != nil {
return err
}
fmt.Printf("would migrate org %s (%s) — %d vendor snapshot(s)\n",
fmt.Printf("would migrate org %s (%s) — %d thirdParty snapshot(s)\n",
org.organizationID, org.organizationName, count)
continue
}
@@ -143,8 +143,8 @@ func migrate(ctx context.Context, pgClient *pg.Client, dryRun bool) error {
return nil
}
func migrateOrg(ctx context.Context, tx pg.Tx, org orgWithVendorSnapshots) error {
snapshots, err := loadVendorSnapshots(ctx, tx, org.organizationID)
func migrateOrg(ctx context.Context, tx pg.Tx, org orgWithThirdPartySnapshots) error {
snapshots, err := loadThirdPartySnapshots(ctx, tx, org.organizationID)
if err != nil {
return err
}
@@ -186,15 +186,15 @@ INSERT INTO documents (
_, err = tx.Exec(
ctx,
`INSERT INTO generated_documents (organization_id, tenant_id, vendors_document_id, created_at, updated_at)
VALUES (@organization_id, @tenant_id, @vendors_document_id, @created_at, @updated_at)
ON CONFLICT (organization_id) DO UPDATE SET vendors_document_id = @vendors_document_id, updated_at = @updated_at`,
`INSERT INTO generated_documents (organization_id, tenant_id, third_parties_document_id, created_at, updated_at)
VALUES (@organization_id, @tenant_id, @third_parties_document_id, @created_at, @updated_at)
ON CONFLICT (organization_id) DO UPDATE SET third_parties_document_id = @third_parties_document_id, updated_at = @updated_at`,
pgx.NamedArgs{
"organization_id": org.organizationID,
"tenant_id": org.tenantID,
"vendors_document_id": documentID,
"created_at": now,
"updated_at": now,
"organization_id": org.organizationID,
"tenant_id": org.tenantID,
"third_parties_document_id": documentID,
"created_at": now,
"updated_at": now,
},
)
if err != nil {
@@ -234,7 +234,7 @@ INSERT INTO document_versions (
"tenant_id": org.tenantID,
"organization_id": org.organizationID,
"document_id": documentID,
"title": "Vendors",
"title": "ThirdParties",
"major": major + 1,
"content": content,
"published_at": snap.publishedAt,
@@ -251,7 +251,7 @@ INSERT INTO document_versions (
return nil
}
func loadOrgsWithVendorSnapshots(ctx context.Context, conn pg.Querier) ([]orgWithVendorSnapshots, error) {
func loadOrgsWithThirdPartySnapshots(ctx context.Context, conn pg.Querier) ([]orgWithThirdPartySnapshots, error) {
rows, err := conn.Query(
ctx,
`
@@ -263,7 +263,7 @@ SELECT DISTINCT
FROM organizations o
WHERE NOT EXISTS (
SELECT 1 FROM generated_documents gd
WHERE gd.organization_id = o.id AND gd.vendors_document_id IS NOT NULL
WHERE gd.organization_id = o.id AND gd.third_parties_document_id IS NOT NULL
)
AND EXISTS (
SELECT 1 FROM snapshots s
@@ -273,13 +273,13 @@ ORDER BY o.created_at;
`,
)
if err != nil {
return nil, fmt.Errorf("cannot query organizations with vendor snapshots: %w", err)
return nil, fmt.Errorf("cannot query organizations with thirdParty snapshots: %w", err)
}
defer rows.Close()
var result []orgWithVendorSnapshots
var result []orgWithThirdPartySnapshots
for rows.Next() {
var o orgWithVendorSnapshots
var o orgWithThirdPartySnapshots
var createdAt time.Time
if err := rows.Scan(&o.organizationID, &o.tenantID, &o.organizationName, &createdAt); err != nil {
return nil, fmt.Errorf("cannot scan organization: %w", err)
@@ -290,7 +290,7 @@ ORDER BY o.created_at;
return result, rows.Err()
}
func loadVendorSnapshots(ctx context.Context, conn pg.Querier, organizationID gid.GID) ([]vendorSnapshot, error) {
func loadThirdPartySnapshots(ctx context.Context, conn pg.Querier, organizationID gid.GID) ([]thirdPartySnapshot, error) {
rows, err := conn.Query(
ctx,
`
@@ -305,13 +305,13 @@ ORDER BY s.created_at ASC;
pgx.NamedArgs{"organization_id": organizationID},
)
if err != nil {
return nil, fmt.Errorf("cannot query vendor snapshots for org %s: %w", organizationID, err)
return nil, fmt.Errorf("cannot query thirdParty snapshots for org %s: %w", organizationID, err)
}
defer rows.Close()
var result []vendorSnapshot
var result []thirdPartySnapshot
for rows.Next() {
var s vendorSnapshot
var s thirdPartySnapshot
if err := rows.Scan(&s.snapshotID, &s.publishedAt); err != nil {
return nil, fmt.Errorf("cannot scan snapshot: %w", err)
}
@@ -321,7 +321,7 @@ ORDER BY s.created_at ASC;
return result, rows.Err()
}
type vendorInfo struct {
type thirdPartyInfo struct {
id string
name string
category string
@@ -352,7 +352,7 @@ func buildSnapshotContent(
orgName string,
publishedAt time.Time,
) (string, error) {
vendorRows, err := tx.Query(
thirdPartyRows, err := tx.Query(
ctx,
`
SELECT
@@ -376,7 +376,7 @@ SELECT
v.countries,
COALESCE(bo.full_name, 'Not assigned'),
COALESCE(so.full_name, 'Not assigned')
FROM vendors v
FROM third_parties v
LEFT JOIN iam_membership_profiles bo ON bo.id = v.business_owner_profile_id
LEFT JOIN iam_membership_profiles so ON so.id = v.security_owner_profile_id
WHERE v.snapshot_id = @snapshot_id
@@ -385,14 +385,14 @@ ORDER BY v.name ASC;
pgx.NamedArgs{"snapshot_id": snapshotID},
)
if err != nil {
return "", fmt.Errorf("cannot load snapshot vendors: %w", err)
return "", fmt.Errorf("cannot load snapshot thirdParties: %w", err)
}
defer vendorRows.Close()
defer thirdPartyRows.Close()
var vendors []vendorInfo
for vendorRows.Next() {
var v vendorInfo
if err := vendorRows.Scan(
var thirdParties []thirdPartyInfo
for thirdPartyRows.Next() {
var v thirdPartyInfo
if err := thirdPartyRows.Scan(
&v.id, &v.name, &v.category,
&v.legalName, &v.description, &v.headquarterAddress,
&v.websiteURL, &v.privacyPolicyURL, &v.serviceLevelAgreementURL,
@@ -402,52 +402,52 @@ ORDER BY v.name ASC;
&v.certifications, &v.countries,
&v.businessOwnerName, &v.securityOwnerName,
); err != nil {
return "", fmt.Errorf("cannot scan vendor: %w", err)
return "", fmt.Errorf("cannot scan thirdParty: %w", err)
}
vendors = append(vendors, v)
thirdParties = append(thirdParties, v)
}
if err := vendorRows.Err(); err != nil {
if err := thirdPartyRows.Err(); err != nil {
return "", err
}
vendorIDs := make([]string, len(vendors))
for i, v := range vendors {
vendorIDs[i] = v.id
thirdPartyIDs := make([]string, len(thirdParties))
for i, v := range thirdParties {
thirdPartyIDs[i] = v.id
}
servicesByVendor, err := loadSnapshotServices(ctx, tx, snapshotID, vendorIDs)
servicesByThirdParty, err := loadSnapshotServices(ctx, tx, snapshotID, thirdPartyIDs)
if err != nil {
return "", err
}
contactsByVendor, err := loadSnapshotContacts(ctx, tx, snapshotID, vendorIDs)
contactsByThirdParty, err := loadSnapshotContacts(ctx, tx, snapshotID, thirdPartyIDs)
if err != nil {
return "", err
}
assessmentsByVendor, err := loadSnapshotRiskAssessments(ctx, tx, snapshotID, vendorIDs)
assessmentsByThirdParty, err := loadSnapshotRiskAssessments(ctx, tx, snapshotID, thirdPartyIDs)
if err != nil {
return "", err
}
reportsByVendor, err := loadSnapshotComplianceReports(ctx, tx, snapshotID, vendorIDs)
reportsByThirdParty, err := loadSnapshotComplianceReports(ctx, tx, snapshotID, thirdPartyIDs)
if err != nil {
return "", err
}
baaByVendor, err := loadSnapshotBAAs(ctx, tx, snapshotID, vendorIDs)
baaByThirdParty, err := loadSnapshotBAAs(ctx, tx, snapshotID, thirdPartyIDs)
if err != nil {
return "", err
}
dpaByVendor, err := loadSnapshotDPAs(ctx, tx, snapshotID, vendorIDs)
dpaByThirdParty, err := loadSnapshotDPAs(ctx, tx, snapshotID, thirdPartyIDs)
if err != nil {
return "", err
}
rows := make([]docgen.VendorListRow, 0, len(vendors))
for _, v := range vendors {
row := docgen.VendorListRow{
rows := make([]docgen.ThirdPartyListRow, 0, len(thirdParties))
for _, v := range thirdParties {
row := docgen.ThirdPartyListRow{
Name: v.name,
LegalName: deref(v.legalName),
Description: deref(v.description),
@@ -467,99 +467,99 @@ ORDER BY v.name ASC;
Countries: joinOrDefault(v.countries),
BusinessOwner: v.businessOwnerName,
SecurityOwner: v.securityOwnerName,
Services: servicesByVendor[v.id],
Contacts: contactsByVendor[v.id],
RiskAssessments: assessmentsByVendor[v.id],
ComplianceReports: reportsByVendor[v.id],
BusinessAssociateAgreement: baaByVendor[v.id],
DataPrivacyAgreement: dpaByVendor[v.id],
Services: servicesByThirdParty[v.id],
Contacts: contactsByThirdParty[v.id],
RiskAssessments: assessmentsByThirdParty[v.id],
ComplianceReports: reportsByThirdParty[v.id],
BusinessAssociateAgreement: baaByThirdParty[v.id],
DataPrivacyAgreement: dpaByThirdParty[v.id],
}
rows = append(rows, row)
}
docData := docgen.VendorListData{
Title: "Vendors",
OrganizationName: orgName,
CreatedAt: publishedAt,
TotalVendors: len(rows),
Rows: rows,
docData := docgen.ThirdPartyListData{
Title: "ThirdParties",
OrganizationName: orgName,
CreatedAt: publishedAt,
TotalThirdParties: len(rows),
Rows: rows,
}
return probo.BuildVendorListDocument(docData)
return probo.BuildThirdPartyListDocument(docData)
}
func loadSnapshotServices(ctx context.Context, tx pg.Tx, snapshotID string, vendorIDs []string) (map[string][]docgen.VendorListService, error) {
func loadSnapshotServices(ctx context.Context, tx pg.Tx, snapshotID string, thirdPartyIDs []string) (map[string][]docgen.ThirdPartyListService, error) {
rows, err := tx.Query(ctx,
`SELECT vs.vendor_id, vs.name, COALESCE(vs.description, 'Not specified')
FROM vendor_services vs
WHERE vs.snapshot_id = @snapshot_id AND vs.vendor_id = ANY(@vendor_ids)
ORDER BY vs.vendor_id, vs.name ASC`,
pgx.NamedArgs{"snapshot_id": snapshotID, "vendor_ids": vendorIDs})
`SELECT vs.third_party_id, vs.name, COALESCE(vs.description, 'Not specified')
FROM third_party_services vs
WHERE vs.snapshot_id = @snapshot_id AND vs.third_party_id = ANY(@third_party_ids)
ORDER BY vs.third_party_id, vs.name ASC`,
pgx.NamedArgs{"snapshot_id": snapshotID, "third_party_ids": thirdPartyIDs})
if err != nil {
return nil, fmt.Errorf("cannot load snapshot services: %w", err)
}
defer rows.Close()
result := make(map[string][]docgen.VendorListService)
result := make(map[string][]docgen.ThirdPartyListService)
for rows.Next() {
var vendorID, name, desc string
if err := rows.Scan(&vendorID, &name, &desc); err != nil {
var thirdPartyID, name, desc string
if err := rows.Scan(&thirdPartyID, &name, &desc); err != nil {
return nil, fmt.Errorf("cannot scan service: %w", err)
}
result[vendorID] = append(result[vendorID], docgen.VendorListService{Name: name, Description: desc})
result[thirdPartyID] = append(result[thirdPartyID], docgen.ThirdPartyListService{Name: name, Description: desc})
}
return result, rows.Err()
}
func loadSnapshotContacts(ctx context.Context, tx pg.Tx, snapshotID string, vendorIDs []string) (map[string][]docgen.VendorListContact, error) {
func loadSnapshotContacts(ctx context.Context, tx pg.Tx, snapshotID string, thirdPartyIDs []string) (map[string][]docgen.ThirdPartyListContact, error) {
rows, err := tx.Query(ctx,
`SELECT vc.vendor_id,
`SELECT vc.third_party_id,
COALESCE(vc.full_name, 'Not specified'),
COALESCE(vc.email, 'Not specified'),
COALESCE(vc.phone, 'Not specified'),
COALESCE(vc.role, 'Not specified')
FROM vendor_contacts vc
WHERE vc.snapshot_id = @snapshot_id AND vc.vendor_id = ANY(@vendor_ids)
ORDER BY vc.vendor_id, vc.full_name ASC`,
pgx.NamedArgs{"snapshot_id": snapshotID, "vendor_ids": vendorIDs})
FROM third_party_contacts vc
WHERE vc.snapshot_id = @snapshot_id AND vc.third_party_id = ANY(@third_party_ids)
ORDER BY vc.third_party_id, vc.full_name ASC`,
pgx.NamedArgs{"snapshot_id": snapshotID, "third_party_ids": thirdPartyIDs})
if err != nil {
return nil, fmt.Errorf("cannot load snapshot contacts: %w", err)
}
defer rows.Close()
result := make(map[string][]docgen.VendorListContact)
result := make(map[string][]docgen.ThirdPartyListContact)
for rows.Next() {
var vendorID, name, email, phone, role string
if err := rows.Scan(&vendorID, &name, &email, &phone, &role); err != nil {
var thirdPartyID, name, email, phone, role string
if err := rows.Scan(&thirdPartyID, &name, &email, &phone, &role); err != nil {
return nil, fmt.Errorf("cannot scan contact: %w", err)
}
result[vendorID] = append(result[vendorID], docgen.VendorListContact{
result[thirdPartyID] = append(result[thirdPartyID], docgen.ThirdPartyListContact{
FullName: name, Email: email, Phone: phone, Role: role,
})
}
return result, rows.Err()
}
func loadSnapshotRiskAssessments(ctx context.Context, tx pg.Tx, snapshotID string, vendorIDs []string) (map[string][]docgen.VendorListRiskAssessment, error) {
func loadSnapshotRiskAssessments(ctx context.Context, tx pg.Tx, snapshotID string, thirdPartyIDs []string) (map[string][]docgen.ThirdPartyListRiskAssessment, error) {
rows, err := tx.Query(ctx,
`SELECT vra.vendor_id, vra.created_at, vra.expires_at, vra.data_sensitivity, vra.business_impact, COALESCE(vra.notes, 'Not specified')
FROM vendor_risk_assessments vra
WHERE vra.snapshot_id = @snapshot_id AND vra.vendor_id = ANY(@vendor_ids)
ORDER BY vra.vendor_id, vra.created_at DESC`,
pgx.NamedArgs{"snapshot_id": snapshotID, "vendor_ids": vendorIDs})
`SELECT vra.third_party_id, vra.created_at, vra.expires_at, vra.data_sensitivity, vra.business_impact, COALESCE(vra.notes, 'Not specified')
FROM third_party_risk_assessments vra
WHERE vra.snapshot_id = @snapshot_id AND vra.third_party_id = ANY(@third_party_ids)
ORDER BY vra.third_party_id, vra.created_at DESC`,
pgx.NamedArgs{"snapshot_id": snapshotID, "third_party_ids": thirdPartyIDs})
if err != nil {
return nil, fmt.Errorf("cannot load snapshot risk assessments: %w", err)
}
defer rows.Close()
result := make(map[string][]docgen.VendorListRiskAssessment)
result := make(map[string][]docgen.ThirdPartyListRiskAssessment)
for rows.Next() {
var vendorID, sensitivity, impact, notes string
var thirdPartyID, sensitivity, impact, notes string
var assessedAt, expiresAt time.Time
if err := rows.Scan(&vendorID, &assessedAt, &expiresAt, &sensitivity, &impact, &notes); err != nil {
if err := rows.Scan(&thirdPartyID, &assessedAt, &expiresAt, &sensitivity, &impact, &notes); err != nil {
return nil, fmt.Errorf("cannot scan risk assessment: %w", err)
}
result[vendorID] = append(result[vendorID], docgen.VendorListRiskAssessment{
result[thirdPartyID] = append(result[thirdPartyID], docgen.ThirdPartyListRiskAssessment{
AssessedAt: assessedAt.Format("2006-01-02"),
ExpiresAt: expiresAt.Format("2006-01-02"),
DataSensitivity: sensitivity,
@@ -570,81 +570,81 @@ func loadSnapshotRiskAssessments(ctx context.Context, tx pg.Tx, snapshotID strin
return result, rows.Err()
}
func loadSnapshotComplianceReports(ctx context.Context, tx pg.Tx, snapshotID string, vendorIDs []string) (map[string][]docgen.VendorListComplianceReport, error) {
func loadSnapshotComplianceReports(ctx context.Context, tx pg.Tx, snapshotID string, thirdPartyIDs []string) (map[string][]docgen.ThirdPartyListComplianceReport, error) {
rows, err := tx.Query(ctx,
`SELECT vcr.vendor_id, vcr.report_name, vcr.report_date, vcr.valid_until
FROM vendor_compliance_reports vcr
WHERE vcr.snapshot_id = @snapshot_id AND vcr.vendor_id = ANY(@vendor_ids)
ORDER BY vcr.vendor_id, vcr.report_date DESC`,
pgx.NamedArgs{"snapshot_id": snapshotID, "vendor_ids": vendorIDs})
`SELECT vcr.third_party_id, vcr.report_name, vcr.report_date, vcr.valid_until
FROM third_party_compliance_reports vcr
WHERE vcr.snapshot_id = @snapshot_id AND vcr.third_party_id = ANY(@third_party_ids)
ORDER BY vcr.third_party_id, vcr.report_date DESC`,
pgx.NamedArgs{"snapshot_id": snapshotID, "third_party_ids": thirdPartyIDs})
if err != nil {
return nil, fmt.Errorf("cannot load snapshot compliance reports: %w", err)
}
defer rows.Close()
result := make(map[string][]docgen.VendorListComplianceReport)
result := make(map[string][]docgen.ThirdPartyListComplianceReport)
for rows.Next() {
var vendorID, name string
var thirdPartyID, name string
var reportDate time.Time
var validUntil *time.Time
if err := rows.Scan(&vendorID, &name, &reportDate, &validUntil); err != nil {
if err := rows.Scan(&thirdPartyID, &name, &reportDate, &validUntil); err != nil {
return nil, fmt.Errorf("cannot scan compliance report: %w", err)
}
vu := "Not specified"
if validUntil != nil {
vu = validUntil.Format("2006-01-02")
}
result[vendorID] = append(result[vendorID], docgen.VendorListComplianceReport{
result[thirdPartyID] = append(result[thirdPartyID], docgen.ThirdPartyListComplianceReport{
ReportName: name, ReportDate: reportDate.Format("2006-01-02"), ValidUntil: vu,
})
}
return result, rows.Err()
}
func loadSnapshotBAAs(ctx context.Context, tx pg.Tx, snapshotID string, vendorIDs []string) (map[string]*docgen.VendorListAgreement, error) {
func loadSnapshotBAAs(ctx context.Context, tx pg.Tx, snapshotID string, thirdPartyIDs []string) (map[string]*docgen.ThirdPartyListAgreement, error) {
rows, err := tx.Query(ctx,
`SELECT vbaa.vendor_id, vbaa.valid_from, vbaa.valid_until
FROM vendor_business_associate_agreements vbaa
WHERE vbaa.snapshot_id = @snapshot_id AND vbaa.vendor_id = ANY(@vendor_ids)`,
pgx.NamedArgs{"snapshot_id": snapshotID, "vendor_ids": vendorIDs})
`SELECT vbaa.third_party_id, vbaa.valid_from, vbaa.valid_until
FROM third_party_business_associate_agreements vbaa
WHERE vbaa.snapshot_id = @snapshot_id AND vbaa.third_party_id = ANY(@third_party_ids)`,
pgx.NamedArgs{"snapshot_id": snapshotID, "third_party_ids": thirdPartyIDs})
if err != nil {
return nil, fmt.Errorf("cannot load snapshot BAAs: %w", err)
}
defer rows.Close()
result := make(map[string]*docgen.VendorListAgreement)
result := make(map[string]*docgen.ThirdPartyListAgreement)
for rows.Next() {
var vendorID string
var thirdPartyID string
var validFrom, validUntil *time.Time
if err := rows.Scan(&vendorID, &validFrom, &validUntil); err != nil {
if err := rows.Scan(&thirdPartyID, &validFrom, &validUntil); err != nil {
return nil, fmt.Errorf("cannot scan BAA: %w", err)
}
result[vendorID] = &docgen.VendorListAgreement{
result[thirdPartyID] = &docgen.ThirdPartyListAgreement{
ValidFrom: fmtTime(validFrom), ValidUntil: fmtTime(validUntil),
}
}
return result, rows.Err()
}
func loadSnapshotDPAs(ctx context.Context, tx pg.Tx, snapshotID string, vendorIDs []string) (map[string]*docgen.VendorListAgreement, error) {
func loadSnapshotDPAs(ctx context.Context, tx pg.Tx, snapshotID string, thirdPartyIDs []string) (map[string]*docgen.ThirdPartyListAgreement, error) {
rows, err := tx.Query(ctx,
`SELECT vdpa.vendor_id, vdpa.valid_from, vdpa.valid_until
FROM vendor_data_privacy_agreements vdpa
WHERE vdpa.snapshot_id = @snapshot_id AND vdpa.vendor_id = ANY(@vendor_ids)`,
pgx.NamedArgs{"snapshot_id": snapshotID, "vendor_ids": vendorIDs})
`SELECT vdpa.third_party_id, vdpa.valid_from, vdpa.valid_until
FROM third_party_data_privacy_agreements vdpa
WHERE vdpa.snapshot_id = @snapshot_id AND vdpa.third_party_id = ANY(@third_party_ids)`,
pgx.NamedArgs{"snapshot_id": snapshotID, "third_party_ids": thirdPartyIDs})
if err != nil {
return nil, fmt.Errorf("cannot load snapshot DPAs: %w", err)
}
defer rows.Close()
result := make(map[string]*docgen.VendorListAgreement)
result := make(map[string]*docgen.ThirdPartyListAgreement)
for rows.Next() {
var vendorID string
var thirdPartyID string
var validFrom, validUntil *time.Time
if err := rows.Scan(&vendorID, &validFrom, &validUntil); err != nil {
if err := rows.Scan(&thirdPartyID, &validFrom, &validUntil); err != nil {
return nil, fmt.Errorf("cannot scan DPA: %w", err)
}
result[vendorID] = &docgen.VendorListAgreement{
result[thirdPartyID] = &docgen.ThirdPartyListAgreement{
ValidFrom: fmtTime(validFrom), ValidUntil: fmtTime(validUntil),
}
}