obligations-document, tom-document, loeschfristen-document, compliance-scope-triggers, sdk-flow/flow-data, processing-activities, loeschfristen-baseline-catalog, catalog-registry, dsfa mitigation-library + risk-catalog, vvt-baseline-catalog, vendor contract-review checklists + findings, demo-data, tom-compliance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
191 lines
6.1 KiB
TypeScript
191 lines
6.1 KiB
TypeScript
/**
|
|
* Catalog Registry — Public API, Entry Converters & Registry Assembly
|
|
*/
|
|
|
|
import type {
|
|
CatalogId,
|
|
CatalogMeta,
|
|
CatalogModule,
|
|
CatalogEntry,
|
|
CatalogStats,
|
|
CatalogOverviewStats,
|
|
CustomCatalogEntry,
|
|
CustomCatalogs,
|
|
} from '../types'
|
|
import { DSFA_AI_CATALOG_META, DSFA_AI_SYSTEM_ENTRIES } from './registry-dsfa'
|
|
import { VENDOR_VVT_CATALOG_META, VENDOR_VVT_SYSTEM_ENTRIES } from './registry-vendor'
|
|
import { REFERENCE_CATALOG_META, REFERENCE_SYSTEM_ENTRIES } from './registry-reference'
|
|
|
|
// =============================================================================
|
|
// CATALOG REGISTRY (assembled from sub-registries)
|
|
// =============================================================================
|
|
|
|
export const CATALOG_REGISTRY: Record<CatalogId, CatalogMeta> = {
|
|
...DSFA_AI_CATALOG_META,
|
|
...VENDOR_VVT_CATALOG_META,
|
|
...REFERENCE_CATALOG_META,
|
|
} as Record<CatalogId, CatalogMeta>
|
|
|
|
// =============================================================================
|
|
// SYSTEM ENTRIES MAP (assembled from sub-registries)
|
|
// =============================================================================
|
|
|
|
const SYSTEM_ENTRIES_MAP: Record<CatalogId, Record<string, unknown>[]> = {
|
|
...DSFA_AI_SYSTEM_ENTRIES,
|
|
...VENDOR_VVT_SYSTEM_ENTRIES,
|
|
...REFERENCE_SYSTEM_ENTRIES,
|
|
} as Record<CatalogId, Record<string, unknown>[]>
|
|
|
|
// =============================================================================
|
|
// HELPER: Resolve localized text fields
|
|
// =============================================================================
|
|
|
|
function resolveField(value: unknown): string {
|
|
if (value === null || value === undefined) return ''
|
|
if (typeof value === 'string') return value
|
|
if (typeof value === 'number') return String(value)
|
|
if (typeof value === 'object' && 'de' in (value as Record<string, unknown>)) {
|
|
return String((value as Record<string, string>).de || '')
|
|
}
|
|
return String(value)
|
|
}
|
|
|
|
// =============================================================================
|
|
// ENTRY CONVERTERS
|
|
// =============================================================================
|
|
|
|
function systemEntryToCatalogEntry(
|
|
catalogId: CatalogId,
|
|
data: Record<string, unknown>,
|
|
): CatalogEntry {
|
|
const meta = CATALOG_REGISTRY[catalogId]
|
|
const idValue = resolveField(data[meta.idField])
|
|
const nameValue = resolveField(data[meta.nameField])
|
|
const descValue = meta.descriptionField ? resolveField(data[meta.descriptionField]) : undefined
|
|
const catValue = meta.categoryField ? resolveField(data[meta.categoryField]) : undefined
|
|
|
|
return {
|
|
id: idValue || crypto.randomUUID(),
|
|
catalogId,
|
|
source: 'system',
|
|
data,
|
|
displayName: nameValue || idValue || '(Unbenannt)',
|
|
displayDescription: descValue,
|
|
category: catValue,
|
|
}
|
|
}
|
|
|
|
function customEntryToCatalogEntry(
|
|
catalogId: CatalogId,
|
|
entry: CustomCatalogEntry,
|
|
): CatalogEntry {
|
|
const meta = CATALOG_REGISTRY[catalogId]
|
|
const nameValue = resolveField(entry.data[meta.nameField])
|
|
const descValue = meta.descriptionField ? resolveField(entry.data[meta.descriptionField]) : undefined
|
|
const catValue = meta.categoryField ? resolveField(entry.data[meta.categoryField]) : undefined
|
|
|
|
return {
|
|
id: entry.id,
|
|
catalogId,
|
|
source: 'custom',
|
|
data: entry.data,
|
|
displayName: nameValue || '(Unbenannt)',
|
|
displayDescription: descValue,
|
|
category: catValue,
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// PUBLIC API
|
|
// =============================================================================
|
|
|
|
export function getSystemEntries(catalogId: CatalogId): CatalogEntry[] {
|
|
const raw = SYSTEM_ENTRIES_MAP[catalogId] || []
|
|
return raw.map(data => systemEntryToCatalogEntry(catalogId, data))
|
|
}
|
|
|
|
export function getAllEntries(
|
|
catalogId: CatalogId,
|
|
customEntries: CustomCatalogEntry[] = [],
|
|
): CatalogEntry[] {
|
|
const system = getSystemEntries(catalogId)
|
|
const custom = customEntries.map(e => customEntryToCatalogEntry(catalogId, e))
|
|
return [...system, ...custom]
|
|
}
|
|
|
|
export function getCatalogsByModule(module: CatalogModule): CatalogMeta[] {
|
|
return Object.values(CATALOG_REGISTRY).filter(c => c.module === module)
|
|
}
|
|
|
|
export function getCatalogStats(
|
|
catalogId: CatalogId,
|
|
customEntries: CustomCatalogEntry[] = [],
|
|
): CatalogStats {
|
|
const meta = CATALOG_REGISTRY[catalogId]
|
|
return {
|
|
catalogId,
|
|
systemCount: meta.systemCount,
|
|
customCount: customEntries.length,
|
|
totalCount: meta.systemCount + customEntries.length,
|
|
}
|
|
}
|
|
|
|
export function getOverviewStats(customCatalogs: CustomCatalogs): CatalogOverviewStats {
|
|
const modules: CatalogModule[] = ['dsfa', 'vvt', 'vendor', 'ai_act', 'reference']
|
|
const byModule = {} as Record<CatalogModule, { catalogs: number; entries: number }>
|
|
|
|
let totalSystemEntries = 0
|
|
let totalCustomEntries = 0
|
|
|
|
for (const mod of modules) {
|
|
const cats = getCatalogsByModule(mod)
|
|
let entries = 0
|
|
for (const cat of cats) {
|
|
const customCount = customCatalogs[cat.id]?.length ?? 0
|
|
entries += cat.systemCount + customCount
|
|
totalSystemEntries += cat.systemCount
|
|
totalCustomEntries += customCount
|
|
}
|
|
byModule[mod] = { catalogs: cats.length, entries }
|
|
}
|
|
|
|
return {
|
|
totalCatalogs: Object.keys(CATALOG_REGISTRY).length,
|
|
totalSystemEntries,
|
|
totalCustomEntries,
|
|
totalEntries: totalSystemEntries + totalCustomEntries,
|
|
byModule,
|
|
}
|
|
}
|
|
|
|
export function searchCatalog(
|
|
catalogId: CatalogId,
|
|
query: string,
|
|
customEntries: CustomCatalogEntry[] = [],
|
|
): CatalogEntry[] {
|
|
const allEntries = getAllEntries(catalogId, customEntries)
|
|
const meta = CATALOG_REGISTRY[catalogId]
|
|
const q = query.toLowerCase().trim()
|
|
|
|
if (!q) return allEntries
|
|
|
|
return allEntries
|
|
.map(entry => {
|
|
let score = 0
|
|
for (const field of meta.searchableFields) {
|
|
const value = resolveField(entry.data[field]).toLowerCase()
|
|
if (value.includes(q)) {
|
|
score += value.startsWith(q) ? 10 : 5
|
|
}
|
|
}
|
|
// Also search display name
|
|
if (entry.displayName.toLowerCase().includes(q)) {
|
|
score += 15
|
|
}
|
|
return { entry, score }
|
|
})
|
|
.filter(r => r.score > 0)
|
|
.sort((a, b) => b.score - a.score)
|
|
.map(r => r.entry)
|
|
}
|