Add processing activity exports

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-12-22 17:36:50 +01:00
parent 2a49c1e627
commit c76764b69a
29 changed files with 4021 additions and 226 deletions

View File

@@ -35,6 +35,12 @@ func (e ErrDataProtectionImpactAssessmentNotFound) Error() string {
return fmt.Sprintf("data protection impact assessment not found: %q", e.Identifier)
}
type ErrNoDataProtectionImpactAssessmentsFound struct{}
func (e ErrNoDataProtectionImpactAssessmentsFound) Error() string {
return "no data protection impact assessments found"
}
type (
DataProtectionImpactAssessment struct {
ID gid.GID `db:"id"`
@@ -151,6 +157,56 @@ WHERE
return nil
}
func (dpias *DataProtectionImpactAssessments) LoadAllByOrganizationID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
organizationID gid.GID,
filter *DataProtectionImpactAssessmentFilter,
) error {
q := `
SELECT
id,
snapshot_id,
source_id,
organization_id,
processing_activity_id,
description,
necessity_and_proportionality,
potential_risk,
mitigations,
residual_risk,
created_at,
updated_at
FROM
processing_activity_data_protection_impact_assessments
WHERE
%s
AND organization_id = @organization_id
AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
args := pgx.StrictNamedArgs{"organization_id": organizationID}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query data protection impact assessments: %w", err)
}
results, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[DataProtectionImpactAssessment])
if err != nil {
return fmt.Errorf("cannot collect data protection impact assessments: %w", err)
}
*dpias = results
return nil
}
func (dpia *DataProtectionImpactAssessment) LoadByID(
ctx context.Context,
conn pg.Conn,
@@ -429,4 +485,3 @@ WHERE dpia.tenant_id = @tenant_id AND dpia.organization_id = @organization_id AN
return nil
}

View File

@@ -53,3 +53,9 @@ func (f *DataProtectionImpactAssessmentFilter) SQLFragment() string {
}
}
func (f *DataProtectionImpactAssessmentFilter) SnapshotID() *gid.GID {
if f.snapshotID == nil || *f.snapshotID == nil {
return nil
}
return *f.snapshotID
}

View File

@@ -26,6 +26,12 @@ import (
"go.probo.inc/probo/pkg/page"
)
type ErrNoProcessingActivitiesFound struct{}
func (e ErrNoProcessingActivitiesFound) Error() string {
return "no processing activities found"
}
type (
ProcessingActivity struct {
ID gid.GID `db:"id"`
@@ -231,6 +237,70 @@ WHERE
return nil
}
func (p *ProcessingActivities) LoadAllByOrganizationID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
organizationID gid.GID,
filter *ProcessingActivityFilter,
) error {
q := `
SELECT
id,
snapshot_id,
source_id,
organization_id,
name,
purpose,
data_subject_category,
personal_data_category,
special_or_criminal_data,
consent_evidence_link,
lawful_basis,
recipients,
location,
international_transfers,
transfer_safeguards,
retention_period,
security_measures,
data_protection_impact_assessment_needed,
transfer_impact_assessment_needed,
last_review_date,
next_review_date,
role,
data_protection_officer_id,
created_at,
updated_at
FROM
processing_activities
WHERE
%s
AND organization_id = @organization_id
AND %s
ORDER BY created_at DESC
`
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
args := pgx.StrictNamedArgs{"organization_id": organizationID}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query processing activities: %w", err)
}
processingActivities, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ProcessingActivity])
if err != nil {
return fmt.Errorf("cannot collect processing activities: %w", err)
}
*p = processingActivities
return nil
}
func (p *ProcessingActivity) Insert(
ctx context.Context,
conn pg.Conn,

View File

@@ -52,3 +52,10 @@ func (f *ProcessingActivityFilter) SQLFragment() string {
return "snapshot_id = @filter_snapshot_id"
}
}
func (f *ProcessingActivityFilter) SnapshotID() *gid.GID {
if f.snapshotID == nil || *f.snapshotID == nil {
return nil
}
return *f.snapshotID
}

View File

@@ -20,10 +20,10 @@ import (
"maps"
"time"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
"github.com/jackc/pgx/v5"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
)
type (
@@ -99,6 +99,7 @@ func (s *Snapshots) CountByOrganizationID(
conn pg.Conn,
scope Scoper,
organizationID gid.GID,
filter *SnapshotFilter,
) (int, error) {
q := `
SELECT
@@ -108,12 +109,14 @@ FROM
WHERE
%s
AND organization_id = @organization_id
AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment())
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
args := pgx.StrictNamedArgs{"organization_id": organizationID}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments())
row := conn.QueryRow(ctx, q, args)

View File

