Add vendors to processing activities
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
8
pkg/coredata/migrations/20251030T120000Z.sql
Normal file
8
pkg/coredata/migrations/20251030T120000Z.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
CREATE TABLE processing_activity_vendors (
|
||||
processing_activity_id TEXT REFERENCES processing_activities(id) ON DELETE CASCADE,
|
||||
vendor_id TEXT REFERENCES vendors(id) ON DELETE CASCADE,
|
||||
tenant_id TEXT NOT NULL,
|
||||
snapshot_id TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
PRIMARY KEY (processing_activity_id, vendor_id)
|
||||
);
|
||||
@@ -395,6 +395,24 @@ WHERE
|
||||
}
|
||||
|
||||
func (pas ProcessingActivities) Snapshot(ctx context.Context, conn pg.Conn, scope Scoper, organizationID, snapshotID gid.GID) error {
|
||||
snapshotters := []ProcessingActivitySnapshotter{ProcessingActivities{}, Vendors{}, ProcessingActivityVendors{}}
|
||||
|
||||
for _, snapshotter := range snapshotters {
|
||||
if err := snapshotter.InsertProcessingActivitySnapshots(ctx, conn, scope, organizationID, snapshotID); err != nil {
|
||||
return fmt.Errorf("cannot create processing activity snapshots: (%T) %w", snapshotter, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pas ProcessingActivities) InsertProcessingActivitySnapshots(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
snapshotID gid.GID,
|
||||
) error {
|
||||
query := `
|
||||
INSERT INTO processing_activities (
|
||||
id,
|
||||
|
||||
177
pkg/coredata/processing_activity_vendor.go
Normal file
177
pkg/coredata/processing_activity_vendor.go
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
)
|
||||
|
||||
type (
|
||||
ProcessingActivityVendor struct {
|
||||
ProcessingActivityID gid.GID `db:"processing_activity_id"`
|
||||
VendorID gid.GID `db:"vendor_id"`
|
||||
TenantID gid.TenantID `db:"tenant_id"`
|
||||
SnapshotID *gid.GID `db:"snapshot_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
}
|
||||
|
||||
ProcessingActivityVendors []*ProcessingActivityVendor
|
||||
|
||||
ProcessingActivitySnapshotter interface {
|
||||
InsertProcessingActivitySnapshots(ctx context.Context, conn pg.Conn, scope Scoper, organizationID, snapshotID gid.GID) error
|
||||
}
|
||||
)
|
||||
|
||||
func (pav ProcessingActivityVendors) Merge(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
processingActivityID gid.GID,
|
||||
vendorIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
WITH vendor_ids AS (
|
||||
SELECT
|
||||
unnest(@vendor_ids::text[]) AS vendor_id,
|
||||
@tenant_id AS tenant_id,
|
||||
@processing_activity_id AS processing_activity_id,
|
||||
@created_at::timestamptz AS created_at
|
||||
)
|
||||
MERGE INTO processing_activity_vendors AS tgt
|
||||
USING vendor_ids AS src
|
||||
ON tgt.tenant_id = src.tenant_id
|
||||
AND tgt.processing_activity_id = src.processing_activity_id
|
||||
AND tgt.vendor_id = src.vendor_id
|
||||
WHEN NOT MATCHED
|
||||
THEN INSERT (tenant_id, processing_activity_id, vendor_id, created_at)
|
||||
VALUES (src.tenant_id, src.processing_activity_id, src.vendor_id, src.created_at)
|
||||
WHEN NOT MATCHED BY SOURCE
|
||||
AND tgt.tenant_id = @tenant_id AND tgt.processing_activity_id = @processing_activity_id
|
||||
THEN DELETE
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"processing_activity_id": processingActivityID,
|
||||
"created_at": time.Now(),
|
||||
"vendor_ids": vendorIDs,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot merge processing activity vendors: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pav ProcessingActivityVendors) Insert(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
processingActivityID gid.GID,
|
||||
vendorIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
WITH vendor_ids AS (
|
||||
SELECT unnest(@vendor_ids::text[]) AS vendor_id
|
||||
)
|
||||
INSERT INTO processing_activity_vendors (tenant_id, processing_activity_id, vendor_id, created_at)
|
||||
SELECT
|
||||
@tenant_id AS tenant_id,
|
||||
@processing_activity_id AS processing_activity_id,
|
||||
vendor_id,
|
||||
@created_at AS created_at
|
||||
FROM vendor_ids
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"processing_activity_id": processingActivityID,
|
||||
"created_at": time.Now(),
|
||||
"vendor_ids": vendorIDs,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert processing activity vendors: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pav ProcessingActivityVendors) InsertProcessingActivitySnapshots(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
snapshotID gid.GID,
|
||||
) error {
|
||||
query := `
|
||||
WITH
|
||||
source_processing_activities AS (
|
||||
SELECT id
|
||||
FROM processing_activities
|
||||
WHERE organization_id = @organization_id AND snapshot_id IS NULL
|
||||
),
|
||||
snapshot_processing_activities AS (
|
||||
SELECT id, source_id
|
||||
FROM processing_activities
|
||||
WHERE organization_id = @organization_id AND snapshot_id = @snapshot_id
|
||||
),
|
||||
snapshot_vendors AS (
|
||||
SELECT id, source_id
|
||||
FROM vendors
|
||||
WHERE organization_id = @organization_id AND snapshot_id = @snapshot_id
|
||||
),
|
||||
source_processing_activity_vendors AS (
|
||||
SELECT processing_activity_id, vendor_id, snapshot_id, created_at
|
||||
FROM processing_activity_vendors
|
||||
WHERE %s AND processing_activity_id = ANY(SELECT id FROM source_processing_activities) AND snapshot_id IS NULL
|
||||
)
|
||||
INSERT INTO processing_activity_vendors (tenant_id, processing_activity_id, vendor_id, snapshot_id, created_at)
|
||||
SELECT
|
||||
@tenant_id,
|
||||
spa.id,
|
||||
sv.id,
|
||||
@snapshot_id,
|
||||
pav.created_at
|
||||
FROM source_processing_activity_vendors pav
|
||||
JOIN snapshot_processing_activities spa ON spa.source_id = pav.processing_activity_id
|
||||
JOIN snapshot_vendors sv ON sv.source_id = pav.vendor_id
|
||||
`
|
||||
|
||||
query = fmt.Sprintf(query, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"snapshot_id": snapshotID,
|
||||
"organization_id": organizationID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, query, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert processing activity vendor snapshots: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -731,6 +731,104 @@ WHERE %s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Vendors) LoadByProcessingActivityID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
processingActivityID gid.GID,
|
||||
cursor *page.Cursor[VendorOrderField],
|
||||
) error {
|
||||
q := `
|
||||
WITH vend AS (
|
||||
SELECT
|
||||
v.id,
|
||||
v.tenant_id,
|
||||
v.organization_id,
|
||||
v.name,
|
||||
v.description,
|
||||
v.category,
|
||||
v.headquarter_address,
|
||||
v.legal_name,
|
||||
v.website_url,
|
||||
v.privacy_policy_url,
|
||||
v.service_level_agreement_url,
|
||||
v.data_processing_agreement_url,
|
||||
v.business_associate_agreement_url,
|
||||
v.subprocessors_list_url,
|
||||
v.certifications,
|
||||
v.countries,
|
||||
v.business_owner_id,
|
||||
v.security_owner_id,
|
||||
v.status_page_url,
|
||||
v.terms_of_service_url,
|
||||
v.security_page_url,
|
||||
v.trust_page_url,
|
||||
v.show_on_trust_center,
|
||||
v.snapshot_id,
|
||||
v.source_id,
|
||||
v.created_at,
|
||||
v.updated_at
|
||||
FROM
|
||||
vendors v
|
||||
INNER JOIN
|
||||
processing_activity_vendors pav ON v.id = pav.vendor_id
|
||||
WHERE
|
||||
pav.processing_activity_id = @processing_activity_id
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
category,
|
||||
headquarter_address,
|
||||
legal_name,
|
||||
website_url,
|
||||
privacy_policy_url,
|
||||
service_level_agreement_url,
|
||||
data_processing_agreement_url,
|
||||
business_associate_agreement_url,
|
||||
subprocessors_list_url,
|
||||
certifications,
|
||||
countries,
|
||||
business_owner_id,
|
||||
security_owner_id,
|
||||
status_page_url,
|
||||
terms_of_service_url,
|
||||
security_page_url,
|
||||
trust_page_url,
|
||||
show_on_trust_center,
|
||||
snapshot_id,
|
||||
source_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
vend
|
||||
WHERE %s
|
||||
AND %s
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"processing_activity_id": processingActivityID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, cursor.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query vendors: %w", err)
|
||||
}
|
||||
|
||||
vendors, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Vendor])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect vendors: %w", err)
|
||||
}
|
||||
|
||||
*v = vendors
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d Vendors) InsertDataSnapshots(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
@@ -935,6 +1033,108 @@ FROM source_vendors v
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vs Vendors) InsertProcessingActivitySnapshots(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
snapshotID gid.GID,
|
||||
) error {
|
||||
query := `
|
||||
WITH
|
||||
source_processing_activities AS (
|
||||
SELECT id
|
||||
FROM processing_activities
|
||||
WHERE organization_id = @organization_id AND snapshot_id IS NULL
|
||||
),
|
||||
source_processing_activity_vendors AS (
|
||||
SELECT processing_activity_id, vendor_id, snapshot_id, created_at
|
||||
FROM processing_activity_vendors
|
||||
WHERE processing_activity_id = ANY(SELECT id FROM source_processing_activities)
|
||||
),
|
||||
source_vendors AS (
|
||||
SELECT *
|
||||
FROM vendors
|
||||
WHERE %s AND id = ANY(SELECT vendor_id FROM source_processing_activity_vendors)
|
||||
)
|
||||
INSERT INTO vendors (
|
||||
tenant_id,
|
||||
id,
|
||||
snapshot_id,
|
||||
source_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
category,
|
||||
headquarter_address,
|
||||
legal_name,
|
||||
website_url,
|
||||
privacy_policy_url,
|
||||
service_level_agreement_url,
|
||||
data_processing_agreement_url,
|
||||
business_associate_agreement_url,
|
||||
subprocessors_list_url,
|
||||
certifications,
|
||||
countries,
|
||||
business_owner_id,
|
||||
security_owner_id,
|
||||
status_page_url,
|
||||
terms_of_service_url,
|
||||
security_page_url,
|
||||
trust_page_url,
|
||||
show_on_trust_center,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
@tenant_id,
|
||||
generate_gid(decode_base64_unpadded(@tenant_id), @vendor_entity_type),
|
||||
@snapshot_id,
|
||||
v.id,
|
||||
v.organization_id,
|
||||
v.name,
|
||||
v.description,
|
||||
v.category,
|
||||
v.headquarter_address,
|
||||
v.legal_name,
|
||||
v.website_url,
|
||||
v.privacy_policy_url,
|
||||
v.service_level_agreement_url,
|
||||
v.data_processing_agreement_url,
|
||||
v.business_associate_agreement_url,
|
||||
v.subprocessors_list_url,
|
||||
v.certifications,
|
||||
v.countries,
|
||||
v.business_owner_id,
|
||||
v.security_owner_id,
|
||||
v.status_page_url,
|
||||
v.terms_of_service_url,
|
||||
v.security_page_url,
|
||||
v.trust_page_url,
|
||||
v.show_on_trust_center,
|
||||
v.created_at,
|
||||
v.updated_at
|
||||
FROM source_vendors v
|
||||
`
|
||||
|
||||
query = fmt.Sprintf(query, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"snapshot_id": snapshotID,
|
||||
"organization_id": organizationID,
|
||||
"vendor_entity_type": VendorEntityType,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, query, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert vendor snapshots for processing activities: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v Vendors) Snapshot(ctx context.Context, conn pg.Conn, scope Scoper, organizationID, snapshotID gid.GID) error {
|
||||
for _, snapshotter := range []VendorSnapshotter{
|
||||
Vendors{},
|
||||
|
||||
Reference in New Issue
Block a user