Files
cmdb-insight/backend/src/api/controllers/SyncController.ts
Bert Hausmans 6bb5907bbd Fix TypeScript compilation errors in backend
- Fix query parameter type issues (string | string[] to string) in controllers
- Add public getDatabaseAdapter() method to SchemaRepository for db access
- Fix SchemaSyncService export and import issues
- Fix referenceObject vs referenceObjectType property name
- Add missing jiraAssetsClient import in normalizedCacheStore
- Fix duplicate properties in object literals
- Add type annotations for implicit any types
- Fix type predicate issues with generics
- Fix method calls (getEnabledObjectTypes, syncAllSchemas)
- Fix type mismatches (ObjectTypeRecord vs expected types)
- Fix Buffer type issue in biaMatchingService
- Export SchemaSyncService class for ServiceFactory
2026-01-21 09:29:05 +01:00

290 lines
13 KiB
TypeScript

/**
* SyncController - API handlers for sync operations
*/
import { Request, Response } from 'express';
import { logger } from '../../services/logger.js';
import { getServices } from '../../services/ServiceFactory.js';
export class SyncController {
/**
* Sync all schemas
* POST /api/v2/sync/schemas
*/
async syncSchemas(req: Request, res: Response): Promise<void> {
try {
const services = getServices();
const result = await services.schemaSyncService.syncAll();
res.json({
...result,
success: result.success !== undefined ? result.success : true,
});
} catch (error) {
logger.error('SyncController: Failed to sync schemas', error);
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
});
}
}
/**
* Sync all enabled object types
* POST /api/v2/sync/objects
*/
async syncAllObjects(req: Request, res: Response): Promise<void> {
try {
const services = getServices();
// Get enabled types
const rawTypes = await services.schemaRepo.getEnabledObjectTypes();
if (rawTypes.length === 0) {
res.status(400).json({
success: false,
error: 'No object types enabled for syncing. Please configure object types in Schema Configuration.',
});
return;
}
const results = [];
let totalObjectsProcessed = 0;
let totalObjectsCached = 0;
let totalRelations = 0;
// Sync each enabled type
for (const type of rawTypes) {
const result = await services.objectSyncService.syncObjectType(
type.schemaId.toString(),
type.id,
type.typeName,
type.displayName
);
results.push({
typeName: type.typeName,
displayName: type.displayName,
...result,
});
totalObjectsProcessed += result.objectsProcessed;
totalObjectsCached += result.objectsCached;
totalRelations += result.relationsExtracted;
}
res.json({
success: true,
stats: results,
totalObjectsProcessed,
totalObjectsCached,
totalRelations,
});
} catch (error) {
logger.error('SyncController: Failed to sync objects', error);
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
});
}
}
/**
* Sync a specific object type
* POST /api/v2/sync/objects/:typeName
*/
async syncObjectType(req: Request, res: Response): Promise<void> {
try {
const typeName = Array.isArray(req.params.typeName) ? req.params.typeName[0] : req.params.typeName;
if (!typeName) {
res.status(400).json({ error: 'typeName parameter required' });
return;
}
const services = getServices();
// Get enabled types
let rawTypes = await services.schemaRepo.getEnabledObjectTypes();
let enabledTypes = rawTypes.map(t => ({
typeName: t.typeName,
displayName: t.displayName,
schemaId: t.schemaId.toString(),
objectTypeId: t.id,
}));
// Filter out any entries with missing typeName
enabledTypes = enabledTypes.filter((t: { typeName?: string }) => t && t.typeName);
// Debug logging - also check database directly
logger.info(`SyncController: Looking for type "${typeName}" in ${enabledTypes.length} enabled types`);
logger.debug(`SyncController: Enabled types: ${JSON.stringify(enabledTypes.map((t: { typeName?: string; displayName?: string }) => ({ typeName: t?.typeName, displayName: t?.displayName })))}`);
// Additional debug: Check database directly for enabled types (including those with missing type_name)
const db = services.schemaRepo.getDatabaseAdapter();
const isPostgres = db.isPostgres === true;
const enabledCondition = isPostgres ? 'enabled IS true' : 'enabled = 1';
const dbCheck = await db.query<{ type_name: string | null; display_name: string; enabled: boolean | number; id: number; jira_type_id: number }>(
`SELECT id, jira_type_id, type_name, display_name, enabled FROM object_types WHERE ${enabledCondition}`
);
logger.info(`SyncController: Found ${dbCheck.length} enabled types in database (raw check)`);
logger.debug(`SyncController: Database enabled types (raw): ${JSON.stringify(dbCheck.map(t => ({ id: t.id, displayName: t.display_name, typeName: t.type_name, hasTypeName: !!(t.type_name && t.type_name.trim() !== '') })))}`);
// Check if AzureSubscription or similar is enabled but missing type_name
const typeNameLower = typeName.toLowerCase();
const matchingByDisplayName = dbCheck.filter((t: { display_name: string }) =>
t.display_name.toLowerCase().includes(typeNameLower) ||
typeNameLower.includes(t.display_name.toLowerCase())
);
if (matchingByDisplayName.length > 0) {
logger.warn(`SyncController: Found enabled type(s) matching "${typeName}" by display_name but not in enabled list:`, {
matches: matchingByDisplayName.map(t => ({
id: t.id,
displayName: t.display_name,
typeName: t.type_name,
hasTypeName: !!(t.type_name && t.type_name.trim() !== ''),
enabled: t.enabled,
})),
});
}
const type = enabledTypes.find((t: { typeName?: string }) => t && t.typeName === typeName);
if (!type) {
// Check if type exists but is not enabled or has missing type_name
const allType = await services.schemaRepo.getObjectTypeByTypeName(typeName);
if (allType) {
// Debug: Check the actual enabled value and query
const enabledValue = allType.enabled;
const enabledType = typeof enabledValue;
logger.warn(`SyncController: Type "${typeName}" found but not in enabled list. enabled=${enabledValue} (type: ${enabledType}), enabledTypes.length=${enabledTypes.length}`);
logger.debug(`SyncController: Enabled types details: ${JSON.stringify(enabledTypes)}`);
// Try to find it with different case (handle undefined typeName)
const typeNameLower = typeName.toLowerCase();
const caseInsensitiveMatch = enabledTypes.find((t: { typeName?: string }) => t && t.typeName && t.typeName.toLowerCase() === typeNameLower);
if (caseInsensitiveMatch) {
logger.warn(`SyncController: Found type with different case: "${caseInsensitiveMatch.typeName}" vs "${typeName}"`);
// Use the found type with correct case
const result = await services.objectSyncService.syncObjectType(
caseInsensitiveMatch.schemaId.toString(),
caseInsensitiveMatch.objectTypeId,
caseInsensitiveMatch.typeName,
caseInsensitiveMatch.displayName
);
res.json({
success: true,
...result,
hasErrors: result.errors.length > 0,
note: `Type name case corrected: "${typeName}" -> "${caseInsensitiveMatch.typeName}"`,
});
return;
}
// Direct SQL query to verify enabled status and type_name
const db = services.schemaRepo.getDatabaseAdapter();
const isPostgres = db.isPostgres === true;
const rawCheck = await db.queryOne<{ enabled: boolean | number; type_name: string | null; display_name: string }>(
`SELECT enabled, type_name, display_name FROM object_types WHERE type_name = ?`,
[typeName]
);
// Check if type is enabled but missing type_name in enabled list (might be filtered out)
const enabledCondition = isPostgres ? 'enabled IS true' : 'enabled = 1';
const enabledWithMissingTypeName = await db.query<{ display_name: string; type_name: string | null; enabled: boolean | number }>(
`SELECT display_name, type_name, enabled FROM object_types WHERE display_name ILIKE ? AND ${enabledCondition}`,
[`%${typeName}%`]
);
// Get list of all enabled type names for better error message
const enabledTypeNames = enabledTypes.map((t: { typeName?: string }) => t.typeName).filter(Boolean) as string[];
// Check if the issue is that the type is enabled but has a missing type_name
if (rawCheck && (rawCheck.enabled === true || rawCheck.enabled === 1)) {
if (!rawCheck.type_name || rawCheck.type_name.trim() === '') {
res.status(400).json({
success: false,
error: `Object type "${typeName}" is enabled in the database but has a missing or empty type_name. This prevents it from being synced. Please run schema sync again to fix the type_name, or use the "Fix Missing Type Names" debug tool (Settings → Debug).`,
details: {
requestedType: typeName,
displayName: rawCheck.display_name,
enabledInDatabase: rawCheck.enabled,
typeNameInDatabase: rawCheck.type_name,
enabledTypesCount: enabledTypes.length,
enabledTypeNames: enabledTypeNames,
hint: 'Run schema sync to ensure all object types have a valid type_name, or use the Debug page to fix missing type names.',
},
});
return;
}
}
res.status(400).json({
success: false,
error: `Object type "${typeName}" is not enabled for syncing. Currently enabled types: ${enabledTypeNames.length > 0 ? enabledTypeNames.join(', ') : 'none'}. Please enable "${typeName}" in Schema Configuration settings (Settings → Schema Configuratie).`,
details: {
requestedType: typeName,
enabledInDatabase: rawCheck?.enabled,
typeNameInDatabase: rawCheck?.type_name,
enabledTypesCount: enabledTypes.length,
enabledTypeNames: enabledTypeNames,
hint: enabledTypeNames.length === 0
? 'No object types are currently enabled. Please enable at least one object type in Schema Configuration.'
: `You enabled: ${enabledTypeNames.join(', ')}. Please enable "${typeName}" if you want to sync it.`,
},
});
} else {
// Type not found by type_name - check by display_name (case-insensitive)
const db = services.schemaRepo.getDatabaseAdapter();
const byDisplayName = await db.queryOne<{ enabled: boolean | number; type_name: string | null; display_name: string }>(
`SELECT enabled, type_name, display_name FROM object_types WHERE display_name ILIKE ? LIMIT 1`,
[`%${typeName}%`]
);
if (byDisplayName && (byDisplayName.enabled === true || byDisplayName.enabled === 1)) {
// Type is enabled but type_name might be missing or different
res.status(400).json({
success: false,
error: `Found enabled type "${byDisplayName.display_name}" but it has ${byDisplayName.type_name ? `type_name="${byDisplayName.type_name}"` : 'missing type_name'}. ${!byDisplayName.type_name ? 'Please run schema sync to fix the type_name, or use the "Fix Missing Type Names" debug tool.' : `Please use the correct type_name: "${byDisplayName.type_name}"`}`,
details: {
requestedType: typeName,
foundDisplayName: byDisplayName.display_name,
foundTypeName: byDisplayName.type_name,
enabledInDatabase: byDisplayName.enabled,
hint: !byDisplayName.type_name
? 'Run schema sync to ensure all object types have a valid type_name.'
: `Use type_name "${byDisplayName.type_name}" instead of "${typeName}"`,
},
});
return;
}
res.status(400).json({
success: false,
error: `Object type ${typeName} not found. Available enabled types: ${enabledTypes.map((t: { typeName?: string }) => t.typeName).filter(Boolean).join(', ') || 'none'}. Please run schema sync first.`,
});
}
return;
}
const result = await services.objectSyncService.syncObjectType(
type.schemaId.toString(),
type.objectTypeId,
type.typeName,
type.displayName
);
// Return success even if there are errors (errors are in result.errors array)
res.json({
success: true,
...result,
hasErrors: result.errors.length > 0,
});
} catch (error) {
logger.error(`SyncController: Failed to sync object type ${req.params.typeName}`, error);
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
});
}
}
}