didi-lot2-backend/backend/services/orchestration-layer/didiFramework/src/utils/dependency-checker.ts
2026-07-10 03:39:53 -07:00

377 lines
8.7 KiB
TypeScript

/**
* Dependency Checker Utility
*
* Verifies if a record can be safely deleted by checking for child references.
* Provides warnings and prevents accidental deletion of records with dependencies.
*/
import { query } from '../config/database';
// Types
export interface DependencyRule {
childTable: string;
childColumn: string;
displayName: string;
}
export interface DependencyCheckResult {
canDelete: boolean;
hasChildren: boolean;
dependencies: {
table: string;
displayName: string;
count: number;
}[];
totalChildren: number;
warning?: string;
}
export interface DeleteResult {
success: boolean;
deleted: boolean;
message: string;
hadDependencies?: boolean;
dependencyDetails?: DependencyCheckResult;
}
/**
* Dependency map for all tables that have child references
* Key: parent table name
* Value: array of child table references
*/
const DEPENDENCY_MAP: Record<string, DependencyRule[]> = {
// Analysis Core
'dimension': [{
childTable: 'subdimension',
childColumn: 'dimension_id',
displayName: 'Subdimensiuni'
}],
'subdimension': [{
childTable: 'technique',
childColumn: 'subdimension_id',
displayName: 'Tehnici'
}],
'technique': [
{
childTable: 'technique_indicator',
childColumn: 'technique_id',
displayName: 'Indicatori'
},
{
childTable: 'technique_validation_rule',
childColumn: 'technique_id',
displayName: 'Reguli de validare'
}
],
// Source Assessment
'platform_modifier': [{
childTable: 'platform',
childColumn: 'platform_modifier_id',
displayName: 'Platforme'
}],
'source_type': [{
childTable: 'domain_attribute',
childColumn: 'source_type_id',
displayName: 'Atribute domeniu'
}],
'source_credibility': [{
childTable: 'domain_attribute',
childColumn: 'source_credibility_id',
displayName: 'Atribute domeniu'
}],
'domain_age_score': [{
childTable: 'domain_attribute',
childColumn: 'domain_age_score',
displayName: 'Atribute domeniu'
}],
'domain_risk_level': [{
childTable: 'domain_attribute',
childColumn: 'domain_risk_level_id',
displayName: 'Atribute domeniu'
}],
'domain_red_flag': [{
childTable: 'domain_attribute',
childColumn: 'domain_red_flag_id',
displayName: 'Atribute domeniu'
}],
'author_classification': [{
childTable: 'author',
childColumn: 'author_classification_id',
displayName: 'Autori'
}],
'author_credibility': [{
childTable: 'author',
childColumn: 'author_credibility_id',
displayName: 'Autori'
}],
// Source hierarchy
'source': [
{
childTable: 'platform',
childColumn: 'platform_id',
displayName: 'Platforme'
},
{
childTable: 'domain',
childColumn: 'domain_id',
displayName: 'Domenii'
},
{
childTable: 'author',
childColumn: 'author_id',
displayName: 'Autori'
}
],
'domain': [{
childTable: 'domain_attribute',
childColumn: 'domain_id',
displayName: 'Atribute domeniu'
}]
};
/**
* Tables that can be deleted without any child checks
*/
const LEAF_TABLES = new Set([
'technique_indicator',
'technique_validation_rule',
'platform',
'domain_attribute',
'author',
'claim',
'claim_type',
'confidence',
'interpretation',
'verdict_category',
'risk_mapping',
'severity_assessment',
'component_weight',
'weight_scenario',
'multiplier',
'source_assessment',
'notification',
'email_server'
]);
/**
* Check if a table has dependency rules defined
*/
export function hasDependencyRules(tableName: string): boolean {
return tableName in DEPENDENCY_MAP;
}
/**
* Check if a table is a leaf table (no children possible)
*/
export function isLeafTable(tableName: string): boolean {
return LEAF_TABLES.has(tableName);
}
/**
* Get dependency rules for a table
*/
export function getDependencyRules(tableName: string): DependencyRule[] {
return DEPENDENCY_MAP[tableName] || [];
}
/**
* Check dependencies for a specific record before deletion
*
* @param tableName - The table name to check
* @param idColumn - The primary key column name
* @param idValue - The primary key value
* @returns DependencyCheckResult with details about children
*/
export async function checkDependencies(
tableName: string,
idColumn: string,
idValue: number | string
): Promise<DependencyCheckResult> {
// If it's a leaf table, it can always be deleted
if (isLeafTable(tableName)) {
return {
canDelete: true,
hasChildren: false,
dependencies: [],
totalChildren: 0
};
}
const rules = getDependencyRules(tableName);
// If no rules defined, assume it can be deleted
if (rules.length === 0) {
return {
canDelete: true,
hasChildren: false,
dependencies: [],
totalChildren: 0
};
}
const dependencies: { table: string; displayName: string; count: number }[] = [];
let totalChildren = 0;
// Check each dependency rule
for (const rule of rules) {
const countQuery = `SELECT COUNT(*) as count FROM ${rule.childTable} WHERE ${rule.childColumn} = $1`;
const result = await query<{ count: string }>(countQuery, [idValue]);
const count = parseInt(result[0]?.count || '0', 10);
if (count > 0) {
dependencies.push({
table: rule.childTable,
displayName: rule.displayName,
count
});
totalChildren += count;
}
}
const hasChildren = totalChildren > 0;
return {
canDelete: !hasChildren,
hasChildren,
dependencies,
totalChildren,
warning: hasChildren
? `Nu se poate șterge: există ${totalChildren} înregistrări dependente (${dependencies.map(d => `${d.count} ${d.displayName}`).join(', ')})`
: undefined
};
}
/**
* Safe delete function - checks dependencies before deleting
*
* @param tableName - Table to delete from
* @param idColumn - Primary key column name
* @param idValue - Primary key value
* @param force - If true, attempts to delete even with children (will fail at DB level)
* @returns DeleteResult with success status and details
*/
export async function safeDelete(
tableName: string,
idColumn: string,
idValue: number | string,
force: boolean = false
): Promise<DeleteResult> {
// First check dependencies
const depCheck = await checkDependencies(tableName, idColumn, idValue);
if (depCheck.hasChildren && !force) {
return {
success: false,
deleted: false,
message: depCheck.warning || 'Record has dependencies and cannot be deleted',
hadDependencies: true,
dependencyDetails: depCheck
};
}
// Attempt to delete
try {
const deleteQuery = `DELETE FROM ${tableName} WHERE ${idColumn} = $1 RETURNING *`;
const result = await query(deleteQuery, [idValue]);
if (result.length === 0) {
return {
success: false,
deleted: false,
message: 'Înregistrarea nu a fost găsită'
};
}
return {
success: true,
deleted: true,
message: 'Înregistrarea a fost ștearsă cu succes'
};
} catch (error: any) {
// Handle FK violation errors from PostgreSQL
if (error.code === '23503') { // foreign_key_violation
return {
success: false,
deleted: false,
message: 'Nu se poate șterge: există înregistrări dependente în baza de date',
hadDependencies: true
};
}
throw error;
}
}
/**
* Get a summary of all dependencies for a table (useful for UI display)
*/
export function getDependencySummary(tableName: string): {
hasRules: boolean;
isLeaf: boolean;
rules: DependencyRule[];
canHaveChildren: boolean;
} {
const isLeaf = isLeafTable(tableName);
const rules = getDependencyRules(tableName);
return {
hasRules: rules.length > 0,
isLeaf,
rules,
canHaveChildren: !isLeaf && rules.length > 0
};
}
/**
* Batch check dependencies for multiple records
*/
export async function batchCheckDependencies(
tableName: string,
idColumn: string,
idValues: (number | string)[]
): Promise<Map<number | string, DependencyCheckResult>> {
const results = new Map<number | string, DependencyCheckResult>();
// For leaf tables, all can be deleted
if (isLeafTable(tableName)) {
for (const id of idValues) {
results.set(id, {
canDelete: true,
hasChildren: false,
dependencies: [],
totalChildren: 0
});
}
return results;
}
// Check each record
for (const id of idValues) {
const check = await checkDependencies(tableName, idColumn, id);
results.set(id, check);
}
return results;
}
export default {
checkDependencies,
safeDelete,
hasDependencyRules,
isLeafTable,
getDependencyRules,
getDependencySummary,
batchCheckDependencies
};