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 }