refactor(admin): split lib document generators and data catalogs into domain barrels
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>
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
Reference in New Issue
Block a user