Initial commit: breakpilot-compliance - Compliance SDK Platform
Services: Admin-Compliance, Backend-Compliance, AI-Compliance-SDK, Consent-SDK, Developer-Portal, PCA-Platform, DSMS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
235
ai-compliance-sdk/internal/dsgvo/models.go
Normal file
235
ai-compliance-sdk/internal/dsgvo/models.go
Normal file
@@ -0,0 +1,235 @@
|
||||
package dsgvo
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// VVT - Verarbeitungsverzeichnis (Art. 30 DSGVO)
|
||||
// ============================================================================
|
||||
|
||||
// ProcessingActivity represents an entry in the Records of Processing Activities
|
||||
type ProcessingActivity struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
TenantID uuid.UUID `json:"tenant_id"`
|
||||
NamespaceID *uuid.UUID `json:"namespace_id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Purpose string `json:"purpose"`
|
||||
LegalBasis string `json:"legal_basis"` // consent, contract, legal_obligation, vital_interests, public_interest, legitimate_interests
|
||||
LegalBasisDetails string `json:"legal_basis_details,omitempty"`
|
||||
DataCategories []string `json:"data_categories"` // personal, sensitive, health, financial, etc.
|
||||
DataSubjectCategories []string `json:"data_subject_categories"` // customers, employees, suppliers, etc.
|
||||
Recipients []string `json:"recipients"` // Internal departments, external processors
|
||||
ThirdCountryTransfer bool `json:"third_country_transfer"`
|
||||
TransferSafeguards string `json:"transfer_safeguards,omitempty"` // SCCs, adequacy decision, BCRs
|
||||
RetentionPeriod string `json:"retention_period"`
|
||||
RetentionPolicyID *uuid.UUID `json:"retention_policy_id,omitempty"`
|
||||
TOMReference []uuid.UUID `json:"tom_reference,omitempty"` // Links to TOM entries
|
||||
DSFARequired bool `json:"dsfa_required"`
|
||||
DSFAID *uuid.UUID `json:"dsfa_id,omitempty"`
|
||||
ResponsiblePerson string `json:"responsible_person"`
|
||||
ResponsibleDepartment string `json:"responsible_department"`
|
||||
Systems []string `json:"systems"` // IT systems involved
|
||||
Status string `json:"status"` // draft, active, under_review, archived
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uuid.UUID `json:"created_by"`
|
||||
LastReviewedAt *time.Time `json:"last_reviewed_at,omitempty"`
|
||||
NextReviewAt *time.Time `json:"next_review_at,omitempty"`
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DSFA - Datenschutz-Folgenabschätzung (Art. 35 DSGVO)
|
||||
// ============================================================================
|
||||
|
||||
// DSFA represents a Data Protection Impact Assessment
|
||||
type DSFA struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
TenantID uuid.UUID `json:"tenant_id"`
|
||||
NamespaceID *uuid.UUID `json:"namespace_id,omitempty"`
|
||||
ProcessingActivityID *uuid.UUID `json:"processing_activity_id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
ProcessingDescription string `json:"processing_description"`
|
||||
NecessityAssessment string `json:"necessity_assessment"`
|
||||
ProportionalityAssment string `json:"proportionality_assessment"`
|
||||
Risks []DSFARisk `json:"risks"`
|
||||
Mitigations []DSFAMitigation `json:"mitigations"`
|
||||
DPOConsulted bool `json:"dpo_consulted"`
|
||||
DPOOpinion string `json:"dpo_opinion,omitempty"`
|
||||
AuthorityConsulted bool `json:"authority_consulted"`
|
||||
AuthorityReference string `json:"authority_reference,omitempty"`
|
||||
Status string `json:"status"` // draft, in_progress, completed, approved, rejected
|
||||
OverallRiskLevel string `json:"overall_risk_level"` // low, medium, high, very_high
|
||||
Conclusion string `json:"conclusion"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uuid.UUID `json:"created_by"`
|
||||
ApprovedBy *uuid.UUID `json:"approved_by,omitempty"`
|
||||
ApprovedAt *time.Time `json:"approved_at,omitempty"`
|
||||
}
|
||||
|
||||
// DSFARisk represents a risk identified in the DSFA
|
||||
type DSFARisk struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Category string `json:"category"` // confidentiality, integrity, availability, rights_freedoms
|
||||
Description string `json:"description"`
|
||||
Likelihood string `json:"likelihood"` // low, medium, high
|
||||
Impact string `json:"impact"` // low, medium, high
|
||||
RiskLevel string `json:"risk_level"` // calculated: low, medium, high, very_high
|
||||
AffectedData []string `json:"affected_data"`
|
||||
}
|
||||
|
||||
// DSFAMitigation represents a mitigation measure for a DSFA risk
|
||||
type DSFAMitigation struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
RiskID uuid.UUID `json:"risk_id"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"` // technical, organizational, legal
|
||||
Status string `json:"status"` // planned, in_progress, implemented, verified
|
||||
ImplementedAt *time.Time `json:"implemented_at,omitempty"`
|
||||
VerifiedAt *time.Time `json:"verified_at,omitempty"`
|
||||
ResidualRisk string `json:"residual_risk"` // low, medium, high
|
||||
TOMReference *uuid.UUID `json:"tom_reference,omitempty"`
|
||||
ResponsibleParty string `json:"responsible_party"`
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TOM - Technische und Organisatorische Maßnahmen (Art. 32 DSGVO)
|
||||
// ============================================================================
|
||||
|
||||
// TOM represents a Technical or Organizational Measure
|
||||
type TOM struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
TenantID uuid.UUID `json:"tenant_id"`
|
||||
NamespaceID *uuid.UUID `json:"namespace_id,omitempty"`
|
||||
Category string `json:"category"` // access_control, encryption, pseudonymization, availability, resilience, monitoring, incident_response
|
||||
Subcategory string `json:"subcategory,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"` // technical, organizational
|
||||
ImplementationStatus string `json:"implementation_status"` // planned, in_progress, implemented, verified, not_applicable
|
||||
ImplementedAt *time.Time `json:"implemented_at,omitempty"`
|
||||
VerifiedAt *time.Time `json:"verified_at,omitempty"`
|
||||
VerifiedBy *uuid.UUID `json:"verified_by,omitempty"`
|
||||
EffectivenessRating string `json:"effectiveness_rating,omitempty"` // low, medium, high
|
||||
Documentation string `json:"documentation,omitempty"`
|
||||
ResponsiblePerson string `json:"responsible_person"`
|
||||
ResponsibleDepartment string `json:"responsible_department"`
|
||||
ReviewFrequency string `json:"review_frequency"` // monthly, quarterly, annually
|
||||
LastReviewAt *time.Time `json:"last_review_at,omitempty"`
|
||||
NextReviewAt *time.Time `json:"next_review_at,omitempty"`
|
||||
RelatedControls []string `json:"related_controls,omitempty"` // ISO 27001 controls, SOC2, etc.
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uuid.UUID `json:"created_by"`
|
||||
}
|
||||
|
||||
// TOMCategory represents predefined TOM categories per Art. 32 DSGVO
|
||||
var TOMCategories = []string{
|
||||
"access_control", // Zutrittskontrolle
|
||||
"admission_control", // Zugangskontrolle
|
||||
"access_management", // Zugriffskontrolle
|
||||
"transfer_control", // Weitergabekontrolle
|
||||
"input_control", // Eingabekontrolle
|
||||
"availability_control", // Verfügbarkeitskontrolle
|
||||
"separation_control", // Trennungskontrolle
|
||||
"encryption", // Verschlüsselung
|
||||
"pseudonymization", // Pseudonymisierung
|
||||
"resilience", // Belastbarkeit
|
||||
"recovery", // Wiederherstellung
|
||||
"testing", // Regelmäßige Überprüfung
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DSR - Data Subject Requests / Betroffenenrechte (Art. 15-22 DSGVO)
|
||||
// ============================================================================
|
||||
|
||||
// DSR represents a Data Subject Request
|
||||
type DSR struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
TenantID uuid.UUID `json:"tenant_id"`
|
||||
NamespaceID *uuid.UUID `json:"namespace_id,omitempty"`
|
||||
RequestType string `json:"request_type"` // access, rectification, erasure, restriction, portability, objection
|
||||
Status string `json:"status"` // received, verified, in_progress, completed, rejected, extended
|
||||
SubjectName string `json:"subject_name"`
|
||||
SubjectEmail string `json:"subject_email"`
|
||||
SubjectIdentifier string `json:"subject_identifier,omitempty"` // Customer ID, User ID, etc.
|
||||
RequestDescription string `json:"request_description"`
|
||||
RequestChannel string `json:"request_channel"` // email, form, phone, letter
|
||||
ReceivedAt time.Time `json:"received_at"`
|
||||
VerifiedAt *time.Time `json:"verified_at,omitempty"`
|
||||
VerificationMethod string `json:"verification_method,omitempty"`
|
||||
DeadlineAt time.Time `json:"deadline_at"` // Art. 12(3): 1 month, extendable by 2 months
|
||||
ExtendedDeadlineAt *time.Time `json:"extended_deadline_at,omitempty"`
|
||||
ExtensionReason string `json:"extension_reason,omitempty"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||
ResponseSent bool `json:"response_sent"`
|
||||
ResponseSentAt *time.Time `json:"response_sent_at,omitempty"`
|
||||
ResponseMethod string `json:"response_method,omitempty"`
|
||||
RejectionReason string `json:"rejection_reason,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
AffectedSystems []string `json:"affected_systems,omitempty"`
|
||||
AssignedTo *uuid.UUID `json:"assigned_to,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uuid.UUID `json:"created_by"`
|
||||
}
|
||||
|
||||
// DSRType represents the types of data subject requests
|
||||
var DSRTypes = map[string]string{
|
||||
"access": "Art. 15 - Auskunftsrecht",
|
||||
"rectification": "Art. 16 - Recht auf Berichtigung",
|
||||
"erasure": "Art. 17 - Recht auf Löschung",
|
||||
"restriction": "Art. 18 - Recht auf Einschränkung",
|
||||
"portability": "Art. 20 - Recht auf Datenübertragbarkeit",
|
||||
"objection": "Art. 21 - Widerspruchsrecht",
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Retention - Löschfristen (Art. 17 DSGVO)
|
||||
// ============================================================================
|
||||
|
||||
// RetentionPolicy represents a data retention policy
|
||||
type RetentionPolicy struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
TenantID uuid.UUID `json:"tenant_id"`
|
||||
NamespaceID *uuid.UUID `json:"namespace_id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
DataCategory string `json:"data_category"`
|
||||
RetentionPeriodDays int `json:"retention_period_days"`
|
||||
RetentionPeriodText string `json:"retention_period_text"` // Human readable: "3 Jahre", "10 Jahre nach Vertragsende"
|
||||
LegalBasis string `json:"legal_basis"` // Legal requirement, consent, legitimate interest
|
||||
LegalReference string `json:"legal_reference,omitempty"` // § 147 AO, § 257 HGB, etc.
|
||||
DeletionMethod string `json:"deletion_method"` // automatic, manual, anonymization
|
||||
DeletionProcedure string `json:"deletion_procedure,omitempty"`
|
||||
ExceptionCriteria string `json:"exception_criteria,omitempty"`
|
||||
ApplicableSystems []string `json:"applicable_systems,omitempty"`
|
||||
ResponsiblePerson string `json:"responsible_person"`
|
||||
ResponsibleDepartment string `json:"responsible_department"`
|
||||
Status string `json:"status"` // draft, active, archived
|
||||
LastReviewAt *time.Time `json:"last_review_at,omitempty"`
|
||||
NextReviewAt *time.Time `json:"next_review_at,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedBy uuid.UUID `json:"created_by"`
|
||||
}
|
||||
|
||||
// CommonRetentionPeriods defines common retention periods in German law
|
||||
var CommonRetentionPeriods = map[string]int{
|
||||
"steuerlich_10_jahre": 3650, // § 147 AO - Buchungsbelege
|
||||
"handelsrechtlich_6_jahre": 2190, // § 257 HGB - Handelsbriefe
|
||||
"arbeitsrechtlich_3_jahre": 1095, // Lohnunterlagen nach Ausscheiden
|
||||
"bewerbungen_6_monate": 180, // AGG-Frist
|
||||
"consent_widerruf_3_jahre": 1095, // Nachweis der Einwilligung
|
||||
"vertragsunterlagen_3_jahre": 1095, // Verjährungsfrist
|
||||
}
|
||||
664
ai-compliance-sdk/internal/dsgvo/store.go
Normal file
664
ai-compliance-sdk/internal/dsgvo/store.go
Normal file
@@ -0,0 +1,664 @@
|
||||
package dsgvo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// Store handles DSGVO data persistence
|
||||
type Store struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
// NewStore creates a new DSGVO store
|
||||
func NewStore(pool *pgxpool.Pool) *Store {
|
||||
return &Store{pool: pool}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// VVT - Verarbeitungsverzeichnis
|
||||
// ============================================================================
|
||||
|
||||
// CreateProcessingActivity creates a new processing activity
|
||||
func (s *Store) CreateProcessingActivity(ctx context.Context, pa *ProcessingActivity) error {
|
||||
pa.ID = uuid.New()
|
||||
pa.CreatedAt = time.Now().UTC()
|
||||
pa.UpdatedAt = pa.CreatedAt
|
||||
|
||||
metadata, _ := json.Marshal(pa.Metadata)
|
||||
dataCategories, _ := json.Marshal(pa.DataCategories)
|
||||
dataSubjectCategories, _ := json.Marshal(pa.DataSubjectCategories)
|
||||
recipients, _ := json.Marshal(pa.Recipients)
|
||||
tomReference, _ := json.Marshal(pa.TOMReference)
|
||||
systems, _ := json.Marshal(pa.Systems)
|
||||
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
INSERT INTO dsgvo_processing_activities (
|
||||
id, tenant_id, namespace_id, name, description, purpose, legal_basis, legal_basis_details,
|
||||
data_categories, data_subject_categories, recipients, third_country_transfer, transfer_safeguards,
|
||||
retention_period, retention_policy_id, tom_reference, dsfa_required, dsfa_id,
|
||||
responsible_person, responsible_department, systems, status, metadata,
|
||||
created_at, updated_at, created_by, last_reviewed_at, next_review_at
|
||||
) VALUES ($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)
|
||||
`, pa.ID, pa.TenantID, pa.NamespaceID, pa.Name, pa.Description, pa.Purpose, pa.LegalBasis, pa.LegalBasisDetails,
|
||||
dataCategories, dataSubjectCategories, recipients, pa.ThirdCountryTransfer, pa.TransferSafeguards,
|
||||
pa.RetentionPeriod, pa.RetentionPolicyID, tomReference, pa.DSFARequired, pa.DSFAID,
|
||||
pa.ResponsiblePerson, pa.ResponsibleDepartment, systems, pa.Status, metadata,
|
||||
pa.CreatedAt, pa.UpdatedAt, pa.CreatedBy, pa.LastReviewedAt, pa.NextReviewAt)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetProcessingActivity retrieves a processing activity by ID
|
||||
func (s *Store) GetProcessingActivity(ctx context.Context, id uuid.UUID) (*ProcessingActivity, error) {
|
||||
var pa ProcessingActivity
|
||||
var metadata, dataCategories, dataSubjectCategories, recipients, tomReference, systems []byte
|
||||
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
SELECT id, tenant_id, namespace_id, name, description, purpose, legal_basis, legal_basis_details,
|
||||
data_categories, data_subject_categories, recipients, third_country_transfer, transfer_safeguards,
|
||||
retention_period, retention_policy_id, tom_reference, dsfa_required, dsfa_id,
|
||||
responsible_person, responsible_department, systems, status, metadata,
|
||||
created_at, updated_at, created_by, last_reviewed_at, next_review_at
|
||||
FROM dsgvo_processing_activities WHERE id = $1
|
||||
`, id).Scan(&pa.ID, &pa.TenantID, &pa.NamespaceID, &pa.Name, &pa.Description, &pa.Purpose, &pa.LegalBasis, &pa.LegalBasisDetails,
|
||||
&dataCategories, &dataSubjectCategories, &recipients, &pa.ThirdCountryTransfer, &pa.TransferSafeguards,
|
||||
&pa.RetentionPeriod, &pa.RetentionPolicyID, &tomReference, &pa.DSFARequired, &pa.DSFAID,
|
||||
&pa.ResponsiblePerson, &pa.ResponsibleDepartment, &systems, &pa.Status, &metadata,
|
||||
&pa.CreatedAt, &pa.UpdatedAt, &pa.CreatedBy, &pa.LastReviewedAt, &pa.NextReviewAt)
|
||||
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json.Unmarshal(metadata, &pa.Metadata)
|
||||
json.Unmarshal(dataCategories, &pa.DataCategories)
|
||||
json.Unmarshal(dataSubjectCategories, &pa.DataSubjectCategories)
|
||||
json.Unmarshal(recipients, &pa.Recipients)
|
||||
json.Unmarshal(tomReference, &pa.TOMReference)
|
||||
json.Unmarshal(systems, &pa.Systems)
|
||||
|
||||
return &pa, nil
|
||||
}
|
||||
|
||||
// ListProcessingActivities lists processing activities for a tenant
|
||||
func (s *Store) ListProcessingActivities(ctx context.Context, tenantID uuid.UUID, namespaceID *uuid.UUID) ([]ProcessingActivity, error) {
|
||||
query := `
|
||||
SELECT id, tenant_id, namespace_id, name, description, purpose, legal_basis, legal_basis_details,
|
||||
data_categories, data_subject_categories, recipients, third_country_transfer, transfer_safeguards,
|
||||
retention_period, retention_policy_id, tom_reference, dsfa_required, dsfa_id,
|
||||
responsible_person, responsible_department, systems, status, metadata,
|
||||
created_at, updated_at, created_by, last_reviewed_at, next_review_at
|
||||
FROM dsgvo_processing_activities WHERE tenant_id = $1`
|
||||
|
||||
args := []interface{}{tenantID}
|
||||
if namespaceID != nil {
|
||||
query += " AND namespace_id = $2"
|
||||
args = append(args, *namespaceID)
|
||||
}
|
||||
query += " ORDER BY name"
|
||||
|
||||
rows, err := s.pool.Query(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var activities []ProcessingActivity
|
||||
for rows.Next() {
|
||||
var pa ProcessingActivity
|
||||
var metadata, dataCategories, dataSubjectCategories, recipients, tomReference, systems []byte
|
||||
|
||||
err := rows.Scan(&pa.ID, &pa.TenantID, &pa.NamespaceID, &pa.Name, &pa.Description, &pa.Purpose, &pa.LegalBasis, &pa.LegalBasisDetails,
|
||||
&dataCategories, &dataSubjectCategories, &recipients, &pa.ThirdCountryTransfer, &pa.TransferSafeguards,
|
||||
&pa.RetentionPeriod, &pa.RetentionPolicyID, &tomReference, &pa.DSFARequired, &pa.DSFAID,
|
||||
&pa.ResponsiblePerson, &pa.ResponsibleDepartment, &systems, &pa.Status, &metadata,
|
||||
&pa.CreatedAt, &pa.UpdatedAt, &pa.CreatedBy, &pa.LastReviewedAt, &pa.NextReviewAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json.Unmarshal(metadata, &pa.Metadata)
|
||||
json.Unmarshal(dataCategories, &pa.DataCategories)
|
||||
json.Unmarshal(dataSubjectCategories, &pa.DataSubjectCategories)
|
||||
json.Unmarshal(recipients, &pa.Recipients)
|
||||
json.Unmarshal(tomReference, &pa.TOMReference)
|
||||
json.Unmarshal(systems, &pa.Systems)
|
||||
|
||||
activities = append(activities, pa)
|
||||
}
|
||||
|
||||
return activities, nil
|
||||
}
|
||||
|
||||
// UpdateProcessingActivity updates a processing activity
|
||||
func (s *Store) UpdateProcessingActivity(ctx context.Context, pa *ProcessingActivity) error {
|
||||
pa.UpdatedAt = time.Now().UTC()
|
||||
|
||||
metadata, _ := json.Marshal(pa.Metadata)
|
||||
dataCategories, _ := json.Marshal(pa.DataCategories)
|
||||
dataSubjectCategories, _ := json.Marshal(pa.DataSubjectCategories)
|
||||
recipients, _ := json.Marshal(pa.Recipients)
|
||||
tomReference, _ := json.Marshal(pa.TOMReference)
|
||||
systems, _ := json.Marshal(pa.Systems)
|
||||
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
UPDATE dsgvo_processing_activities SET
|
||||
name = $2, description = $3, purpose = $4, legal_basis = $5, legal_basis_details = $6,
|
||||
data_categories = $7, data_subject_categories = $8, recipients = $9, third_country_transfer = $10,
|
||||
transfer_safeguards = $11, retention_period = $12, retention_policy_id = $13, tom_reference = $14,
|
||||
dsfa_required = $15, dsfa_id = $16, responsible_person = $17, responsible_department = $18,
|
||||
systems = $19, status = $20, metadata = $21, updated_at = $22, last_reviewed_at = $23, next_review_at = $24
|
||||
WHERE id = $1
|
||||
`, pa.ID, pa.Name, pa.Description, pa.Purpose, pa.LegalBasis, pa.LegalBasisDetails,
|
||||
dataCategories, dataSubjectCategories, recipients, pa.ThirdCountryTransfer,
|
||||
pa.TransferSafeguards, pa.RetentionPeriod, pa.RetentionPolicyID, tomReference,
|
||||
pa.DSFARequired, pa.DSFAID, pa.ResponsiblePerson, pa.ResponsibleDepartment,
|
||||
systems, pa.Status, metadata, pa.UpdatedAt, pa.LastReviewedAt, pa.NextReviewAt)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteProcessingActivity deletes a processing activity
|
||||
func (s *Store) DeleteProcessingActivity(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := s.pool.Exec(ctx, "DELETE FROM dsgvo_processing_activities WHERE id = $1", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TOM - Technische und Organisatorische Maßnahmen
|
||||
// ============================================================================
|
||||
|
||||
// CreateTOM creates a new TOM entry
|
||||
func (s *Store) CreateTOM(ctx context.Context, tom *TOM) error {
|
||||
tom.ID = uuid.New()
|
||||
tom.CreatedAt = time.Now().UTC()
|
||||
tom.UpdatedAt = tom.CreatedAt
|
||||
|
||||
metadata, _ := json.Marshal(tom.Metadata)
|
||||
relatedControls, _ := json.Marshal(tom.RelatedControls)
|
||||
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
INSERT INTO dsgvo_tom (
|
||||
id, tenant_id, namespace_id, category, subcategory, name, description, type,
|
||||
implementation_status, implemented_at, verified_at, verified_by, effectiveness_rating,
|
||||
documentation, responsible_person, responsible_department, review_frequency,
|
||||
last_review_at, next_review_at, related_controls, metadata, created_at, updated_at, created_by
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)
|
||||
`, tom.ID, tom.TenantID, tom.NamespaceID, tom.Category, tom.Subcategory, tom.Name, tom.Description, tom.Type,
|
||||
tom.ImplementationStatus, tom.ImplementedAt, tom.VerifiedAt, tom.VerifiedBy, tom.EffectivenessRating,
|
||||
tom.Documentation, tom.ResponsiblePerson, tom.ResponsibleDepartment, tom.ReviewFrequency,
|
||||
tom.LastReviewAt, tom.NextReviewAt, relatedControls, metadata, tom.CreatedAt, tom.UpdatedAt, tom.CreatedBy)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTOM retrieves a TOM by ID
|
||||
func (s *Store) GetTOM(ctx context.Context, id uuid.UUID) (*TOM, error) {
|
||||
var tom TOM
|
||||
var metadata, relatedControls []byte
|
||||
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
SELECT id, tenant_id, namespace_id, category, subcategory, name, description, type,
|
||||
implementation_status, implemented_at, verified_at, verified_by, effectiveness_rating,
|
||||
documentation, responsible_person, responsible_department, review_frequency,
|
||||
last_review_at, next_review_at, related_controls, metadata, created_at, updated_at, created_by
|
||||
FROM dsgvo_tom WHERE id = $1
|
||||
`, id).Scan(&tom.ID, &tom.TenantID, &tom.NamespaceID, &tom.Category, &tom.Subcategory, &tom.Name, &tom.Description, &tom.Type,
|
||||
&tom.ImplementationStatus, &tom.ImplementedAt, &tom.VerifiedAt, &tom.VerifiedBy, &tom.EffectivenessRating,
|
||||
&tom.Documentation, &tom.ResponsiblePerson, &tom.ResponsibleDepartment, &tom.ReviewFrequency,
|
||||
&tom.LastReviewAt, &tom.NextReviewAt, &relatedControls, &metadata, &tom.CreatedAt, &tom.UpdatedAt, &tom.CreatedBy)
|
||||
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json.Unmarshal(metadata, &tom.Metadata)
|
||||
json.Unmarshal(relatedControls, &tom.RelatedControls)
|
||||
|
||||
return &tom, nil
|
||||
}
|
||||
|
||||
// ListTOMs lists TOMs for a tenant
|
||||
func (s *Store) ListTOMs(ctx context.Context, tenantID uuid.UUID, category string) ([]TOM, error) {
|
||||
query := `
|
||||
SELECT id, tenant_id, namespace_id, category, subcategory, name, description, type,
|
||||
implementation_status, implemented_at, verified_at, verified_by, effectiveness_rating,
|
||||
documentation, responsible_person, responsible_department, review_frequency,
|
||||
last_review_at, next_review_at, related_controls, metadata, created_at, updated_at, created_by
|
||||
FROM dsgvo_tom WHERE tenant_id = $1`
|
||||
|
||||
args := []interface{}{tenantID}
|
||||
if category != "" {
|
||||
query += " AND category = $2"
|
||||
args = append(args, category)
|
||||
}
|
||||
query += " ORDER BY category, name"
|
||||
|
||||
rows, err := s.pool.Query(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var toms []TOM
|
||||
for rows.Next() {
|
||||
var tom TOM
|
||||
var metadata, relatedControls []byte
|
||||
|
||||
err := rows.Scan(&tom.ID, &tom.TenantID, &tom.NamespaceID, &tom.Category, &tom.Subcategory, &tom.Name, &tom.Description, &tom.Type,
|
||||
&tom.ImplementationStatus, &tom.ImplementedAt, &tom.VerifiedAt, &tom.VerifiedBy, &tom.EffectivenessRating,
|
||||
&tom.Documentation, &tom.ResponsiblePerson, &tom.ResponsibleDepartment, &tom.ReviewFrequency,
|
||||
&tom.LastReviewAt, &tom.NextReviewAt, &relatedControls, &metadata, &tom.CreatedAt, &tom.UpdatedAt, &tom.CreatedBy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json.Unmarshal(metadata, &tom.Metadata)
|
||||
json.Unmarshal(relatedControls, &tom.RelatedControls)
|
||||
|
||||
toms = append(toms, tom)
|
||||
}
|
||||
|
||||
return toms, nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DSR - Data Subject Requests
|
||||
// ============================================================================
|
||||
|
||||
// CreateDSR creates a new DSR
|
||||
func (s *Store) CreateDSR(ctx context.Context, dsr *DSR) error {
|
||||
dsr.ID = uuid.New()
|
||||
dsr.CreatedAt = time.Now().UTC()
|
||||
dsr.UpdatedAt = dsr.CreatedAt
|
||||
|
||||
// Default deadline: 1 month from receipt
|
||||
if dsr.DeadlineAt.IsZero() {
|
||||
dsr.DeadlineAt = dsr.ReceivedAt.AddDate(0, 1, 0)
|
||||
}
|
||||
|
||||
metadata, _ := json.Marshal(dsr.Metadata)
|
||||
affectedSystems, _ := json.Marshal(dsr.AffectedSystems)
|
||||
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
INSERT INTO dsgvo_dsr (
|
||||
id, tenant_id, namespace_id, request_type, status, subject_name, subject_email,
|
||||
subject_identifier, request_description, request_channel, received_at, verified_at,
|
||||
verification_method, deadline_at, extended_deadline_at, extension_reason,
|
||||
completed_at, response_sent, response_sent_at, response_method, rejection_reason,
|
||||
notes, affected_systems, assigned_to, metadata, created_at, updated_at, created_by
|
||||
) VALUES ($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)
|
||||
`, dsr.ID, dsr.TenantID, dsr.NamespaceID, dsr.RequestType, dsr.Status, dsr.SubjectName, dsr.SubjectEmail,
|
||||
dsr.SubjectIdentifier, dsr.RequestDescription, dsr.RequestChannel, dsr.ReceivedAt, dsr.VerifiedAt,
|
||||
dsr.VerificationMethod, dsr.DeadlineAt, dsr.ExtendedDeadlineAt, dsr.ExtensionReason,
|
||||
dsr.CompletedAt, dsr.ResponseSent, dsr.ResponseSentAt, dsr.ResponseMethod, dsr.RejectionReason,
|
||||
dsr.Notes, affectedSystems, dsr.AssignedTo, metadata, dsr.CreatedAt, dsr.UpdatedAt, dsr.CreatedBy)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetDSR retrieves a DSR by ID
|
||||
func (s *Store) GetDSR(ctx context.Context, id uuid.UUID) (*DSR, error) {
|
||||
var dsr DSR
|
||||
var metadata, affectedSystems []byte
|
||||
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
SELECT id, tenant_id, namespace_id, request_type, status, subject_name, subject_email,
|
||||
subject_identifier, request_description, request_channel, received_at, verified_at,
|
||||
verification_method, deadline_at, extended_deadline_at, extension_reason,
|
||||
completed_at, response_sent, response_sent_at, response_method, rejection_reason,
|
||||
notes, affected_systems, assigned_to, metadata, created_at, updated_at, created_by
|
||||
FROM dsgvo_dsr WHERE id = $1
|
||||
`, id).Scan(&dsr.ID, &dsr.TenantID, &dsr.NamespaceID, &dsr.RequestType, &dsr.Status, &dsr.SubjectName, &dsr.SubjectEmail,
|
||||
&dsr.SubjectIdentifier, &dsr.RequestDescription, &dsr.RequestChannel, &dsr.ReceivedAt, &dsr.VerifiedAt,
|
||||
&dsr.VerificationMethod, &dsr.DeadlineAt, &dsr.ExtendedDeadlineAt, &dsr.ExtensionReason,
|
||||
&dsr.CompletedAt, &dsr.ResponseSent, &dsr.ResponseSentAt, &dsr.ResponseMethod, &dsr.RejectionReason,
|
||||
&dsr.Notes, &affectedSystems, &dsr.AssignedTo, &metadata, &dsr.CreatedAt, &dsr.UpdatedAt, &dsr.CreatedBy)
|
||||
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json.Unmarshal(metadata, &dsr.Metadata)
|
||||
json.Unmarshal(affectedSystems, &dsr.AffectedSystems)
|
||||
|
||||
return &dsr, nil
|
||||
}
|
||||
|
||||
// ListDSRs lists DSRs for a tenant with optional filters
|
||||
func (s *Store) ListDSRs(ctx context.Context, tenantID uuid.UUID, status string, requestType string) ([]DSR, error) {
|
||||
query := `
|
||||
SELECT id, tenant_id, namespace_id, request_type, status, subject_name, subject_email,
|
||||
subject_identifier, request_description, request_channel, received_at, verified_at,
|
||||
verification_method, deadline_at, extended_deadline_at, extension_reason,
|
||||
completed_at, response_sent, response_sent_at, response_method, rejection_reason,
|
||||
notes, affected_systems, assigned_to, metadata, created_at, updated_at, created_by
|
||||
FROM dsgvo_dsr WHERE tenant_id = $1`
|
||||
|
||||
args := []interface{}{tenantID}
|
||||
argIdx := 2
|
||||
|
||||
if status != "" {
|
||||
query += " AND status = $" + string(rune('0'+argIdx))
|
||||
args = append(args, status)
|
||||
argIdx++
|
||||
}
|
||||
if requestType != "" {
|
||||
query += " AND request_type = $" + string(rune('0'+argIdx))
|
||||
args = append(args, requestType)
|
||||
}
|
||||
query += " ORDER BY deadline_at ASC"
|
||||
|
||||
rows, err := s.pool.Query(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var dsrs []DSR
|
||||
for rows.Next() {
|
||||
var dsr DSR
|
||||
var metadata, affectedSystems []byte
|
||||
|
||||
err := rows.Scan(&dsr.ID, &dsr.TenantID, &dsr.NamespaceID, &dsr.RequestType, &dsr.Status, &dsr.SubjectName, &dsr.SubjectEmail,
|
||||
&dsr.SubjectIdentifier, &dsr.RequestDescription, &dsr.RequestChannel, &dsr.ReceivedAt, &dsr.VerifiedAt,
|
||||
&dsr.VerificationMethod, &dsr.DeadlineAt, &dsr.ExtendedDeadlineAt, &dsr.ExtensionReason,
|
||||
&dsr.CompletedAt, &dsr.ResponseSent, &dsr.ResponseSentAt, &dsr.ResponseMethod, &dsr.RejectionReason,
|
||||
&dsr.Notes, &affectedSystems, &dsr.AssignedTo, &metadata, &dsr.CreatedAt, &dsr.UpdatedAt, &dsr.CreatedBy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json.Unmarshal(metadata, &dsr.Metadata)
|
||||
json.Unmarshal(affectedSystems, &dsr.AffectedSystems)
|
||||
|
||||
dsrs = append(dsrs, dsr)
|
||||
}
|
||||
|
||||
return dsrs, nil
|
||||
}
|
||||
|
||||
// UpdateDSR updates a DSR
|
||||
func (s *Store) UpdateDSR(ctx context.Context, dsr *DSR) error {
|
||||
dsr.UpdatedAt = time.Now().UTC()
|
||||
|
||||
metadata, _ := json.Marshal(dsr.Metadata)
|
||||
affectedSystems, _ := json.Marshal(dsr.AffectedSystems)
|
||||
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
UPDATE dsgvo_dsr SET
|
||||
status = $2, verified_at = $3, verification_method = $4, extended_deadline_at = $5,
|
||||
extension_reason = $6, completed_at = $7, response_sent = $8, response_sent_at = $9,
|
||||
response_method = $10, rejection_reason = $11, notes = $12, affected_systems = $13,
|
||||
assigned_to = $14, metadata = $15, updated_at = $16
|
||||
WHERE id = $1
|
||||
`, dsr.ID, dsr.Status, dsr.VerifiedAt, dsr.VerificationMethod, dsr.ExtendedDeadlineAt,
|
||||
dsr.ExtensionReason, dsr.CompletedAt, dsr.ResponseSent, dsr.ResponseSentAt,
|
||||
dsr.ResponseMethod, dsr.RejectionReason, dsr.Notes, affectedSystems,
|
||||
dsr.AssignedTo, metadata, dsr.UpdatedAt)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Retention Policies
|
||||
// ============================================================================
|
||||
|
||||
// CreateRetentionPolicy creates a new retention policy
|
||||
func (s *Store) CreateRetentionPolicy(ctx context.Context, rp *RetentionPolicy) error {
|
||||
rp.ID = uuid.New()
|
||||
rp.CreatedAt = time.Now().UTC()
|
||||
rp.UpdatedAt = rp.CreatedAt
|
||||
|
||||
metadata, _ := json.Marshal(rp.Metadata)
|
||||
applicableSystems, _ := json.Marshal(rp.ApplicableSystems)
|
||||
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
INSERT INTO dsgvo_retention_policies (
|
||||
id, tenant_id, namespace_id, name, description, data_category, retention_period_days,
|
||||
retention_period_text, legal_basis, legal_reference, deletion_method, deletion_procedure,
|
||||
exception_criteria, applicable_systems, responsible_person, responsible_department,
|
||||
status, last_review_at, next_review_at, metadata, created_at, updated_at, created_by
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23)
|
||||
`, rp.ID, rp.TenantID, rp.NamespaceID, rp.Name, rp.Description, rp.DataCategory, rp.RetentionPeriodDays,
|
||||
rp.RetentionPeriodText, rp.LegalBasis, rp.LegalReference, rp.DeletionMethod, rp.DeletionProcedure,
|
||||
rp.ExceptionCriteria, applicableSystems, rp.ResponsiblePerson, rp.ResponsibleDepartment,
|
||||
rp.Status, rp.LastReviewAt, rp.NextReviewAt, metadata, rp.CreatedAt, rp.UpdatedAt, rp.CreatedBy)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ListRetentionPolicies lists retention policies for a tenant
|
||||
func (s *Store) ListRetentionPolicies(ctx context.Context, tenantID uuid.UUID) ([]RetentionPolicy, error) {
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
SELECT id, tenant_id, namespace_id, name, description, data_category, retention_period_days,
|
||||
retention_period_text, legal_basis, legal_reference, deletion_method, deletion_procedure,
|
||||
exception_criteria, applicable_systems, responsible_person, responsible_department,
|
||||
status, last_review_at, next_review_at, metadata, created_at, updated_at, created_by
|
||||
FROM dsgvo_retention_policies WHERE tenant_id = $1 ORDER BY name
|
||||
`, tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var policies []RetentionPolicy
|
||||
for rows.Next() {
|
||||
var rp RetentionPolicy
|
||||
var metadata, applicableSystems []byte
|
||||
|
||||
err := rows.Scan(&rp.ID, &rp.TenantID, &rp.NamespaceID, &rp.Name, &rp.Description, &rp.DataCategory, &rp.RetentionPeriodDays,
|
||||
&rp.RetentionPeriodText, &rp.LegalBasis, &rp.LegalReference, &rp.DeletionMethod, &rp.DeletionProcedure,
|
||||
&rp.ExceptionCriteria, &applicableSystems, &rp.ResponsiblePerson, &rp.ResponsibleDepartment,
|
||||
&rp.Status, &rp.LastReviewAt, &rp.NextReviewAt, &metadata, &rp.CreatedAt, &rp.UpdatedAt, &rp.CreatedBy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json.Unmarshal(metadata, &rp.Metadata)
|
||||
json.Unmarshal(applicableSystems, &rp.ApplicableSystems)
|
||||
|
||||
policies = append(policies, rp)
|
||||
}
|
||||
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DSFA - Datenschutz-Folgenabschätzung
|
||||
// ============================================================================
|
||||
|
||||
// CreateDSFA creates a new DSFA
|
||||
func (s *Store) CreateDSFA(ctx context.Context, dsfa *DSFA) error {
|
||||
dsfa.ID = uuid.New()
|
||||
dsfa.CreatedAt = time.Now().UTC()
|
||||
dsfa.UpdatedAt = dsfa.CreatedAt
|
||||
|
||||
metadata, _ := json.Marshal(dsfa.Metadata)
|
||||
risks, _ := json.Marshal(dsfa.Risks)
|
||||
mitigations, _ := json.Marshal(dsfa.Mitigations)
|
||||
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
INSERT INTO dsgvo_dsfa (
|
||||
id, tenant_id, namespace_id, processing_activity_id, name, description,
|
||||
processing_description, necessity_assessment, proportionality_assessment,
|
||||
risks, mitigations, dpo_consulted, dpo_opinion, authority_consulted, authority_reference,
|
||||
status, overall_risk_level, conclusion, metadata, created_at, updated_at, created_by,
|
||||
approved_by, approved_at
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)
|
||||
`, dsfa.ID, dsfa.TenantID, dsfa.NamespaceID, dsfa.ProcessingActivityID, dsfa.Name, dsfa.Description,
|
||||
dsfa.ProcessingDescription, dsfa.NecessityAssessment, dsfa.ProportionalityAssment,
|
||||
risks, mitigations, dsfa.DPOConsulted, dsfa.DPOOpinion, dsfa.AuthorityConsulted, dsfa.AuthorityReference,
|
||||
dsfa.Status, dsfa.OverallRiskLevel, dsfa.Conclusion, metadata, dsfa.CreatedAt, dsfa.UpdatedAt, dsfa.CreatedBy,
|
||||
dsfa.ApprovedBy, dsfa.ApprovedAt)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetDSFA retrieves a DSFA by ID
|
||||
func (s *Store) GetDSFA(ctx context.Context, id uuid.UUID) (*DSFA, error) {
|
||||
var dsfa DSFA
|
||||
var metadata, risks, mitigations []byte
|
||||
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
SELECT id, tenant_id, namespace_id, processing_activity_id, name, description,
|
||||
processing_description, necessity_assessment, proportionality_assessment,
|
||||
risks, mitigations, dpo_consulted, dpo_opinion, authority_consulted, authority_reference,
|
||||
status, overall_risk_level, conclusion, metadata, created_at, updated_at, created_by,
|
||||
approved_by, approved_at
|
||||
FROM dsgvo_dsfa WHERE id = $1
|
||||
`, id).Scan(&dsfa.ID, &dsfa.TenantID, &dsfa.NamespaceID, &dsfa.ProcessingActivityID, &dsfa.Name, &dsfa.Description,
|
||||
&dsfa.ProcessingDescription, &dsfa.NecessityAssessment, &dsfa.ProportionalityAssment,
|
||||
&risks, &mitigations, &dsfa.DPOConsulted, &dsfa.DPOOpinion, &dsfa.AuthorityConsulted, &dsfa.AuthorityReference,
|
||||
&dsfa.Status, &dsfa.OverallRiskLevel, &dsfa.Conclusion, &metadata, &dsfa.CreatedAt, &dsfa.UpdatedAt, &dsfa.CreatedBy,
|
||||
&dsfa.ApprovedBy, &dsfa.ApprovedAt)
|
||||
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json.Unmarshal(metadata, &dsfa.Metadata)
|
||||
json.Unmarshal(risks, &dsfa.Risks)
|
||||
json.Unmarshal(mitigations, &dsfa.Mitigations)
|
||||
|
||||
return &dsfa, nil
|
||||
}
|
||||
|
||||
// ListDSFAs lists DSFAs for a tenant
|
||||
func (s *Store) ListDSFAs(ctx context.Context, tenantID uuid.UUID, status string) ([]DSFA, error) {
|
||||
query := `
|
||||
SELECT id, tenant_id, namespace_id, processing_activity_id, name, description,
|
||||
processing_description, necessity_assessment, proportionality_assessment,
|
||||
risks, mitigations, dpo_consulted, dpo_opinion, authority_consulted, authority_reference,
|
||||
status, overall_risk_level, conclusion, metadata, created_at, updated_at, created_by,
|
||||
approved_by, approved_at
|
||||
FROM dsgvo_dsfa WHERE tenant_id = $1`
|
||||
|
||||
args := []interface{}{tenantID}
|
||||
if status != "" {
|
||||
query += " AND status = $2"
|
||||
args = append(args, status)
|
||||
}
|
||||
query += " ORDER BY created_at DESC"
|
||||
|
||||
rows, err := s.pool.Query(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var dsfas []DSFA
|
||||
for rows.Next() {
|
||||
var dsfa DSFA
|
||||
var metadata, risks, mitigations []byte
|
||||
|
||||
err := rows.Scan(&dsfa.ID, &dsfa.TenantID, &dsfa.NamespaceID, &dsfa.ProcessingActivityID, &dsfa.Name, &dsfa.Description,
|
||||
&dsfa.ProcessingDescription, &dsfa.NecessityAssessment, &dsfa.ProportionalityAssment,
|
||||
&risks, &mitigations, &dsfa.DPOConsulted, &dsfa.DPOOpinion, &dsfa.AuthorityConsulted, &dsfa.AuthorityReference,
|
||||
&dsfa.Status, &dsfa.OverallRiskLevel, &dsfa.Conclusion, &metadata, &dsfa.CreatedAt, &dsfa.UpdatedAt, &dsfa.CreatedBy,
|
||||
&dsfa.ApprovedBy, &dsfa.ApprovedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json.Unmarshal(metadata, &dsfa.Metadata)
|
||||
json.Unmarshal(risks, &dsfa.Risks)
|
||||
json.Unmarshal(mitigations, &dsfa.Mitigations)
|
||||
|
||||
dsfas = append(dsfas, dsfa)
|
||||
}
|
||||
|
||||
return dsfas, nil
|
||||
}
|
||||
|
||||
// UpdateDSFA updates a DSFA
|
||||
func (s *Store) UpdateDSFA(ctx context.Context, dsfa *DSFA) error {
|
||||
dsfa.UpdatedAt = time.Now().UTC()
|
||||
|
||||
metadata, _ := json.Marshal(dsfa.Metadata)
|
||||
risks, _ := json.Marshal(dsfa.Risks)
|
||||
mitigations, _ := json.Marshal(dsfa.Mitigations)
|
||||
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
UPDATE dsgvo_dsfa SET
|
||||
name = $2, description = $3, processing_description = $4,
|
||||
necessity_assessment = $5, proportionality_assessment = $6,
|
||||
risks = $7, mitigations = $8, dpo_consulted = $9, dpo_opinion = $10,
|
||||
authority_consulted = $11, authority_reference = $12, status = $13,
|
||||
overall_risk_level = $14, conclusion = $15, metadata = $16, updated_at = $17,
|
||||
approved_by = $18, approved_at = $19
|
||||
WHERE id = $1
|
||||
`, dsfa.ID, dsfa.Name, dsfa.Description, dsfa.ProcessingDescription,
|
||||
dsfa.NecessityAssessment, dsfa.ProportionalityAssment,
|
||||
risks, mitigations, dsfa.DPOConsulted, dsfa.DPOOpinion,
|
||||
dsfa.AuthorityConsulted, dsfa.AuthorityReference, dsfa.Status,
|
||||
dsfa.OverallRiskLevel, dsfa.Conclusion, metadata, dsfa.UpdatedAt,
|
||||
dsfa.ApprovedBy, dsfa.ApprovedAt)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteDSFA deletes a DSFA
|
||||
func (s *Store) DeleteDSFA(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := s.pool.Exec(ctx, "DELETE FROM dsgvo_dsfa WHERE id = $1", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Statistics
|
||||
// ============================================================================
|
||||
|
||||
// DSGVOStats contains DSGVO module statistics
|
||||
type DSGVOStats struct {
|
||||
ProcessingActivities int `json:"processing_activities"`
|
||||
ActiveProcessings int `json:"active_processings"`
|
||||
TOMsImplemented int `json:"toms_implemented"`
|
||||
TOMsPlanned int `json:"toms_planned"`
|
||||
OpenDSRs int `json:"open_dsrs"`
|
||||
OverdueDSRs int `json:"overdue_dsrs"`
|
||||
RetentionPolicies int `json:"retention_policies"`
|
||||
DSFAsCompleted int `json:"dsfas_completed"`
|
||||
}
|
||||
|
||||
// GetStats returns DSGVO statistics for a tenant
|
||||
func (s *Store) GetStats(ctx context.Context, tenantID uuid.UUID) (*DSGVOStats, error) {
|
||||
stats := &DSGVOStats{}
|
||||
|
||||
// Processing Activities
|
||||
s.pool.QueryRow(ctx, "SELECT COUNT(*) FROM dsgvo_processing_activities WHERE tenant_id = $1", tenantID).Scan(&stats.ProcessingActivities)
|
||||
s.pool.QueryRow(ctx, "SELECT COUNT(*) FROM dsgvo_processing_activities WHERE tenant_id = $1 AND status = 'active'", tenantID).Scan(&stats.ActiveProcessings)
|
||||
|
||||
// TOMs
|
||||
s.pool.QueryRow(ctx, "SELECT COUNT(*) FROM dsgvo_tom WHERE tenant_id = $1 AND implementation_status = 'implemented'", tenantID).Scan(&stats.TOMsImplemented)
|
||||
s.pool.QueryRow(ctx, "SELECT COUNT(*) FROM dsgvo_tom WHERE tenant_id = $1 AND implementation_status IN ('planned', 'in_progress')", tenantID).Scan(&stats.TOMsPlanned)
|
||||
|
||||
// DSRs
|
||||
s.pool.QueryRow(ctx, "SELECT COUNT(*) FROM dsgvo_dsr WHERE tenant_id = $1 AND status NOT IN ('completed', 'rejected')", tenantID).Scan(&stats.OpenDSRs)
|
||||
s.pool.QueryRow(ctx, "SELECT COUNT(*) FROM dsgvo_dsr WHERE tenant_id = $1 AND status NOT IN ('completed', 'rejected') AND deadline_at < NOW()", tenantID).Scan(&stats.OverdueDSRs)
|
||||
|
||||
// Retention Policies
|
||||
s.pool.QueryRow(ctx, "SELECT COUNT(*) FROM dsgvo_retention_policies WHERE tenant_id = $1 AND status = 'active'", tenantID).Scan(&stats.RetentionPolicies)
|
||||
|
||||
// DSFAs
|
||||
s.pool.QueryRow(ctx, "SELECT COUNT(*) FROM dsgvo_dsfa WHERE tenant_id = $1 AND status = 'approved'", tenantID).Scan(&stats.DSFAsCompleted)
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
Reference in New Issue
Block a user