Merge feat/zeroclaw-compliance-agent into main
Brings all compliance doc-check features: - 162 regex checks + 1874 Master Controls - LLM-agnostic agent with tool calling - Banner check (46 checks, 30 CMPs, stealth, Shadow DOM) - Impressum check (24 checks) - Deep consent verification (DataLayer, GCM, TCF) - CMP E2E tests (39 tests) - HTML email reports, FAQ, persistent history Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
import type { CompanyProfilePreset } from './company-profile-presets'
|
||||
|
||||
export const COMPANY_PROFILE_PRESETS: CompanyProfilePreset[] = [
|
||||
{
|
||||
id: 'saas_startup',
|
||||
label: 'SaaS Startup',
|
||||
description: 'B2B Software-Startup, 1-5 Mitarbeiter, Cloud-basiert, remote-first',
|
||||
icon: '\u{1F680}',
|
||||
profile: {
|
||||
legalForm: 'GmbH', industry: ['tech'], businessModel: 'b2b',
|
||||
companySize: 'micro', employeeCount: '1-9', headquartersCountry: 'DE',
|
||||
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: true,
|
||||
},
|
||||
scopeHints: {
|
||||
org_employee_count: '1-9', org_industry: 'tech', org_business_model: 'b2b',
|
||||
proc_ai_usage: 'yes', tech_hosting_location: 'eu',
|
||||
tech_encryption_transit: 'yes', tech_encryption_rest: 'yes',
|
||||
comp_documentation_level: 'basic',
|
||||
},
|
||||
recommendedDocs: [
|
||||
'privacy_policy', 'impressum', 'agb', 'cookie_policy', 'cookie_banner',
|
||||
'dpa', 'tom_documentation', 'vvt_register', 'loeschkonzept',
|
||||
'employee_dsi', 'applicant_dsi',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'consumer_app',
|
||||
label: 'App Startup (Consumer)',
|
||||
description: 'B2C Mobile App, 1-5 Mitarbeiter, App Store, Nutzerdaten',
|
||||
icon: '\u{1F4F1}',
|
||||
profile: {
|
||||
legalForm: 'GmbH', industry: ['tech'], businessModel: 'b2c',
|
||||
companySize: 'micro', employeeCount: '1-9', headquartersCountry: 'DE',
|
||||
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: false,
|
||||
},
|
||||
scopeHints: {
|
||||
org_employee_count: '1-9', org_industry: 'tech', org_business_model: 'b2c',
|
||||
data_volume: '1000-10000', proc_tracking: 'yes',
|
||||
prod_consent_management: 'yes', tech_hosting_location: 'eu',
|
||||
},
|
||||
recommendedDocs: [
|
||||
'privacy_policy', 'impressum', 'terms_of_use', 'cookie_policy', 'cookie_banner',
|
||||
'community_guidelines', 'acceptable_use', 'widerruf',
|
||||
'dpa', 'tom_documentation', 'vvt_register', 'loeschkonzept',
|
||||
'employee_dsi', 'applicant_dsi', 'social_media_dsi',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'ecommerce',
|
||||
label: 'E-Commerce / Online-Shop',
|
||||
description: 'Online-Handel B2C, 5-20 Mitarbeiter, Webshop, Zahlungsabwicklung',
|
||||
icon: '\u{1F6D2}',
|
||||
profile: {
|
||||
legalForm: 'GmbH', industry: ['retail'], businessModel: 'b2c',
|
||||
companySize: 'small', employeeCount: '10-49', headquartersCountry: 'DE',
|
||||
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: false,
|
||||
},
|
||||
scopeHints: {
|
||||
org_employee_count: '10-49', org_industry: 'retail', org_business_model: 'b2c',
|
||||
prod_webshop: 'yes', data_volume: '10000-100000',
|
||||
tech_hosting_location: 'eu', prod_consent_management: 'yes',
|
||||
},
|
||||
recommendedDocs: [
|
||||
'privacy_policy', 'impressum', 'agb', 'widerruf', 'cookie_policy', 'cookie_banner',
|
||||
'dpa', 'tom_documentation', 'vvt_register', 'loeschkonzept',
|
||||
'employee_dsi', 'applicant_dsi',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'it_agency',
|
||||
label: 'IT-Dienstleister / Agentur',
|
||||
description: 'IT-Beratung oder Agentur, 10-50 Mitarbeiter, Kundenprojekte',
|
||||
icon: '\u{1F4BB}',
|
||||
profile: {
|
||||
legalForm: 'GmbH', industry: ['tech'], businessModel: 'b2b',
|
||||
companySize: 'small', employeeCount: '10-49', headquartersCountry: 'DE',
|
||||
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: true,
|
||||
},
|
||||
scopeHints: {
|
||||
org_employee_count: '10-49', org_industry: 'tech', org_business_model: 'b2b',
|
||||
proc_ai_usage: 'yes', tech_hosting_location: 'eu',
|
||||
comp_vendor_management: 'yes', comp_training: 'yes',
|
||||
},
|
||||
recommendedDocs: [
|
||||
'privacy_policy', 'impressum', 'agb', 'cookie_policy', 'cookie_banner',
|
||||
'dpa', 'nda', 'sla', 'tom_documentation', 'vvt_register', 'loeschkonzept',
|
||||
'employee_dsi', 'applicant_dsi',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'maschinenbau',
|
||||
label: 'Maschinenbau KMU',
|
||||
description: 'Maschinenbau B2B, 50-200 Mitarbeiter, Produktion, CE-Kennzeichnung',
|
||||
icon: '\u{1F3ED}',
|
||||
profile: {
|
||||
legalForm: 'GmbH', industry: ['manufacturing'], businessModel: 'b2b',
|
||||
companySize: 'medium', employeeCount: '50-249', headquartersCountry: 'DE',
|
||||
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: false,
|
||||
},
|
||||
scopeHints: {
|
||||
org_employee_count: '50-249', org_industry: 'manufacturing', org_business_model: 'b2b',
|
||||
proc_employee_monitoring: 'no', tech_hosting_location: 'eu',
|
||||
comp_vendor_management: 'yes', comp_documentation_level: 'structured',
|
||||
},
|
||||
recommendedDocs: [
|
||||
'privacy_policy', 'impressum', 'agb', 'cookie_policy', 'cookie_banner',
|
||||
'dpa', 'nda', 'tom_documentation', 'vvt_register', 'loeschkonzept',
|
||||
'employee_dsi', 'applicant_dsi', 'whistleblower_policy',
|
||||
'dsfa', 'pflichtenregister',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'law_firm',
|
||||
label: 'Rechtsanwaltskanzlei',
|
||||
description: 'Kanzlei, 5-20 Mitarbeiter, Mandantendaten, besondere Vertraulichkeit',
|
||||
icon: '\u2696\uFE0F',
|
||||
profile: {
|
||||
legalForm: 'PartG', industry: ['legal'], businessModel: 'b2b',
|
||||
companySize: 'small', employeeCount: '1-9', headquartersCountry: 'DE',
|
||||
targetMarkets: ['DE'], isDataController: true, isDataProcessor: false,
|
||||
},
|
||||
scopeHints: {
|
||||
org_employee_count: '1-9', org_industry: 'legal', org_business_model: 'b2b',
|
||||
data_art9: 'no', tech_encryption_transit: 'yes',
|
||||
tech_encryption_rest: 'yes', comp_documentation_level: 'basic',
|
||||
},
|
||||
recommendedDocs: [
|
||||
'privacy_policy', 'impressum', 'cookie_policy', 'cookie_banner',
|
||||
'dpa', 'nda', 'tom_documentation', 'vvt_register', 'loeschkonzept',
|
||||
'employee_dsi', 'applicant_dsi',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'healthcare',
|
||||
label: 'Arztpraxis / Gesundheit',
|
||||
description: 'Gesundheitswesen, 5-50 Mitarbeiter, Patientendaten (Art. 9), hoher Schutzbedarf',
|
||||
icon: '\u{1F3E5}',
|
||||
profile: {
|
||||
legalForm: 'GbR', industry: ['healthcare'], businessModel: 'b2c',
|
||||
companySize: 'small', employeeCount: '1-9', headquartersCountry: 'DE',
|
||||
targetMarkets: ['DE'], isDataController: true, isDataProcessor: false,
|
||||
},
|
||||
scopeHints: {
|
||||
org_employee_count: '1-9', org_industry: 'healthcare', org_business_model: 'b2c',
|
||||
data_art9: 'yes', tech_encryption_transit: 'yes',
|
||||
tech_encryption_rest: 'yes', comp_documentation_level: 'basic',
|
||||
},
|
||||
recommendedDocs: [
|
||||
'privacy_policy', 'impressum', 'cookie_policy', 'cookie_banner',
|
||||
'dpa', 'tom_documentation', 'vvt_register', 'loeschkonzept', 'dsfa',
|
||||
'employee_dsi', 'applicant_dsi', 'pflichtenregister',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'handwerk',
|
||||
label: 'Handwerksbetrieb',
|
||||
description: 'Handwerk, 5-20 Mitarbeiter, Kundendaten, einfache IT',
|
||||
icon: '\u{1F527}',
|
||||
profile: {
|
||||
legalForm: 'GmbH', industry: ['crafts'], businessModel: 'b2c',
|
||||
companySize: 'small', employeeCount: '1-9', headquartersCountry: 'DE',
|
||||
targetMarkets: ['DE'], isDataController: true, isDataProcessor: false,
|
||||
},
|
||||
scopeHints: {
|
||||
org_employee_count: '1-9', org_industry: 'other', org_business_model: 'b2c',
|
||||
data_art9: 'no', tech_hosting_location: 'eu', comp_documentation_level: 'none',
|
||||
},
|
||||
recommendedDocs: [
|
||||
'privacy_policy', 'impressum', 'agb', 'cookie_policy', 'cookie_banner',
|
||||
'tom_documentation', 'vvt_register', 'loeschkonzept', 'employee_dsi',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'education',
|
||||
label: 'Bildungseinrichtung',
|
||||
description: 'Schule, Hochschule oder Weiterbildung, 20-100 Mitarbeiter, Schuelerdaten',
|
||||
icon: '\u{1F393}',
|
||||
profile: {
|
||||
legalForm: 'gGmbH', industry: ['education'], businessModel: 'b2c',
|
||||
companySize: 'medium', employeeCount: '10-49', headquartersCountry: 'DE',
|
||||
targetMarkets: ['DE'], isDataController: true, isDataProcessor: false,
|
||||
},
|
||||
scopeHints: {
|
||||
org_employee_count: '10-49', org_industry: 'education', org_business_model: 'b2c',
|
||||
data_minors: 'yes', tech_hosting_location: 'eu', comp_training: 'yes',
|
||||
},
|
||||
recommendedDocs: [
|
||||
'privacy_policy', 'impressum', 'cookie_policy', 'cookie_banner',
|
||||
'dpa', 'tom_documentation', 'vvt_register', 'loeschkonzept', 'dsfa',
|
||||
'employee_dsi', 'applicant_dsi', 'pflichtenregister',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'enterprise',
|
||||
label: 'Konzern / Enterprise',
|
||||
description: 'Grossunternehmen, 500+ MA, international, reguliert, ISO 27001',
|
||||
icon: '\u{1F3E2}',
|
||||
profile: {
|
||||
legalForm: 'AG', industry: ['finance'], businessModel: 'b2b',
|
||||
companySize: 'enterprise', employeeCount: '1000+', headquartersCountry: 'DE',
|
||||
targetMarkets: ['DE', 'EU', 'US'], isDataController: true, isDataProcessor: true,
|
||||
},
|
||||
scopeHints: {
|
||||
org_employee_count: '1000+', org_industry: 'finance', org_business_model: 'b2b',
|
||||
org_cert_target: 'iso27001', data_art9: 'yes', data_volume: '>1000000',
|
||||
proc_ai_usage: 'yes', tech_third_country: 'yes',
|
||||
tech_hosting_location: 'eu_us_adequacy', comp_vendor_management: 'yes',
|
||||
comp_training: 'yes', comp_documentation_level: 'comprehensive',
|
||||
},
|
||||
recommendedDocs: [
|
||||
'privacy_policy', 'impressum', 'agb', 'cookie_policy', 'cookie_banner',
|
||||
'dpa', 'nda', 'sla', 'cloud_service_agreement',
|
||||
'tom_documentation', 'vvt_register', 'loeschkonzept', 'dsfa', 'pflichtenregister',
|
||||
'data_protection_concept', 'consent_texts', 'informationspflichten', 'verpflichtungserklaerung',
|
||||
'dsr_process_art15', 'dsr_process_art16', 'dsr_process_art17',
|
||||
'dsr_process_art18', 'dsr_process_art20', 'dsr_process_art21',
|
||||
'isms_manual', 'it_security_concept', 'risk_management_concept',
|
||||
'information_security_policy', 'access_control_policy', 'encryption_policy',
|
||||
'change_management_policy', 'asset_management_policy',
|
||||
'data_protection_policy', 'data_classification_policy',
|
||||
'data_retention_policy', 'data_transfer_policy', 'privacy_incident_policy',
|
||||
'employee_dsi', 'applicant_dsi', 'whistleblower_policy', 'social_media_dsi',
|
||||
'employee_security_policy', 'security_awareness_policy', 'offboarding_policy',
|
||||
'transfer_impact_assessment', 'scc_companion',
|
||||
'vendor_risk_management_policy', 'third_party_security_policy',
|
||||
'business_continuity_policy', 'disaster_recovery_policy', 'crisis_management_policy',
|
||||
'ai_usage_policy', 'standard_operating_procedure',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'cloud_provider',
|
||||
label: 'Cloud / SaaS-Anbieter',
|
||||
description: 'Cloud-Infrastruktur oder SaaS, 20-100 MA, DevOps, ISO 27001 Ziel',
|
||||
icon: '\u2601\uFE0F',
|
||||
profile: {
|
||||
legalForm: 'GmbH', industry: ['tech'], businessModel: 'b2b',
|
||||
companySize: 'small', employeeCount: '10-49', headquartersCountry: 'DE',
|
||||
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: true,
|
||||
},
|
||||
scopeHints: {
|
||||
org_employee_count: '10-49', org_industry: 'tech', org_business_model: 'b2b',
|
||||
org_cert_iso27001: 'yes', proc_ai_usage: 'yes', tech_hosting_location: 'eu',
|
||||
tech_encryption_transit: 'yes', tech_encryption_rest: 'yes',
|
||||
comp_vendor_management: 'yes', comp_documentation_level: 'structured',
|
||||
},
|
||||
recommendedDocs: [
|
||||
'privacy_policy', 'impressum', 'agb', 'cookie_policy', 'cookie_banner',
|
||||
'dpa', 'nda', 'sla', 'cloud_service_agreement',
|
||||
'tom_documentation', 'vvt_register', 'loeschkonzept', 'pflichtenregister',
|
||||
'data_protection_concept', 'consent_texts',
|
||||
'isms_manual', 'it_security_concept', 'backup_recovery_concept',
|
||||
'logging_concept', 'incident_response_plan',
|
||||
'access_control_concept', 'risk_management_concept',
|
||||
'information_security_policy', 'access_control_policy', 'password_policy',
|
||||
'encryption_policy', 'logging_policy', 'backup_policy',
|
||||
'incident_response_policy', 'change_management_policy',
|
||||
'patch_management_policy', 'asset_management_policy',
|
||||
'cloud_security_policy', 'devsecops_policy',
|
||||
'secrets_management_policy', 'vulnerability_management_policy',
|
||||
'employee_dsi', 'applicant_dsi', 'employee_security_policy',
|
||||
'remote_work_policy', 'offboarding_policy',
|
||||
'vendor_risk_management_policy', 'third_party_security_policy',
|
||||
'business_continuity_policy', 'disaster_recovery_policy',
|
||||
'ai_usage_policy', 'cybersecurity_policy', 'byod_policy',
|
||||
'standard_operating_procedure',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'fintech',
|
||||
label: 'Finanzdienstleister',
|
||||
description: 'Finanz- oder Versicherungsbranche, 50-500 MA, reguliert',
|
||||
icon: '\u{1F3E6}',
|
||||
profile: {
|
||||
legalForm: 'GmbH', industry: ['finance'], businessModel: 'b2b',
|
||||
companySize: 'medium', employeeCount: '50-249', headquartersCountry: 'DE',
|
||||
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: true,
|
||||
},
|
||||
scopeHints: {
|
||||
org_employee_count: '50-249', org_industry: 'finance', org_business_model: 'b2b',
|
||||
data_art9: 'no', data_volume: '100000-1000000', tech_hosting_location: 'eu',
|
||||
tech_encryption_transit: 'yes', tech_encryption_rest: 'yes',
|
||||
comp_vendor_management: 'yes', comp_training: 'yes',
|
||||
comp_documentation_level: 'comprehensive',
|
||||
},
|
||||
recommendedDocs: [
|
||||
'privacy_policy', 'impressum', 'agb', 'cookie_policy', 'cookie_banner',
|
||||
'dpa', 'nda', 'sla',
|
||||
'tom_documentation', 'vvt_register', 'loeschkonzept', 'dsfa', 'pflichtenregister',
|
||||
'data_protection_concept', 'verpflichtungserklaerung', 'informationspflichten',
|
||||
'dsr_process_art15', 'dsr_process_art17', 'dsr_process_art20',
|
||||
'data_protection_policy', 'data_classification_policy',
|
||||
'data_retention_policy', 'data_transfer_policy', 'privacy_incident_policy',
|
||||
'it_security_concept', 'risk_management_concept',
|
||||
'information_security_policy', 'access_control_policy', 'encryption_policy',
|
||||
'employee_dsi', 'applicant_dsi', 'whistleblower_policy',
|
||||
'employee_security_policy', 'security_awareness_policy', 'offboarding_policy',
|
||||
'transfer_impact_assessment', 'vendor_risk_management_policy',
|
||||
'supplier_security_policy',
|
||||
'business_continuity_policy', 'disaster_recovery_policy', 'crisis_management_policy',
|
||||
'standard_operating_procedure',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'platform',
|
||||
label: 'Plattform / Marketplace',
|
||||
description: 'Online-Plattform mit Nutzern, UGC, Community, 10-50 MA',
|
||||
icon: '\u{1F310}',
|
||||
profile: {
|
||||
legalForm: 'GmbH', industry: ['tech'], businessModel: 'b2b2c',
|
||||
companySize: 'small', employeeCount: '10-49', headquartersCountry: 'DE',
|
||||
targetMarkets: ['DE', 'EU'], isDataController: true, isDataProcessor: false,
|
||||
},
|
||||
scopeHints: {
|
||||
org_employee_count: '10-49', org_industry: 'tech', org_business_model: 'b2b2c',
|
||||
data_volume: '10000-100000', proc_tracking: 'yes',
|
||||
prod_ugc_platform: 'yes', prod_consent_management: 'yes',
|
||||
tech_hosting_location: 'eu',
|
||||
},
|
||||
recommendedDocs: [
|
||||
'privacy_policy', 'impressum', 'terms_of_use', 'agb',
|
||||
'cookie_policy', 'cookie_banner', 'dpa',
|
||||
'community_guidelines', 'acceptable_use',
|
||||
'media_content_policy', 'copyright_policy', 'data_usage_clause',
|
||||
'tom_documentation', 'vvt_register', 'loeschkonzept', 'dsfa',
|
||||
'consent_texts', 'social_media_dsi', 'video_conference_dsi',
|
||||
'dsr_process_art15', 'dsr_process_art17', 'dsr_process_art20', 'dsr_process_art21',
|
||||
'employee_dsi', 'applicant_dsi',
|
||||
'ai_usage_policy',
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Company Profile Presets — Branchenvorlagen fuer typische Kundenszenarien
|
||||
*
|
||||
* Jeder Preset enthaelt ein vorbefuelltes CompanyProfile + typische Scope-Antworten.
|
||||
* Der Kunde waehlt beim Onboarding ein Profil und passt es dann an.
|
||||
*
|
||||
* Data split: Interface here, preset data in ./company-profile-preset-data.ts
|
||||
*/
|
||||
|
||||
export interface CompanyProfilePreset {
|
||||
id: string
|
||||
label: string
|
||||
description: string
|
||||
icon: string
|
||||
/** Vorbefuellte CompanyProfile-Felder */
|
||||
profile: {
|
||||
legalForm: string
|
||||
industry: string[]
|
||||
businessModel: string
|
||||
companySize: string
|
||||
employeeCount: string
|
||||
headquartersCountry: string
|
||||
targetMarkets: string[]
|
||||
isDataController: boolean
|
||||
isDataProcessor: boolean
|
||||
}
|
||||
/** Typische Scope-Antworten fuer diese Branche */
|
||||
scopeHints: Record<string, string>
|
||||
/** Typische Dokumente die diese Branche braucht */
|
||||
recommendedDocs: string[]
|
||||
}
|
||||
|
||||
export { COMPANY_PROFILE_PRESETS } from './company-profile-preset-data'
|
||||
@@ -52,6 +52,15 @@ export const QUESTION_SCORE_WEIGHTS: Record<
|
||||
comp_training: { risk: 5, complexity: 4, assurance: 7 },
|
||||
comp_vendor_management: { risk: 6, complexity: 6, assurance: 7 },
|
||||
comp_documentation_level: { risk: 6, complexity: 7, assurance: 8 },
|
||||
|
||||
// Zusaetzliche Fragen fuer Template-Empfehlungen (7 Fragen)
|
||||
org_has_employees: { risk: 2, complexity: 3, assurance: 3 },
|
||||
org_has_social_media: { risk: 3, complexity: 2, assurance: 3 },
|
||||
org_has_video_conferencing: { risk: 2, complexity: 2, assurance: 2 },
|
||||
proc_uses_ai_tools: { risk: 7, complexity: 6, assurance: 7 },
|
||||
proc_byod_allowed: { risk: 5, complexity: 4, assurance: 5 },
|
||||
prod_ugc_platform: { risk: 6, complexity: 5, assurance: 6 },
|
||||
org_cert_iso27001: { risk: 2, complexity: 8, assurance: 9 },
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* DSFA Bundesland-Blacklists — Verarbeitungen die IMMER eine DSFA erfordern.
|
||||
*
|
||||
* Jede Aufsichtsbehoerde fuehrt eine eigene Positiv-/Negativliste
|
||||
* gemaess Art. 35 Abs. 4 DSGVO. Diese Listen definieren Verarbeitungen
|
||||
* die UNABHAENGIG von der Schwellwertanalyse eine DSFA erfordern.
|
||||
*
|
||||
* Quellen: Offizielle Muss-Listen der LfDI/BfDI (oeffentlich zugaenglich).
|
||||
* KEIN Normtext — eigene Zusammenfassung der Kriterien.
|
||||
*/
|
||||
|
||||
export interface BlacklistEntry {
|
||||
id: string
|
||||
description: string
|
||||
triggerKeywords: string[]
|
||||
}
|
||||
|
||||
export interface BundeslandBlacklist {
|
||||
state: string
|
||||
stateCode: string
|
||||
authority: string
|
||||
authorityUrl: string
|
||||
entries: BlacklistEntry[]
|
||||
}
|
||||
|
||||
// Gemeinsame Kriterien die in ALLEN Bundeslaendern gelten (DSK-Liste)
|
||||
const DSK_COMMON: BlacklistEntry[] = [
|
||||
{ id: 'dsk-01', description: 'Umfangreiche Verarbeitung besonderer Kategorien (Art. 9)', triggerKeywords: ['art9', 'gesundheit', 'biometrie', 'genetik', 'religion', 'gewerkschaft'] },
|
||||
{ id: 'dsk-02', description: 'Systematische umfangreiche Ueberwachung oeffentlicher Bereiche', triggerKeywords: ['videoueberwachung', 'kamera', 'oeffentlich', 'ueberwachung'] },
|
||||
{ id: 'dsk-03', description: 'Scoring/Profiling mit rechtlicher Wirkung', triggerKeywords: ['scoring', 'profiling', 'bonitaet', 'kredit', 'automatisiert'] },
|
||||
{ id: 'dsk-04', description: 'Verarbeitung von Daten Minderjaehriger fuer Marketing/Profiling', triggerKeywords: ['minderjaehrig', 'kinder', 'schueler', 'marketing'] },
|
||||
{ id: 'dsk-05', description: 'Zusammenfuehrung von Daten aus verschiedenen Quellen', triggerKeywords: ['zusammenfuehrung', 'matching', 'datenfusion', 'big data'] },
|
||||
{ id: 'dsk-06', description: 'Einsatz neuer Technologien (KI, Biometrie, IoT)', triggerKeywords: ['ki', 'kuenstliche intelligenz', 'biometrie', 'iot', 'gesichtserkennung'] },
|
||||
{ id: 'dsk-07', description: 'Umfangreiche Verarbeitung von Standortdaten', triggerKeywords: ['standort', 'gps', 'tracking', 'bewegungsprofil'] },
|
||||
{ id: 'dsk-08', description: 'Verarbeitung von Beschaeftigtendaten mit Ueberwachungscharakter', triggerKeywords: ['mitarbeiterueberwachung', 'keylogger', 'bildschirmaufnahme', 'leistungskontrolle'] },
|
||||
{ id: 'dsk-09', description: 'Anonymisierung besonderer Kategorien', triggerKeywords: ['anonymisierung', 'art9', 'pseudonymisierung'] },
|
||||
{ id: 'dsk-10', description: 'Verarbeitung von Kommunikationsinhalten oder -metadaten', triggerKeywords: ['kommunikation', 'email', 'telefon', 'metadaten', 'inhaltsdaten'] },
|
||||
]
|
||||
|
||||
// Bundesland-spezifische Ergaenzungen
|
||||
const BAYERN_EXTRA: BlacklistEntry[] = [
|
||||
{ id: 'by-01', description: 'Betrieb von Whistleblower-Systemen mit Identifizierungsrisiko', triggerKeywords: ['whistleblower', 'hinweisgeber', 'meldesystem'] },
|
||||
{ id: 'by-02', description: 'Einsatz von Drohnen mit Kamera in oeffentlichen Bereichen', triggerKeywords: ['drohne', 'uav', 'luftaufnahme'] },
|
||||
]
|
||||
|
||||
const NRW_EXTRA: BlacklistEntry[] = [
|
||||
{ id: 'nw-01', description: 'Social-Media-Monitoring von Mitarbeitern oder Bewerbern', triggerKeywords: ['social media', 'monitoring', 'bewerber', 'hintergrundcheck'] },
|
||||
]
|
||||
|
||||
const BERLIN_EXTRA: BlacklistEntry[] = [
|
||||
{ id: 'be-01', description: 'Automatisierte Mieterbonitaetspruefung', triggerKeywords: ['mieter', 'bonitaet', 'wohnung', 'schufa'] },
|
||||
]
|
||||
|
||||
export const BUNDESLAND_BLACKLISTS: Record<string, BundeslandBlacklist> = {
|
||||
BW: { state: 'Baden-Wuerttemberg', stateCode: 'BW', authority: 'LfDI BW', authorityUrl: 'https://www.baden-wuerttemberg.datenschutz.de', entries: [...DSK_COMMON] },
|
||||
BY: { state: 'Bayern', stateCode: 'BY', authority: 'BayLDA', authorityUrl: 'https://www.lda.bayern.de', entries: [...DSK_COMMON, ...BAYERN_EXTRA] },
|
||||
BE: { state: 'Berlin', stateCode: 'BE', authority: 'BlnBDI', authorityUrl: 'https://www.datenschutz-berlin.de', entries: [...DSK_COMMON, ...BERLIN_EXTRA] },
|
||||
BB: { state: 'Brandenburg', stateCode: 'BB', authority: 'LDA BB', authorityUrl: 'https://www.lda.brandenburg.de', entries: [...DSK_COMMON] },
|
||||
HB: { state: 'Bremen', stateCode: 'HB', authority: 'LfDI HB', authorityUrl: 'https://www.datenschutz.bremen.de', entries: [...DSK_COMMON] },
|
||||
HH: { state: 'Hamburg', stateCode: 'HH', authority: 'HmbBfDI', authorityUrl: 'https://datenschutz-hamburg.de', entries: [...DSK_COMMON] },
|
||||
HE: { state: 'Hessen', stateCode: 'HE', authority: 'HBDI', authorityUrl: 'https://datenschutz.hessen.de', entries: [...DSK_COMMON] },
|
||||
MV: { state: 'Mecklenburg-Vorpommern', stateCode: 'MV', authority: 'LfDI MV', authorityUrl: 'https://www.datenschutz-mv.de', entries: [...DSK_COMMON] },
|
||||
NI: { state: 'Niedersachsen', stateCode: 'NI', authority: 'LfD NI', authorityUrl: 'https://lfd.niedersachsen.de', entries: [...DSK_COMMON] },
|
||||
NW: { state: 'Nordrhein-Westfalen', stateCode: 'NW', authority: 'LDI NRW', authorityUrl: 'https://www.ldi.nrw.de', entries: [...DSK_COMMON, ...NRW_EXTRA] },
|
||||
RP: { state: 'Rheinland-Pfalz', stateCode: 'RP', authority: 'LfDI RP', authorityUrl: 'https://www.datenschutz.rlp.de', entries: [...DSK_COMMON] },
|
||||
SL: { state: 'Saarland', stateCode: 'SL', authority: 'UDZ Saarland', authorityUrl: 'https://www.datenschutz.saarland.de', entries: [...DSK_COMMON] },
|
||||
SN: { state: 'Sachsen', stateCode: 'SN', authority: 'SDB', authorityUrl: 'https://www.saechsdsb.de', entries: [...DSK_COMMON] },
|
||||
ST: { state: 'Sachsen-Anhalt', stateCode: 'ST', authority: 'LfD LSA', authorityUrl: 'https://datenschutz.sachsen-anhalt.de', entries: [...DSK_COMMON] },
|
||||
SH: { state: 'Schleswig-Holstein', stateCode: 'SH', authority: 'ULD SH', authorityUrl: 'https://www.datenschutzzentrum.de', entries: [...DSK_COMMON] },
|
||||
TH: { state: 'Thueringen', stateCode: 'TH', authority: 'TLfDI', authorityUrl: 'https://www.tlfdi.de', entries: [...DSK_COMMON] },
|
||||
}
|
||||
|
||||
/**
|
||||
* Check scope answers against Bundesland blacklist.
|
||||
* Returns matching entries that REQUIRE a DSFA.
|
||||
*/
|
||||
export function checkBlacklist(
|
||||
stateCode: string,
|
||||
scopeKeywords: string[],
|
||||
): { matches: BlacklistEntry[]; authority: string; authorityUrl: string } {
|
||||
const bl = BUNDESLAND_BLACKLISTS[stateCode]
|
||||
if (!bl) return { matches: [], authority: 'BfDI', authorityUrl: 'https://www.bfdi.bund.de' }
|
||||
|
||||
const lowerKeywords = scopeKeywords.map(k => k.toLowerCase())
|
||||
const matches = bl.entries.filter(entry =>
|
||||
entry.triggerKeywords.some(tk => lowerKeywords.some(kw => kw.includes(tk) || tk.includes(kw)))
|
||||
)
|
||||
|
||||
return { matches, authority: bl.authority, authorityUrl: bl.authorityUrl }
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive keywords from scope answers for blacklist matching.
|
||||
*/
|
||||
export function scopeAnswersToKeywords(answers: Array<{ questionId: string; value: unknown }>): string[] {
|
||||
const keywords: string[] = []
|
||||
for (const a of answers) {
|
||||
if (a.value === true || a.value === 'yes') {
|
||||
keywords.push(a.questionId.replace(/_/g, ' '))
|
||||
// Map specific question IDs to keywords
|
||||
if (a.questionId === 'data_art9') keywords.push('art9', 'besondere kategorien')
|
||||
if (a.questionId === 'data_minors') keywords.push('minderjaehrig', 'kinder')
|
||||
if (a.questionId === 'proc_adm_scoring') keywords.push('scoring', 'profiling', 'automatisiert')
|
||||
if (a.questionId === 'proc_video_surveillance') keywords.push('videoueberwachung', 'kamera')
|
||||
if (a.questionId === 'proc_ai_usage') keywords.push('ki', 'kuenstliche intelligenz')
|
||||
if (a.questionId === 'proc_employee_monitoring') keywords.push('mitarbeiterueberwachung')
|
||||
}
|
||||
if (Array.isArray(a.value)) {
|
||||
for (const v of a.value) keywords.push(String(v).toLowerCase())
|
||||
}
|
||||
}
|
||||
return keywords
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* DSFA Pre-Fill — derives initial DSFA data from Company Profile + Scope answers.
|
||||
*
|
||||
* Maps: Firmensitz → Bundesland, Scope-Antworten → Datenkategorien/Risiken,
|
||||
* Use Cases → Verarbeitungstaetigkeiten, DPO → Beratungsinformationen.
|
||||
*/
|
||||
|
||||
import type { ScopeProfilingAnswer } from '@/lib/sdk/compliance-scope-types'
|
||||
|
||||
interface CompanyProfileMinimal {
|
||||
headquartersState?: string
|
||||
industry?: string[]
|
||||
businessModel?: string
|
||||
dpoName?: string | null
|
||||
dpoEmail?: string | null
|
||||
companyName?: string
|
||||
}
|
||||
|
||||
export interface DSFAPrefillResult {
|
||||
title: string
|
||||
description: string
|
||||
processingActivity: string
|
||||
dataCategories: string[]
|
||||
dataSubjects: string[]
|
||||
riskLevel: string
|
||||
measures: string[]
|
||||
federalState: string
|
||||
involvesAi: boolean
|
||||
legalBasis: string
|
||||
processingPurpose: string
|
||||
affectedRights: string[]
|
||||
}
|
||||
|
||||
const ART9_CATEGORY_MAP: Record<string, string> = {
|
||||
health: 'Gesundheitsdaten',
|
||||
biometric: 'Biometrische Daten',
|
||||
genetic: 'Genetische Daten',
|
||||
ethnic: 'Ethnische Herkunft',
|
||||
political: 'Politische Meinungen',
|
||||
religious: 'Religioese Ueberzeugungen',
|
||||
union: 'Gewerkschaftszugehoerigkeit',
|
||||
sexual: 'Sexualleben/Orientierung',
|
||||
}
|
||||
|
||||
const BUNDESLAND_LABELS: Record<string, string> = {
|
||||
BW: 'Baden-Wuerttemberg', BY: 'Bayern', BE: 'Berlin', BB: 'Brandenburg',
|
||||
HB: 'Bremen', HH: 'Hamburg', HE: 'Hessen', MV: 'Mecklenburg-Vorpommern',
|
||||
NI: 'Niedersachsen', NW: 'Nordrhein-Westfalen', RP: 'Rheinland-Pfalz',
|
||||
SL: 'Saarland', SN: 'Sachsen', ST: 'Sachsen-Anhalt',
|
||||
SH: 'Schleswig-Holstein', TH: 'Thueringen',
|
||||
}
|
||||
|
||||
function getAnswer(answers: ScopeProfilingAnswer[], questionId: string): string | string[] | boolean | undefined {
|
||||
const a = answers.find(a => a.questionId === questionId)
|
||||
return a?.value as string | string[] | boolean | undefined
|
||||
}
|
||||
|
||||
export function prefillDSFAFromScope(
|
||||
profile: CompanyProfileMinimal | null,
|
||||
scopeAnswers: ScopeProfilingAnswer[],
|
||||
): DSFAPrefillResult {
|
||||
const result: DSFAPrefillResult = {
|
||||
title: '',
|
||||
description: '',
|
||||
processingActivity: '',
|
||||
dataCategories: [],
|
||||
dataSubjects: [],
|
||||
riskLevel: 'mittel',
|
||||
measures: ['Zugriffskontrolle', 'Verschluesselung'],
|
||||
federalState: '',
|
||||
involvesAi: false,
|
||||
legalBasis: '',
|
||||
processingPurpose: '',
|
||||
affectedRights: [],
|
||||
}
|
||||
|
||||
// 1. Firmensitz → Bundesland
|
||||
if (profile?.headquartersState) {
|
||||
result.federalState = profile.headquartersState
|
||||
}
|
||||
|
||||
// 2. Art. 9 Daten → Datenkategorien + Risikostufe
|
||||
const art9 = getAnswer(scopeAnswers, 'data_art9')
|
||||
if (art9 === true || art9 === 'yes') {
|
||||
result.dataCategories.push('Besondere Kategorien (Art. 9)')
|
||||
result.riskLevel = 'hoch'
|
||||
result.title = 'DSFA — Verarbeitung besonderer Datenkategorien'
|
||||
}
|
||||
if (Array.isArray(art9)) {
|
||||
for (const cat of art9) {
|
||||
const label = ART9_CATEGORY_MAP[cat]
|
||||
if (label) result.dataCategories.push(label)
|
||||
}
|
||||
if (art9.length > 0) result.riskLevel = 'hoch'
|
||||
}
|
||||
|
||||
// 3. Minderjährige → Betroffene + Risiko
|
||||
const minors = getAnswer(scopeAnswers, 'data_minors')
|
||||
if (minors === true || minors === 'yes') {
|
||||
result.dataSubjects.push('Minderjaehrige (unter 16 Jahre)')
|
||||
result.riskLevel = 'hoch'
|
||||
result.affectedRights.push('Besonderer Schutz Minderjaehriger (Art. 8 DSGVO)')
|
||||
if (!result.title) result.title = 'DSFA — Verarbeitung von Daten Minderjaehriger'
|
||||
}
|
||||
|
||||
// 4. Automatisierte Entscheidungen (Scoring)
|
||||
const scoring = getAnswer(scopeAnswers, 'proc_adm_scoring')
|
||||
if (scoring === true || scoring === 'yes') {
|
||||
result.affectedRights.push('Recht auf nicht-automatisierte Entscheidung (Art. 22 DSGVO)')
|
||||
result.riskLevel = 'hoch'
|
||||
if (!result.title) result.title = 'DSFA — Automatisierte Einzelentscheidungen'
|
||||
result.measures.push('Menschliche Pruefung')
|
||||
}
|
||||
|
||||
// 5. KI-Einsatz
|
||||
const aiUsage = getAnswer(scopeAnswers, 'proc_ai_usage')
|
||||
if (aiUsage === true || aiUsage === 'yes' || (Array.isArray(aiUsage) && aiUsage.length > 0)) {
|
||||
result.involvesAi = true
|
||||
result.measures.push('KI-Transparenz', 'Human Oversight')
|
||||
if (!result.title) result.title = 'DSFA — KI-gestuetzte Datenverarbeitung'
|
||||
}
|
||||
|
||||
// 6. Videoueberwachung
|
||||
const video = getAnswer(scopeAnswers, 'proc_video_surveillance')
|
||||
if (video === true || video === 'yes') {
|
||||
result.dataCategories.push('Videoaufnahmen / Bilddaten')
|
||||
result.dataSubjects.push('Besucher', 'Mitarbeiter')
|
||||
if (!result.title) result.title = 'DSFA — Videoueberwachung'
|
||||
}
|
||||
|
||||
// 7. Datenvolumen
|
||||
const volume = getAnswer(scopeAnswers, 'data_volume')
|
||||
if (volume === '100000-1000000' || volume === '>1000000') {
|
||||
result.riskLevel = 'hoch'
|
||||
result.description += 'Grosse Datenmengen erhoehen das Risiko fuer Betroffene. '
|
||||
}
|
||||
|
||||
// 8. Branche + Geschaeftsmodell → Verarbeitungszweck
|
||||
if (profile?.industry?.length) {
|
||||
const ind = profile.industry[0]
|
||||
const purposeMap: Record<string, string> = {
|
||||
healthcare: 'Patientenversorgung und Gesundheitsdatenverarbeitung',
|
||||
finance: 'Finanzdienstleistungen und Bonitaetspruefung',
|
||||
education: 'Bildungsverwaltung und Schuelerbetreuung',
|
||||
tech: 'Software-Entwicklung und Cloud-Dienste',
|
||||
retail: 'Handel und Kundenbeziehungsmanagement',
|
||||
legal: 'Mandatsbearbeitung und Rechtsberatung',
|
||||
}
|
||||
result.processingPurpose = purposeMap[ind] || ''
|
||||
result.processingActivity = purposeMap[ind] || ''
|
||||
}
|
||||
|
||||
if (profile?.businessModel === 'b2c') {
|
||||
result.dataSubjects.push('Endverbraucher')
|
||||
result.legalBasis = 'Art. 6 Abs. 1 lit. b DSGVO (Vertragserfuellung)'
|
||||
} else if (profile?.businessModel === 'b2b') {
|
||||
result.dataSubjects.push('Geschaeftskunden', 'Ansprechpartner')
|
||||
result.legalBasis = 'Art. 6 Abs. 1 lit. f DSGVO (Berechtigtes Interesse)'
|
||||
}
|
||||
|
||||
// Deduplicate
|
||||
result.dataCategories = [...new Set(result.dataCategories)]
|
||||
result.dataSubjects = [...new Set(result.dataSubjects)]
|
||||
result.measures = [...new Set(result.measures)]
|
||||
result.affectedRights = [...new Set(result.affectedRights)]
|
||||
|
||||
// Default title if nothing triggered
|
||||
if (!result.title) {
|
||||
result.title = `DSFA — ${profile?.companyName || 'Datenverarbeitung'}`
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if DSFA is required based on scope answers (Art. 35 Abs. 3 triggers)
|
||||
* AND Bundesland-specific blacklist (Art. 35 Abs. 4).
|
||||
*/
|
||||
export function isDSFARequired(
|
||||
scopeAnswers: ScopeProfilingAnswer[],
|
||||
headquartersState?: string,
|
||||
): {
|
||||
required: boolean
|
||||
triggers: string[]
|
||||
blacklistMatches: string[]
|
||||
authority?: string
|
||||
} {
|
||||
const triggers: string[] = []
|
||||
|
||||
if (getAnswer(scopeAnswers, 'data_art9') === true || getAnswer(scopeAnswers, 'data_art9') === 'yes') {
|
||||
triggers.push('Besondere Datenkategorien (Art. 9 DSGVO)')
|
||||
}
|
||||
if (getAnswer(scopeAnswers, 'data_minors') === true || getAnswer(scopeAnswers, 'data_minors') === 'yes') {
|
||||
triggers.push('Daten Minderjaehriger (Art. 8 DSGVO)')
|
||||
}
|
||||
if (getAnswer(scopeAnswers, 'proc_adm_scoring') === true || getAnswer(scopeAnswers, 'proc_adm_scoring') === 'yes') {
|
||||
triggers.push('Automatisierte Einzelentscheidungen (Art. 22 DSGVO)')
|
||||
}
|
||||
if (getAnswer(scopeAnswers, 'proc_video_surveillance') === true || getAnswer(scopeAnswers, 'proc_video_surveillance') === 'yes') {
|
||||
triggers.push('Systematische Ueberwachung (Art. 35 Abs. 3 lit. c)')
|
||||
}
|
||||
const vol = getAnswer(scopeAnswers, 'data_volume')
|
||||
if (vol === '100000-1000000' || vol === '>1000000') {
|
||||
triggers.push('Umfangreiche Datenverarbeitung (Art. 35 Abs. 3 lit. b)')
|
||||
}
|
||||
|
||||
// Bundesland-Blacklist (Art. 35 Abs. 4)
|
||||
let blacklistMatches: string[] = []
|
||||
let authority: string | undefined
|
||||
if (headquartersState) {
|
||||
const { checkBlacklist, scopeAnswersToKeywords } = require('./bundesland-blacklists')
|
||||
const keywords = scopeAnswersToKeywords(scopeAnswers)
|
||||
const result = checkBlacklist(headquartersState, keywords)
|
||||
blacklistMatches = result.matches.map((m: { description: string }) => m.description)
|
||||
authority = result.authority
|
||||
}
|
||||
|
||||
return {
|
||||
required: triggers.length > 0 || blacklistMatches.length > 0,
|
||||
triggers,
|
||||
blacklistMatches,
|
||||
authority,
|
||||
}
|
||||
}
|
||||
@@ -245,13 +245,16 @@ function generateHTML(config: CookieBannerConfig, privacyPolicyUrl: string): str
|
||||
const categoriesHTML = config.categories
|
||||
.map((cat) => {
|
||||
const isRequired = cat.isRequired
|
||||
// COMPLIANCE: Only "required" categories may be pre-enabled (EuGH Planet49)
|
||||
// Non-required categories must NEVER be defaultEnabled
|
||||
const isEnabled = isRequired ? true : false
|
||||
return `
|
||||
<div class="cookie-banner-category" data-category="${cat.id}">
|
||||
<div class="cookie-banner-category-info">
|
||||
<div class="cookie-banner-category-name">${cat.name.de}</div>
|
||||
<div class="cookie-banner-category-desc">${cat.description.de}</div>
|
||||
</div>
|
||||
<div class="cookie-banner-toggle ${cat.defaultEnabled ? 'active' : ''} ${isRequired ? 'disabled' : ''}"
|
||||
<div class="cookie-banner-toggle ${isEnabled ? 'active' : ''} ${isRequired ? 'disabled' : ''}"
|
||||
data-category="${cat.id}"
|
||||
data-required="${isRequired}"></div>
|
||||
</div>
|
||||
@@ -286,10 +289,22 @@ function generateHTML(config: CookieBannerConfig, privacyPolicyUrl: string): str
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="${privacyPolicyUrl}" class="cookie-banner-link" target="_blank">
|
||||
${config.texts.privacyPolicyLink.de}
|
||||
</a>
|
||||
<div class="cookie-banner-links">
|
||||
<a href="${privacyPolicyUrl}" class="cookie-banner-link" target="_blank">
|
||||
${config.texts.privacyPolicyLink.de}
|
||||
</a>
|
||||
<a href="${config.impressumUrl || '/impressum'}" class="cookie-banner-link" target="_blank">
|
||||
Impressum
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cookie Settings Re-Open (§7(3) DSGVO — Widerruf so einfach wie Einwilligung) -->
|
||||
<a href="#" id="cookieBannerReopen" class="cookie-settings-footer-link"
|
||||
onclick="document.getElementById('cookieBanner').style.display='block';document.getElementById('cookieBannerOverlay').classList.add('active');return false;"
|
||||
style="position:fixed;bottom:8px;left:8px;z-index:9990;font-size:11px;color:#6b7280;text-decoration:none;background:rgba(255,255,255,0.9);padding:4px 8px;border-radius:4px;border:1px solid #e5e7eb;">
|
||||
Cookie-Einstellungen
|
||||
</a>
|
||||
`.trim()
|
||||
}
|
||||
|
||||
@@ -310,6 +325,29 @@ function generateJS(config: CookieBannerConfig): string {
|
||||
const CATEGORIES = ${JSON.stringify(categoryIds)};
|
||||
const REQUIRED_CATEGORIES = ${JSON.stringify(requiredCategories)};
|
||||
|
||||
// Google Consent Mode v2 — PFLICHT seit Maerz 2024 fuer Google Services in EEA
|
||||
// Sets default consent state to "denied" BEFORE any Google tags fire
|
||||
if (typeof gtag === 'function') {
|
||||
gtag('consent', 'default', {
|
||||
analytics_storage: 'denied',
|
||||
ad_storage: 'denied',
|
||||
ad_user_data: 'denied',
|
||||
ad_personalization: 'denied',
|
||||
functionality_storage: 'granted',
|
||||
security_storage: 'granted',
|
||||
});
|
||||
}
|
||||
|
||||
function updateGoogleConsentMode(consent) {
|
||||
if (typeof gtag !== 'function') return;
|
||||
gtag('consent', 'update', {
|
||||
analytics_storage: consent.statistics ? 'granted' : 'denied',
|
||||
ad_storage: consent.marketing ? 'granted' : 'denied',
|
||||
ad_user_data: consent.marketing ? 'granted' : 'denied',
|
||||
ad_personalization: consent.marketing ? 'granted' : 'denied',
|
||||
});
|
||||
}
|
||||
|
||||
function getConsent() {
|
||||
const cookie = document.cookie.split('; ').find(row => row.startsWith(COOKIE_NAME + '='));
|
||||
if (!cookie) return null;
|
||||
@@ -327,6 +365,7 @@ function generateJS(config: CookieBannerConfig): string {
|
||||
';expires=' + date.toUTCString() +
|
||||
';path=/;SameSite=Lax';
|
||||
window.dispatchEvent(new CustomEvent('cookieConsentUpdated', { detail: consent }));
|
||||
updateGoogleConsentMode(consent);
|
||||
}
|
||||
|
||||
function hasConsent(category) {
|
||||
@@ -397,6 +436,31 @@ function generateJS(config: CookieBannerConfig): string {
|
||||
overlay?.classList.remove('active');
|
||||
}
|
||||
|
||||
// Script-Blocking: activate scripts with data-cookie-category ONLY after consent
|
||||
function activateConsentedScripts() {
|
||||
const consent = getConsent();
|
||||
if (!consent) return;
|
||||
|
||||
// Find all blocked scripts (type="text/plain" with data-cookie-category)
|
||||
document.querySelectorAll('script[data-cookie-category][type="text/plain"]').forEach(script => {
|
||||
const category = script.getAttribute('data-cookie-category');
|
||||
if (consent[category] === true) {
|
||||
// Replace type to activate the script
|
||||
const newScript = document.createElement('script');
|
||||
if (script.src) newScript.src = script.src;
|
||||
else newScript.textContent = script.textContent;
|
||||
newScript.type = 'text/javascript';
|
||||
script.parentNode.replaceChild(newScript, script);
|
||||
}
|
||||
});
|
||||
|
||||
// Also fire custom event for programmatic listeners
|
||||
window.dispatchEvent(new CustomEvent('cookieConsentActivated', { detail: consent }));
|
||||
}
|
||||
|
||||
// Run script activation after consent is saved
|
||||
window.addEventListener('cookieConsentUpdated', activateConsentedScripts);
|
||||
|
||||
window.CookieConsent = {
|
||||
getConsent,
|
||||
saveConsent,
|
||||
@@ -405,14 +469,32 @@ function generateJS(config: CookieBannerConfig): string {
|
||||
document.getElementById('cookieBanner')?.classList.add('active');
|
||||
document.getElementById('cookieBannerOverlay')?.classList.add('active');
|
||||
},
|
||||
hide: closeBanner
|
||||
hide: closeBanner,
|
||||
activateScripts: activateConsentedScripts,
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initBanner);
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initBanner();
|
||||
activateConsentedScripts();
|
||||
});
|
||||
} else {
|
||||
initBanner();
|
||||
activateConsentedScripts();
|
||||
}
|
||||
})();
|
||||
|
||||
/*
|
||||
* USAGE: Script-Blocking
|
||||
*
|
||||
* Instead of:
|
||||
* <script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX"></script>
|
||||
*
|
||||
* Use:
|
||||
* <script type="text/plain" data-cookie-category="statistics"
|
||||
* src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX"></script>
|
||||
*
|
||||
* The script will only execute AFTER the user consents to "statistics".
|
||||
*/
|
||||
`.trim()
|
||||
}
|
||||
|
||||
@@ -328,7 +328,8 @@ export function isPolicyOverdue(policy: LoeschfristPolicy): boolean {
|
||||
}
|
||||
|
||||
export function getActiveLegalHolds(policy: LoeschfristPolicy): LegalHold[] {
|
||||
return policy.legalHolds.filter(h => h.status === 'ACTIVE')
|
||||
const holds = Array.isArray(policy.legalHolds) ? policy.legalHolds : []
|
||||
return holds.filter(h => h.status === 'ACTIVE')
|
||||
}
|
||||
|
||||
export function getEffectiveDeletionTrigger(policy: LoeschfristPolicy): DeletionTriggerLevel {
|
||||
|
||||
@@ -85,6 +85,8 @@ export type TemplateType =
|
||||
| 'tom_documentation'
|
||||
| 'loeschkonzept'
|
||||
| 'pflichtenregister'
|
||||
// SOP (Migration 112)
|
||||
| 'standard_operating_procedure'
|
||||
|
||||
export type Jurisdiction = 'DE' | 'AT' | 'CH' | 'EU' | 'US' | 'INTL'
|
||||
|
||||
|
||||
@@ -57,6 +57,19 @@ export const SDK_STEPS: SDKStep[] = [
|
||||
isOptional: true,
|
||||
visibleWhen: (state) => state.customerType === 'existing',
|
||||
},
|
||||
{
|
||||
id: 'ai-act',
|
||||
seq: 350,
|
||||
phase: 1,
|
||||
package: 'vorbereitung',
|
||||
order: 4,
|
||||
name: 'AI Act Klassifizierung',
|
||||
nameShort: 'AI Act',
|
||||
description: 'KI-Risikostufe (nur bei KI-Einsatz)',
|
||||
url: '/sdk/ai-act',
|
||||
checkpointId: 'CP-AI',
|
||||
prerequisiteSteps: ['use-case-assessment'],
|
||||
isOptional: true },
|
||||
{
|
||||
id: 'screening',
|
||||
seq: 500,
|
||||
@@ -65,39 +78,27 @@ export const SDK_STEPS: SDKStep[] = [
|
||||
order: 5,
|
||||
name: 'System Screening',
|
||||
nameShort: 'Screening',
|
||||
description: 'SBOM + Security Check',
|
||||
description: 'SBOM + Vulnerability Scan (OSV.dev)',
|
||||
url: '/sdk/screening',
|
||||
checkpointId: 'CP-SCAN',
|
||||
prerequisiteSteps: ['use-case-assessment'],
|
||||
isOptional: false },
|
||||
{
|
||||
id: 'modules',
|
||||
seq: 600,
|
||||
phase: 1,
|
||||
package: 'vorbereitung',
|
||||
order: 6,
|
||||
name: 'Compliance Modules',
|
||||
nameShort: 'Module',
|
||||
description: 'Abgleich welche Regulierungen gelten',
|
||||
url: '/sdk/modules',
|
||||
checkpointId: 'CP-MOD',
|
||||
prerequisiteSteps: ['screening'],
|
||||
isOptional: false },
|
||||
isOptional: true },
|
||||
// Modules entfernt — Regulierungen werden im Scope-Decision-Tab + Dashboard angezeigt
|
||||
{
|
||||
id: 'source-policy',
|
||||
seq: 700,
|
||||
phase: 1,
|
||||
package: 'vorbereitung',
|
||||
order: 7,
|
||||
name: 'Source Policy',
|
||||
name: 'Quellen-Verwaltung',
|
||||
nameShort: 'Quellen',
|
||||
description: 'Datenquellen-Governance & Whitelist',
|
||||
description: 'RAG Quellen-Whitelist (Enterprise)',
|
||||
url: '/sdk/source-policy',
|
||||
checkpointId: 'CP-SPOL',
|
||||
prerequisiteSteps: ['modules'],
|
||||
isOptional: false },
|
||||
prerequisiteSteps: ['use-case-assessment'],
|
||||
isOptional: true },
|
||||
|
||||
// PAKET 2: ANALYSE (Assessment)
|
||||
// PAKET 2: ANALYSE (Assessment) — Requirements → Controls → Risks → Checklist → Report
|
||||
{
|
||||
id: 'requirements',
|
||||
seq: 1000,
|
||||
@@ -106,10 +107,10 @@ export const SDK_STEPS: SDKStep[] = [
|
||||
order: 1,
|
||||
name: 'Requirements',
|
||||
nameShort: 'Anforderungen',
|
||||
description: 'Pr\u00fcfaspekte aus Regulierungen ableiten',
|
||||
description: 'Pruefaspekte aus Regulierungen ableiten',
|
||||
url: '/sdk/requirements',
|
||||
checkpointId: 'CP-REQ',
|
||||
prerequisiteSteps: ['source-policy'],
|
||||
prerequisiteSteps: ['compliance-scope'],
|
||||
isOptional: false },
|
||||
{
|
||||
id: 'controls',
|
||||
@@ -119,72 +120,46 @@ export const SDK_STEPS: SDKStep[] = [
|
||||
order: 2,
|
||||
name: 'Controls',
|
||||
nameShort: 'Controls',
|
||||
description: 'Erforderliche Ma\u00dfnahmen ermitteln',
|
||||
description: 'Technische & organisatorische Massnahmen',
|
||||
url: '/sdk/controls',
|
||||
checkpointId: 'CP-CTRL',
|
||||
prerequisiteSteps: ['requirements'],
|
||||
isOptional: false },
|
||||
{
|
||||
id: 'evidence',
|
||||
id: 'risks',
|
||||
seq: 1200,
|
||||
phase: 1,
|
||||
package: 'analyse',
|
||||
order: 3,
|
||||
name: 'Evidence',
|
||||
nameShort: 'Nachweise',
|
||||
description: 'Nachweise dokumentieren',
|
||||
url: '/sdk/evidence',
|
||||
checkpointId: 'CP-EVI',
|
||||
name: 'Risk Matrix',
|
||||
nameShort: 'Risiken',
|
||||
description: 'Risikobewertung — wo sind Luecken?',
|
||||
url: '/sdk/risks',
|
||||
checkpointId: 'CP-RISK',
|
||||
prerequisiteSteps: ['controls'],
|
||||
isOptional: false },
|
||||
{
|
||||
id: 'risks',
|
||||
id: 'audit-checklist',
|
||||
seq: 1300,
|
||||
phase: 1,
|
||||
package: 'analyse',
|
||||
order: 4,
|
||||
name: 'Risk Matrix',
|
||||
nameShort: 'Risiken',
|
||||
description: 'Risikobewertung & Residual Risk',
|
||||
url: '/sdk/risks',
|
||||
checkpointId: 'CP-RISK',
|
||||
prerequisiteSteps: ['evidence'],
|
||||
name: 'Audit Checklist',
|
||||
nameShort: 'Checklist',
|
||||
description: 'Pruefbare Checkliste generieren',
|
||||
url: '/sdk/audit-checklist',
|
||||
checkpointId: 'CP-CHK',
|
||||
prerequisiteSteps: ['risks'],
|
||||
isOptional: false },
|
||||
{
|
||||
id: 'ai-act',
|
||||
id: 'audit-report',
|
||||
seq: 1400,
|
||||
phase: 1,
|
||||
package: 'analyse',
|
||||
order: 5,
|
||||
name: 'AI Act Klassifizierung',
|
||||
nameShort: 'AI Act',
|
||||
description: 'Risikostufe nach EU AI Act',
|
||||
url: '/sdk/ai-act',
|
||||
checkpointId: 'CP-AI',
|
||||
prerequisiteSteps: ['risks'],
|
||||
isOptional: false },
|
||||
{
|
||||
id: 'audit-checklist',
|
||||
seq: 1500,
|
||||
phase: 1,
|
||||
package: 'analyse',
|
||||
order: 6,
|
||||
name: 'Audit Checklist',
|
||||
nameShort: 'Checklist',
|
||||
description: 'Pr\u00fcfliste generieren',
|
||||
url: '/sdk/audit-checklist',
|
||||
checkpointId: 'CP-CHK',
|
||||
prerequisiteSteps: ['ai-act'],
|
||||
isOptional: false },
|
||||
{
|
||||
id: 'audit-report',
|
||||
seq: 1600,
|
||||
phase: 1,
|
||||
package: 'analyse',
|
||||
order: 7,
|
||||
name: 'Audit Report',
|
||||
nameShort: 'Report',
|
||||
description: 'Audit-Sitzungen & PDF-Report',
|
||||
description: 'Zusammenfassender Audit-Report (PDF)',
|
||||
url: '/sdk/audit-report',
|
||||
checkpointId: 'CP-AREP',
|
||||
prerequisiteSteps: ['audit-checklist'],
|
||||
@@ -271,8 +246,8 @@ export const SDK_STEPS: SDKStep[] = [
|
||||
phase: 2,
|
||||
package: 'rechtliche-texte',
|
||||
order: 1,
|
||||
name: 'Einwilligungen',
|
||||
nameShort: 'Einwilligungen',
|
||||
name: 'Consent-Records',
|
||||
nameShort: 'Consent-Records',
|
||||
description: 'Datenpunktkatalog & DSI-Generator',
|
||||
url: '/sdk/einwilligungen',
|
||||
checkpointId: 'CP-CONS',
|
||||
@@ -334,6 +309,19 @@ export const SDK_STEPS: SDKStep[] = [
|
||||
isOptional: false },
|
||||
|
||||
// PAKET 5: BETRIEB (Operations)
|
||||
{
|
||||
id: 'evidence',
|
||||
seq: 3900,
|
||||
phase: 2,
|
||||
package: 'betrieb',
|
||||
order: 0,
|
||||
name: 'Evidence',
|
||||
nameShort: 'Nachweise',
|
||||
description: 'Nachweise laufend dokumentieren',
|
||||
url: '/sdk/evidence',
|
||||
checkpointId: 'CP-EVI',
|
||||
prerequisiteSteps: ['audit-report'],
|
||||
isOptional: false },
|
||||
{
|
||||
id: 'dsr',
|
||||
seq: 4000,
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* EU-Angemessenheitsbeschluesse (Art. 45 DSGVO)
|
||||
*
|
||||
* Laender mit Angemessenheitsbeschluss benoetigen KEINE SCC und KEIN TIA
|
||||
* fuer Datenuebermittlungen. Die Liste wird von der EU-Kommission gefuehrt.
|
||||
*
|
||||
* WICHTIG: USA hat Sonderstatus — Angemessenheit gilt NUR fuer Unternehmen,
|
||||
* die nach dem EU-US Data Privacy Framework (DPF) zertifiziert sind.
|
||||
* Nicht-zertifizierte US-Unternehmen brauchen weiterhin SCC + TIA.
|
||||
*
|
||||
* Quelle: https://commission.europa.eu/law/law-topic/data-protection/
|
||||
* international-dimension-data-protection/adequacy-decisions_en
|
||||
*/
|
||||
|
||||
export interface AdequacyDecision {
|
||||
/** ISO 3166-1 alpha-2 Laendercode */
|
||||
countryCode: string
|
||||
/** Laendername (deutsch) */
|
||||
countryName: string
|
||||
/** Jahr des Angemessenheitsbeschlusses */
|
||||
since: number
|
||||
/** Einschraenkungen (z.B. nur bestimmte Sektoren) */
|
||||
restriction?: string
|
||||
/** Befristet? */
|
||||
expires?: string
|
||||
/** Sonderstatus (z.B. DPF-Zertifizierung erforderlich) */
|
||||
requiresCertification?: boolean
|
||||
/** Name der erforderlichen Zertifizierung */
|
||||
certificationName?: string
|
||||
/** Pruef-URL fuer die Zertifizierung */
|
||||
certificationCheckUrl?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Vollstaendige Liste der Laender mit EU-Angemessenheitsbeschluss.
|
||||
* Stand: Mai 2026
|
||||
*/
|
||||
export const ADEQUACY_DECISIONS: AdequacyDecision[] = [
|
||||
{ countryCode: 'AD', countryName: 'Andorra', since: 2010 },
|
||||
{ countryCode: 'AR', countryName: 'Argentinien', since: 2003 },
|
||||
{ countryCode: 'FO', countryName: 'Faeroeer-Inseln', since: 2010 },
|
||||
{ countryCode: 'GG', countryName: 'Guernsey', since: 2003 },
|
||||
{ countryCode: 'IM', countryName: 'Isle of Man', since: 2004 },
|
||||
{ countryCode: 'IL', countryName: 'Israel', since: 2011 },
|
||||
{ countryCode: 'JP', countryName: 'Japan', since: 2019 },
|
||||
{ countryCode: 'JE', countryName: 'Jersey', since: 2008 },
|
||||
{
|
||||
countryCode: 'CA', countryName: 'Kanada', since: 2001,
|
||||
restriction: 'Nur Unternehmen, die dem Personal Information Protection and Electronic Documents Act (PIPEDA) unterliegen',
|
||||
},
|
||||
{ countryCode: 'NZ', countryName: 'Neuseeland', since: 2012 },
|
||||
{ countryCode: 'KR', countryName: 'Republik Korea (Suedkorea)', since: 2022 },
|
||||
{ countryCode: 'CH', countryName: 'Schweiz', since: 2000 },
|
||||
{
|
||||
countryCode: 'GB', countryName: 'Vereinigtes Koenigreich (UK)', since: 2021,
|
||||
expires: 'Befristet, verlaengert bis 2029',
|
||||
},
|
||||
{ countryCode: 'UY', countryName: 'Uruguay', since: 2012 },
|
||||
{
|
||||
countryCode: 'US', countryName: 'Vereinigte Staaten (USA)', since: 2023,
|
||||
restriction: 'Nur Unternehmen, die nach dem EU-US Data Privacy Framework (DPF) zertifiziert sind',
|
||||
requiresCertification: true,
|
||||
certificationName: 'EU-US Data Privacy Framework (DPF)',
|
||||
certificationCheckUrl: 'https://www.dataprivacyframework.gov/list',
|
||||
},
|
||||
]
|
||||
|
||||
/** Set der EU/EWR-Laender (kein Angemessenheitsbeschluss noetig) */
|
||||
export const EU_EEA_COUNTRIES = new Set([
|
||||
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR',
|
||||
'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL',
|
||||
'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE',
|
||||
// EWR (nicht EU, aber gleicher Datenschutzraum)
|
||||
'IS', 'LI', 'NO',
|
||||
])
|
||||
|
||||
/** Set der Laendercodes mit Angemessenheitsbeschluss */
|
||||
export const ADEQUATE_COUNTRIES = new Set(
|
||||
ADEQUACY_DECISIONS.map((d) => d.countryCode)
|
||||
)
|
||||
|
||||
/**
|
||||
* Prueft ob ein Land einen Angemessenheitsbeschluss hat.
|
||||
* Gibt das Decision-Objekt zurueck oder null.
|
||||
*/
|
||||
export function getAdequacyDecision(countryCode: string): AdequacyDecision | null {
|
||||
return ADEQUACY_DECISIONS.find((d) => d.countryCode === countryCode) || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestimmt den Transfer-Status fuer ein Land.
|
||||
*/
|
||||
export function getTransferRequirement(countryCode: string): {
|
||||
isEU: boolean
|
||||
isAdequate: boolean
|
||||
requiresSCC: boolean
|
||||
requiresTIA: boolean
|
||||
requiresCertification: boolean
|
||||
explanation: string
|
||||
} {
|
||||
if (EU_EEA_COUNTRIES.has(countryCode)) {
|
||||
return {
|
||||
isEU: true, isAdequate: true,
|
||||
requiresSCC: false, requiresTIA: false, requiresCertification: false,
|
||||
explanation: 'EU-/EWR-Mitgliedstaat — keine zusaetzlichen Massnahmen erforderlich.',
|
||||
}
|
||||
}
|
||||
|
||||
const decision = getAdequacyDecision(countryCode)
|
||||
if (decision) {
|
||||
if (decision.requiresCertification) {
|
||||
return {
|
||||
isEU: false, isAdequate: true,
|
||||
requiresSCC: false, requiresTIA: false, requiresCertification: true,
|
||||
explanation: `Angemessenheitsbeschluss seit ${decision.since}. ${decision.restriction || ''} Pruefung der Zertifizierung unter: ${decision.certificationCheckUrl || ''}`,
|
||||
}
|
||||
}
|
||||
return {
|
||||
isEU: false, isAdequate: true,
|
||||
requiresSCC: false, requiresTIA: false, requiresCertification: false,
|
||||
explanation: `Angemessenheitsbeschluss der EU-Kommission seit ${decision.since}.${decision.restriction ? ` Einschraenkung: ${decision.restriction}` : ''}${decision.expires ? ` (${decision.expires})` : ''}`,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isEU: false, isAdequate: false,
|
||||
requiresSCC: true, requiresTIA: true, requiresCertification: false,
|
||||
explanation: 'Kein Angemessenheitsbeschluss — EU-Standardvertragsklauseln (SCC) und Transfer Impact Assessment (TIA) erforderlich (Art. 46 Abs. 2 lit. c DSGVO, EuGH Schrems II).',
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,8 @@ import {
|
||||
// CONFIGURATION
|
||||
// =============================================================================
|
||||
|
||||
const WB_API_BASE = process.env.NEXT_PUBLIC_SDK_API_URL || 'http://localhost:8093'
|
||||
// Use compliance backend proxy (Python) instead of Go SDK
|
||||
const WB_API_BASE = '/api/sdk/v1/compliance'
|
||||
const API_TIMEOUT = 30000
|
||||
|
||||
// =============================================================================
|
||||
@@ -121,27 +122,27 @@ export async function fetchReports(filters?: ReportFilters): Promise<ReportListR
|
||||
}
|
||||
|
||||
const queryString = params.toString()
|
||||
const url = `${WB_API_BASE}/api/v1/admin/whistleblower/reports${queryString ? `?${queryString}` : ''}`
|
||||
const url = `${WB_API_BASE}/whistleblower/reports${queryString ? `?${queryString}` : ''}`
|
||||
|
||||
return fetchWithTimeout<ReportListResponse>(url)
|
||||
}
|
||||
|
||||
export async function fetchReport(id: string): Promise<WhistleblowerReport> {
|
||||
return fetchWithTimeout<WhistleblowerReport>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}`
|
||||
`${WB_API_BASE}/whistleblower/reports/${id}`
|
||||
)
|
||||
}
|
||||
|
||||
export async function updateReport(id: string, update: ReportUpdateRequest): Promise<WhistleblowerReport> {
|
||||
return fetchWithTimeout<WhistleblowerReport>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}`,
|
||||
`${WB_API_BASE}/whistleblower/reports/${id}`,
|
||||
{ method: 'PUT', body: JSON.stringify(update) }
|
||||
)
|
||||
}
|
||||
|
||||
export async function deleteReport(id: string): Promise<void> {
|
||||
await fetchWithTimeout<void>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}`,
|
||||
`${WB_API_BASE}/whistleblower/reports/${id}`,
|
||||
{ method: 'DELETE' }
|
||||
)
|
||||
}
|
||||
@@ -154,7 +155,7 @@ export async function submitPublicReport(
|
||||
data: PublicReportSubmission
|
||||
): Promise<{ report: WhistleblowerReport; accessKey: string }> {
|
||||
const response = await fetch(
|
||||
`${WB_API_BASE}/api/v1/public/whistleblower/submit`,
|
||||
`${WB_API_BASE}/whistleblower/submit`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -173,7 +174,7 @@ export async function fetchReportByAccessKey(
|
||||
accessKey: string
|
||||
): Promise<WhistleblowerReport> {
|
||||
const response = await fetch(
|
||||
`${WB_API_BASE}/api/v1/public/whistleblower/report/${accessKey}`,
|
||||
`${WB_API_BASE}/whistleblower/check/${accessKey}`,
|
||||
{ method: 'GET', headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
|
||||
@@ -190,14 +191,14 @@ export async function fetchReportByAccessKey(
|
||||
|
||||
export async function acknowledgeReport(id: string): Promise<WhistleblowerReport> {
|
||||
return fetchWithTimeout<WhistleblowerReport>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/acknowledge`,
|
||||
`${WB_API_BASE}/whistleblower/reports/${id}/acknowledge`,
|
||||
{ method: 'POST' }
|
||||
)
|
||||
}
|
||||
|
||||
export async function startInvestigation(id: string): Promise<WhistleblowerReport> {
|
||||
return fetchWithTimeout<WhistleblowerReport>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/investigate`,
|
||||
`${WB_API_BASE}/whistleblower/reports/${id}/investigate`,
|
||||
{ method: 'POST' }
|
||||
)
|
||||
}
|
||||
@@ -207,7 +208,7 @@ export async function addMeasure(
|
||||
measure: Omit<WhistleblowerMeasure, 'id' | 'reportId' | 'completedAt'>
|
||||
): Promise<WhistleblowerMeasure> {
|
||||
return fetchWithTimeout<WhistleblowerMeasure>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/measures`,
|
||||
`${WB_API_BASE}/whistleblower/reports/${id}/measures`,
|
||||
{ method: 'POST', body: JSON.stringify(measure) }
|
||||
)
|
||||
}
|
||||
@@ -217,7 +218,7 @@ export async function closeReport(
|
||||
resolution: { reason: string; notes: string }
|
||||
): Promise<WhistleblowerReport> {
|
||||
return fetchWithTimeout<WhistleblowerReport>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/close`,
|
||||
`${WB_API_BASE}/whistleblower/reports/${id}/close`,
|
||||
{ method: 'POST', body: JSON.stringify(resolution) }
|
||||
)
|
||||
}
|
||||
@@ -232,14 +233,14 @@ export async function sendMessage(
|
||||
role: 'reporter' | 'ombudsperson'
|
||||
): Promise<AnonymousMessage> {
|
||||
return fetchWithTimeout<AnonymousMessage>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${reportId}/messages`,
|
||||
`${WB_API_BASE}/whistleblower/reports/${reportId}/messages`,
|
||||
{ method: 'POST', body: JSON.stringify({ senderRole: role, message }) }
|
||||
)
|
||||
}
|
||||
|
||||
export async function fetchMessages(reportId: string): Promise<AnonymousMessage[]> {
|
||||
return fetchWithTimeout<AnonymousMessage[]>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${reportId}/messages`
|
||||
`${WB_API_BASE}/whistleblower/reports/${reportId}/messages`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -269,7 +270,7 @@ export async function uploadAttachment(
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${reportId}/attachments`,
|
||||
`${WB_API_BASE}/whistleblower/reports/${reportId}/attachments`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers,
|
||||
@@ -290,7 +291,7 @@ export async function uploadAttachment(
|
||||
|
||||
export async function deleteAttachment(id: string): Promise<void> {
|
||||
await fetchWithTimeout<void>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/attachments/${id}`,
|
||||
`${WB_API_BASE}/whistleblower/attachments/${id}`,
|
||||
{ method: 'DELETE' }
|
||||
)
|
||||
}
|
||||
@@ -301,6 +302,6 @@ export async function deleteAttachment(id: string): Promise<void> {
|
||||
|
||||
export async function fetchWhistleblowerStatistics(): Promise<WhistleblowerStatistics> {
|
||||
return fetchWithTimeout<WhistleblowerStatistics>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/statistics`
|
||||
`${WB_API_BASE}/whistleblower/reports/stats`
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user