@@ -0,0 +1,65 @@
// 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 (
"time"
"github.com/jackc/pgx/v5"
)
type (
SnapshotFilter struct {
snapshotType *SnapshotsType
beforeDate *time.Time
}
)
func NewSnapshotFilter(snapshotType *SnapshotsType) *SnapshotFilter {
return &SnapshotFilter{
snapshotType: snapshotType,
}
}
func (f *SnapshotFilter) WithBeforeDate(beforeDate *time.Time) *SnapshotFilter {
f.beforeDate = beforeDate
return f
}
func (f *SnapshotFilter) SQLArguments() pgx.NamedArgs {
args := pgx.NamedArgs{
"filter_snapshot_type": f.snapshotType,
"filter_before_date": f.beforeDate,
}
return args
}
func (f *SnapshotFilter) SQLFragment() string {
return `
(
CASE
WHEN @filter_snapshot_type::snapshots_type IS NOT NULL THEN
type = @filter_snapshot_type::snapshots_type
ELSE TRUE
END
AND
CASE
WHEN @filter_before_date::timestamptz IS NOT NULL THEN
created_at <= @filter_before_date::timestamptz
ELSE TRUE
END
)`
}

View File

@@ -35,6 +35,12 @@ func (e ErrTransferImpactAssessmentNotFound) Error() string {
return fmt.Sprintf("transfer impact assessment not found: %q", e.Identifier)
}
type ErrNoTransferImpactAssessmentsFound struct{}
func (e ErrNoTransferImpactAssessmentsFound) Error() string {
return "no transfer impact assessments found"
}
type (
TransferImpactAssessment struct {
ID gid.GID `db:"id"`
@@ -151,6 +157,56 @@ WHERE
return nil
}
func (tias *TransferImpactAssessments) LoadAllByOrganizationID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
organizationID gid.GID,
filter *TransferImpactAssessmentFilter,
) error {
q := `
SELECT
id,
snapshot_id,
source_id,
organization_id,
processing_activity_id,
data_subjects,
legal_mechanism,
transfer,
local_law_risk,
supplementary_measures,
created_at,
updated_at
FROM
processing_activity_transfer_impact_assessments
WHERE
%s
AND organization_id = @organization_id
AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
args := pgx.StrictNamedArgs{"organization_id": organizationID}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query transfer impact assessments: %w", err)
}
results, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[TransferImpactAssessment])
if err != nil {
return fmt.Errorf("cannot collect transfer impact assessments: %w", err)
}
*tias = results
return nil
}
func (tia *TransferImpactAssessment) LoadByID(
ctx context.Context,
conn pg.Conn,
@@ -429,4 +485,3 @@ WHERE tia.tenant_id = @tenant_id AND tia.organization_id = @organization_id AND
return nil
}

View File

@@ -53,3 +53,9 @@ func (f *TransferImpactAssessmentFilter) SQLFragment() string {
}
}
func (f *TransferImpactAssessmentFilter) SnapshotID() *gid.GID {
if f.snapshotID == nil || *f.snapshotID == nil {
return nil
}
return *f.snapshotID
}

View File

@@ -21,10 +21,10 @@ import (
"maps"
"time"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
"github.com/jackc/pgx/v5"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
)
type (
@@ -850,6 +850,74 @@ WHERE %s
return nil
}
func (v *Vendors) LoadAllByProcessingActivities(
ctx context.Context,
conn pg.Conn,
scope Scoper,
organizationID gid.GID,
filter *ProcessingActivityFilter,
) (map[gid.GID][]string, error) {
q := `
WITH filtered_processing_activities AS (
SELECT
pa.id
FROM
processing_activities pa
WHERE
pa.tenant_id = @tenant_id
AND pa.organization_id = @organization_id
AND %s
),
filtered_vendors AS (
SELECT
v.id,
v.name
FROM
vendors v
WHERE
v.tenant_id = @tenant_id
)
SELECT
pav.processing_activity_id,
fv.name
FROM
processing_activity_vendors pav
INNER JOIN
filtered_vendors fv ON fv.id = pav.vendor_id
INNER JOIN
filtered_processing_activities fpa ON fpa.id = pav.processing_activity_id
WHERE
pav.tenant_id = @tenant_id
ORDER BY
pav.processing_activity_id, fv.name
`
q = fmt.Sprintf(q, filter.SQLFragment())
args := pgx.StrictNamedArgs{
"organization_id": organizationID,
}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return nil, fmt.Errorf("cannot query vendors: %w", err)
}
defer rows.Close()
vendorMap := make(map[gid.GID][]string)
for rows.Next() {
var processingActivityID gid.GID
var vendorName string
if err := rows.Scan(&processingActivityID, &vendorName); err != nil {
return nil, fmt.Errorf("cannot scan vendor: %w", err)
}
vendorMap[processingActivityID] = append(vendorMap[processingActivityID], vendorName)
}
return vendorMap, nil
}
func (d Vendors) InsertDataSnapshots(
ctx context.Context,
conn pg.Conn,