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
This commit is contained in:
@@ -157,7 +157,11 @@ export class DebugController {
|
|||||||
*/
|
*/
|
||||||
async getObjectTypeStats(req: Request, res: Response): Promise<void> {
|
async getObjectTypeStats(req: Request, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const typeName = req.params.typeName;
|
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();
|
const services = getServices();
|
||||||
|
|
||||||
// Get object count
|
// Get object count
|
||||||
@@ -194,7 +198,7 @@ export class DebugController {
|
|||||||
async getAllObjectTypes(req: Request, res: Response): Promise<void> {
|
async getAllObjectTypes(req: Request, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const services = getServices();
|
const services = getServices();
|
||||||
const db = services.schemaRepo.db;
|
const db = services.schemaRepo.getDatabaseAdapter();
|
||||||
|
|
||||||
// Check if object_types table exists
|
// Check if object_types table exists
|
||||||
try {
|
try {
|
||||||
@@ -243,7 +247,13 @@ export class DebugController {
|
|||||||
// Get enabled types via service (may fail if table has issues)
|
// Get enabled types via service (may fail if table has issues)
|
||||||
let enabledTypes: Array<{ typeName: string; displayName: string; schemaId: string; objectTypeId: number }> = [];
|
let enabledTypes: Array<{ typeName: string; displayName: string; schemaId: string; objectTypeId: number }> = [];
|
||||||
try {
|
try {
|
||||||
enabledTypes = await services.schemaSyncService.getEnabledObjectTypes();
|
const rawTypes = await services.schemaRepo.getEnabledObjectTypes();
|
||||||
|
enabledTypes = rawTypes.map(t => ({
|
||||||
|
typeName: t.typeName,
|
||||||
|
displayName: t.displayName,
|
||||||
|
schemaId: t.schemaId.toString(),
|
||||||
|
objectTypeId: t.id,
|
||||||
|
}));
|
||||||
logger.debug(`DebugController: getEnabledObjectTypes returned ${enabledTypes.length} types: ${enabledTypes.map(t => t.typeName).join(', ')}`);
|
logger.debug(`DebugController: getEnabledObjectTypes returned ${enabledTypes.length} types: ${enabledTypes.map(t => t.typeName).join(', ')}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('DebugController: Failed to get enabled types via service', error);
|
logger.error('DebugController: Failed to get enabled types via service', error);
|
||||||
@@ -295,9 +305,13 @@ export class DebugController {
|
|||||||
*/
|
*/
|
||||||
async diagnoseObjectType(req: Request, res: Response): Promise<void> {
|
async diagnoseObjectType(req: Request, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const typeName = req.params.typeName;
|
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();
|
const services = getServices();
|
||||||
const db = services.schemaRepo.db;
|
const db = services.schemaRepo.getDatabaseAdapter();
|
||||||
const isPostgres = db.isPostgres === true;
|
const isPostgres = db.isPostgres === true;
|
||||||
const enabledCondition = isPostgres ? 'enabled IS true' : 'enabled = 1';
|
const enabledCondition = isPostgres ? 'enabled IS true' : 'enabled = 1';
|
||||||
|
|
||||||
@@ -351,14 +365,14 @@ export class DebugController {
|
|||||||
// Check enabled types via service
|
// Check enabled types via service
|
||||||
let enabledTypesFromService: string[] = [];
|
let enabledTypesFromService: string[] = [];
|
||||||
try {
|
try {
|
||||||
const enabledTypes = await services.schemaSyncService.getEnabledObjectTypes();
|
const rawTypes = await services.schemaRepo.getEnabledObjectTypes();
|
||||||
enabledTypesFromService = enabledTypes.map(t => t.typeName);
|
enabledTypesFromService = rawTypes.map((t: { typeName: string }) => t.typeName);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('DebugController: Failed to get enabled types from service', error);
|
logger.error('DebugController: Failed to get enabled types from service', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if type is in enabled list from service
|
// Check if type is in enabled list from service
|
||||||
const isInEnabledList = enabledTypesFromService.includes(typeName);
|
const isInEnabledList = enabledTypesFromService.includes(typeName as string);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
requestedType: typeName,
|
requestedType: typeName,
|
||||||
@@ -432,7 +446,7 @@ export class DebugController {
|
|||||||
async fixMissingTypeNames(req: Request, res: Response): Promise<void> {
|
async fixMissingTypeNames(req: Request, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const services = getServices();
|
const services = getServices();
|
||||||
const db = services.schemaRepo.db;
|
const db = services.schemaRepo.getDatabaseAdapter();
|
||||||
|
|
||||||
// Find all object types with NULL or empty type_name
|
// Find all object types with NULL or empty type_name
|
||||||
// Also check for enabled ones specifically
|
// Also check for enabled ones specifically
|
||||||
@@ -510,15 +524,16 @@ export class DebugController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Re-fetch enabled types to verify fix (reuse services from line 294)
|
// Re-fetch enabled types to verify fix (reuse services from line 294)
|
||||||
const enabledTypesAfterFix = await services.schemaSyncService.getEnabledObjectTypes();
|
const rawTypes = await services.schemaRepo.getEnabledObjectTypes();
|
||||||
|
const enabledTypesAfterFix = rawTypes.map(t => t.typeName);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
fixed: fixes.length,
|
fixed: fixes.length,
|
||||||
errors: errors.length,
|
errorCount: errors.length,
|
||||||
fixes,
|
fixes,
|
||||||
errors: errors.length > 0 ? errors : undefined,
|
errors: errors.length > 0 ? errors : undefined,
|
||||||
enabledTypesAfterFix: enabledTypesAfterFix.map(t => t.typeName),
|
enabledTypesAfterFix: enabledTypesAfterFix,
|
||||||
note: enabledWithNullTypeName.length > 0
|
note: enabledWithNullTypeName.length > 0
|
||||||
? `Fixed ${enabledWithNullTypeName.length} enabled types that were missing type_name. They should now appear in enabled types list.`
|
? `Fixed ${enabledWithNullTypeName.length} enabled types that were missing type_name. They should now appear in enabled types list.`
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ export class SyncController {
|
|||||||
async syncSchemas(req: Request, res: Response): Promise<void> {
|
async syncSchemas(req: Request, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const services = getServices();
|
const services = getServices();
|
||||||
const result = await services.schemaSyncService.syncAllSchemas();
|
const result = await services.schemaSyncService.syncAll();
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
|
||||||
...result,
|
...result,
|
||||||
|
success: result.success !== undefined ? result.success : true,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('SyncController: Failed to sync schemas', error);
|
logger.error('SyncController: Failed to sync schemas', error);
|
||||||
@@ -38,9 +38,9 @@ export class SyncController {
|
|||||||
const services = getServices();
|
const services = getServices();
|
||||||
|
|
||||||
// Get enabled types
|
// Get enabled types
|
||||||
const enabledTypes = await services.schemaSyncService.getEnabledObjectTypes();
|
const rawTypes = await services.schemaRepo.getEnabledObjectTypes();
|
||||||
|
|
||||||
if (enabledTypes.length === 0) {
|
if (rawTypes.length === 0) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'No object types enabled for syncing. Please configure object types in Schema Configuration.',
|
error: 'No object types enabled for syncing. Please configure object types in Schema Configuration.',
|
||||||
@@ -54,10 +54,10 @@ export class SyncController {
|
|||||||
let totalRelations = 0;
|
let totalRelations = 0;
|
||||||
|
|
||||||
// Sync each enabled type
|
// Sync each enabled type
|
||||||
for (const type of enabledTypes) {
|
for (const type of rawTypes) {
|
||||||
const result = await services.objectSyncService.syncObjectType(
|
const result = await services.objectSyncService.syncObjectType(
|
||||||
type.schemaId,
|
type.schemaId.toString(),
|
||||||
type.objectTypeId,
|
type.id,
|
||||||
type.typeName,
|
type.typeName,
|
||||||
type.displayName
|
type.displayName
|
||||||
);
|
);
|
||||||
@@ -95,21 +95,31 @@ export class SyncController {
|
|||||||
*/
|
*/
|
||||||
async syncObjectType(req: Request, res: Response): Promise<void> {
|
async syncObjectType(req: Request, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const typeName = req.params.typeName;
|
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();
|
const services = getServices();
|
||||||
|
|
||||||
// Get enabled types
|
// Get enabled types
|
||||||
let enabledTypes = await services.schemaSyncService.getEnabledObjectTypes();
|
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
|
// Filter out any entries with missing typeName
|
||||||
enabledTypes = enabledTypes.filter(t => t && t.typeName);
|
enabledTypes = enabledTypes.filter((t: { typeName?: string }) => t && t.typeName);
|
||||||
|
|
||||||
// Debug logging - also check database directly
|
// Debug logging - also check database directly
|
||||||
logger.info(`SyncController: Looking for type "${typeName}" in ${enabledTypes.length} enabled types`);
|
logger.info(`SyncController: Looking for type "${typeName}" in ${enabledTypes.length} enabled types`);
|
||||||
logger.debug(`SyncController: Enabled types: ${JSON.stringify(enabledTypes.map(t => ({ typeName: t?.typeName, displayName: t?.displayName })))}`);
|
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)
|
// Additional debug: Check database directly for enabled types (including those with missing type_name)
|
||||||
const db = services.schemaRepo.db;
|
const db = services.schemaRepo.getDatabaseAdapter();
|
||||||
const isPostgres = db.isPostgres === true;
|
const isPostgres = db.isPostgres === true;
|
||||||
const enabledCondition = isPostgres ? 'enabled IS true' : 'enabled = 1';
|
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 }>(
|
const dbCheck = await db.query<{ type_name: string | null; display_name: string; enabled: boolean | number; id: number; jira_type_id: number }>(
|
||||||
@@ -119,9 +129,10 @@ export class SyncController {
|
|||||||
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() !== '') })))}`);
|
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
|
// Check if AzureSubscription or similar is enabled but missing type_name
|
||||||
const matchingByDisplayName = dbCheck.filter(t =>
|
const typeNameLower = typeName.toLowerCase();
|
||||||
t.display_name.toLowerCase().includes(typeName.toLowerCase()) ||
|
const matchingByDisplayName = dbCheck.filter((t: { display_name: string }) =>
|
||||||
typeName.toLowerCase().includes(t.display_name.toLowerCase())
|
t.display_name.toLowerCase().includes(typeNameLower) ||
|
||||||
|
typeNameLower.includes(t.display_name.toLowerCase())
|
||||||
);
|
);
|
||||||
if (matchingByDisplayName.length > 0) {
|
if (matchingByDisplayName.length > 0) {
|
||||||
logger.warn(`SyncController: Found enabled type(s) matching "${typeName}" by display_name but not in enabled list:`, {
|
logger.warn(`SyncController: Found enabled type(s) matching "${typeName}" by display_name but not in enabled list:`, {
|
||||||
@@ -135,7 +146,7 @@ export class SyncController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = enabledTypes.find(t => t && t.typeName === typeName);
|
const type = enabledTypes.find((t: { typeName?: string }) => t && t.typeName === typeName);
|
||||||
|
|
||||||
if (!type) {
|
if (!type) {
|
||||||
// Check if type exists but is not enabled or has missing type_name
|
// Check if type exists but is not enabled or has missing type_name
|
||||||
@@ -148,12 +159,13 @@ export class SyncController {
|
|||||||
logger.debug(`SyncController: Enabled types details: ${JSON.stringify(enabledTypes)}`);
|
logger.debug(`SyncController: Enabled types details: ${JSON.stringify(enabledTypes)}`);
|
||||||
|
|
||||||
// Try to find it with different case (handle undefined typeName)
|
// Try to find it with different case (handle undefined typeName)
|
||||||
const caseInsensitiveMatch = enabledTypes.find(t => t && t.typeName && t.typeName.toLowerCase() === typeName.toLowerCase());
|
const typeNameLower = typeName.toLowerCase();
|
||||||
|
const caseInsensitiveMatch = enabledTypes.find((t: { typeName?: string }) => t && t.typeName && t.typeName.toLowerCase() === typeNameLower);
|
||||||
if (caseInsensitiveMatch) {
|
if (caseInsensitiveMatch) {
|
||||||
logger.warn(`SyncController: Found type with different case: "${caseInsensitiveMatch.typeName}" vs "${typeName}"`);
|
logger.warn(`SyncController: Found type with different case: "${caseInsensitiveMatch.typeName}" vs "${typeName}"`);
|
||||||
// Use the found type with correct case
|
// Use the found type with correct case
|
||||||
const result = await services.objectSyncService.syncObjectType(
|
const result = await services.objectSyncService.syncObjectType(
|
||||||
caseInsensitiveMatch.schemaId,
|
caseInsensitiveMatch.schemaId.toString(),
|
||||||
caseInsensitiveMatch.objectTypeId,
|
caseInsensitiveMatch.objectTypeId,
|
||||||
caseInsensitiveMatch.typeName,
|
caseInsensitiveMatch.typeName,
|
||||||
caseInsensitiveMatch.displayName
|
caseInsensitiveMatch.displayName
|
||||||
@@ -168,7 +180,7 @@ export class SyncController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Direct SQL query to verify enabled status and type_name
|
// Direct SQL query to verify enabled status and type_name
|
||||||
const db = services.schemaRepo.db;
|
const db = services.schemaRepo.getDatabaseAdapter();
|
||||||
const isPostgres = db.isPostgres === true;
|
const isPostgres = db.isPostgres === true;
|
||||||
const rawCheck = await db.queryOne<{ enabled: boolean | number; type_name: string | null; display_name: string }>(
|
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 = ?`,
|
`SELECT enabled, type_name, display_name FROM object_types WHERE type_name = ?`,
|
||||||
@@ -183,7 +195,7 @@ export class SyncController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Get list of all enabled type names for better error message
|
// Get list of all enabled type names for better error message
|
||||||
const enabledTypeNames = enabledTypes.map(t => t.typeName).filter(Boolean);
|
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
|
// 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 && (rawCheck.enabled === true || rawCheck.enabled === 1)) {
|
||||||
@@ -221,7 +233,7 @@ export class SyncController {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Type not found by type_name - check by display_name (case-insensitive)
|
// Type not found by type_name - check by display_name (case-insensitive)
|
||||||
const db = services.schemaRepo.db;
|
const db = services.schemaRepo.getDatabaseAdapter();
|
||||||
const byDisplayName = await db.queryOne<{ enabled: boolean | number; type_name: string | null; display_name: string }>(
|
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`,
|
`SELECT enabled, type_name, display_name FROM object_types WHERE display_name ILIKE ? LIMIT 1`,
|
||||||
[`%${typeName}%`]
|
[`%${typeName}%`]
|
||||||
@@ -247,14 +259,14 @@ export class SyncController {
|
|||||||
|
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: `Object type ${typeName} not found. Available enabled types: ${enabledTypes.map(t => t.typeName).join(', ') || 'none'}. Please run schema sync first.`,
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await services.objectSyncService.syncObjectType(
|
const result = await services.objectSyncService.syncObjectType(
|
||||||
type.schemaId,
|
type.schemaId.toString(),
|
||||||
type.objectTypeId,
|
type.objectTypeId,
|
||||||
type.typeName,
|
type.typeName,
|
||||||
type.displayName
|
type.displayName
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ app.listen(PORT, async () => {
|
|||||||
if (db) {
|
if (db) {
|
||||||
await db.ensureInitialized?.();
|
await db.ensureInitialized?.();
|
||||||
try {
|
try {
|
||||||
const schemaRow = await db.queryOne<{ count: number }>(
|
const schemaRow = await (db as any).queryOne<{ count: number }>(
|
||||||
`SELECT COUNT(*) as count FROM schemas`
|
`SELECT COUNT(*) as count FROM schemas`
|
||||||
);
|
);
|
||||||
hasSchemas = (schemaRow?.count || 0) > 0;
|
hasSchemas = (schemaRow?.count || 0) > 0;
|
||||||
|
|||||||
@@ -47,6 +47,13 @@ export interface AttributeRecord {
|
|||||||
|
|
||||||
export class SchemaRepository {
|
export class SchemaRepository {
|
||||||
constructor(private db: DatabaseAdapter) {}
|
constructor(private db: DatabaseAdapter) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database adapter (for debug/advanced operations)
|
||||||
|
*/
|
||||||
|
getDatabaseAdapter(): DatabaseAdapter {
|
||||||
|
return this.db;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upsert a schema
|
* Upsert a schema
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ router.post('/discover', requirePermission('manage_settings'), async (req, res)
|
|||||||
schemaCacheService.invalidate(); // Invalidate cache
|
schemaCacheService.invalidate(); // Invalidate cache
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: result.success,
|
|
||||||
message: 'Schema synchronization completed',
|
message: 'Schema synchronization completed',
|
||||||
...result,
|
...result,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ router.post('/discover', async (req: Request, res: Response) => {
|
|||||||
if (result.schemasProcessed === 0) {
|
if (result.schemasProcessed === 0) {
|
||||||
logger.warn('Schema configuration: Sync returned 0 schemas - this might indicate an API issue');
|
logger.warn('Schema configuration: Sync returned 0 schemas - this might indicate an API issue');
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
|
||||||
message: 'No schemas found. Please check: 1) JIRA_SERVICE_ACCOUNT_TOKEN is configured correctly, 2) Jira Assets API is accessible, 3) API endpoint /rest/assets/1.0/objectschema/list is available',
|
message: 'No schemas found. Please check: 1) JIRA_SERVICE_ACCOUNT_TOKEN is configured correctly, 2) Jira Assets API is accessible, 3) API endpoint /rest/assets/1.0/objectschema/list is available',
|
||||||
...result,
|
...result,
|
||||||
});
|
});
|
||||||
@@ -51,7 +50,6 @@ router.post('/discover', async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: result.success,
|
|
||||||
message: 'Schema synchronization completed successfully',
|
message: 'Schema synchronization completed successfully',
|
||||||
schemasDiscovered: result.schemasProcessed,
|
schemasDiscovered: result.schemasProcessed,
|
||||||
objectTypesDiscovered: result.objectTypesProcessed,
|
objectTypesDiscovered: result.objectTypesProcessed,
|
||||||
@@ -88,7 +86,11 @@ router.get('/object-types', async (req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
router.patch('/object-types/:id/enabled', async (req: Request, res: Response) => {
|
router.patch('/object-types/:id/enabled', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const id = req.params.id;
|
const id = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id;
|
||||||
|
if (!id) {
|
||||||
|
res.status(400).json({ error: 'id parameter required' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
const { enabled } = req.body;
|
const { enabled } = req.body;
|
||||||
|
|
||||||
if (typeof enabled !== 'boolean') {
|
if (typeof enabled !== 'boolean') {
|
||||||
@@ -187,7 +189,12 @@ router.patch('/schemas/:schemaId/search-enabled', async (req: Request, res: Resp
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await schemaConfigurationService.setSchemaSearchEnabled(schemaId, searchEnabled);
|
const schemaIdStr = Array.isArray(schemaId) ? schemaId[0] : schemaId;
|
||||||
|
if (!schemaIdStr) {
|
||||||
|
res.status(400).json({ error: 'schemaId parameter required' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await schemaConfigurationService.setSchemaSearchEnabled(schemaIdStr, searchEnabled);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
status: 'success',
|
status: 'success',
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export interface SyncProgress {
|
|||||||
// SchemaSyncService Implementation
|
// SchemaSyncService Implementation
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
class SchemaSyncService {
|
export class SchemaSyncService {
|
||||||
private db: DatabaseAdapter;
|
private db: DatabaseAdapter;
|
||||||
private isPostgres: boolean;
|
private isPostgres: boolean;
|
||||||
private baseUrl: string;
|
private baseUrl: string;
|
||||||
@@ -342,7 +342,7 @@ class SchemaSyncService {
|
|||||||
|
|
||||||
// CRITICAL: Jira sometimes returns type=1 (integer) for reference attributes!
|
// CRITICAL: Jira sometimes returns type=1 (integer) for reference attributes!
|
||||||
// The presence of referenceObjectTypeId is the true indicator of a reference type.
|
// The presence of referenceObjectTypeId is the true indicator of a reference type.
|
||||||
const refTypeId = attr.referenceObjectTypeId || attr.referenceObject?.id || attr.referenceType?.id;
|
const refTypeId = attr.referenceObjectTypeId || attr.referenceObjectType?.id || attr.referenceType?.id;
|
||||||
if (refTypeId) {
|
if (refTypeId) {
|
||||||
type = 'reference';
|
type = 'reference';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export class ServiceFactory {
|
|||||||
this.cacheRepo = new ObjectCacheRepository(db);
|
this.cacheRepo = new ObjectCacheRepository(db);
|
||||||
|
|
||||||
// Initialize services
|
// Initialize services
|
||||||
this.schemaSyncService = new SchemaSyncService(this.schemaRepo);
|
this.schemaSyncService = new SchemaSyncService();
|
||||||
this.objectSyncService = new ObjectSyncService(this.schemaRepo, this.cacheRepo);
|
this.objectSyncService = new ObjectSyncService(this.schemaRepo, this.cacheRepo);
|
||||||
this.payloadProcessor = new PayloadProcessor(this.schemaRepo, this.cacheRepo);
|
this.payloadProcessor = new PayloadProcessor(this.schemaRepo, this.cacheRepo);
|
||||||
this.queryService = new QueryService(this.schemaRepo, this.cacheRepo);
|
this.queryService = new QueryService(this.schemaRepo, this.cacheRepo);
|
||||||
|
|||||||
@@ -105,8 +105,9 @@ export async function loadBIAData(): Promise<BIARecord[]> {
|
|||||||
// Read file using readFileSync and then parse with ExcelJS
|
// Read file using readFileSync and then parse with ExcelJS
|
||||||
const fileBuffer = readFileSync(biaFilePath);
|
const fileBuffer = readFileSync(biaFilePath);
|
||||||
const workbook = new ExcelJS.Workbook();
|
const workbook = new ExcelJS.Workbook();
|
||||||
// ExcelJS accepts Buffer, but TypeScript types may be strict - use type assertion
|
// ExcelJS accepts Buffer, but TypeScript types may be strict
|
||||||
await workbook.xlsx.load(fileBuffer as Buffer);
|
// Use type assertion to satisfy TypeScript's strict Buffer type checking
|
||||||
|
await workbook.xlsx.load(fileBuffer as any);
|
||||||
const worksheet = workbook.worksheets[0]; // First sheet
|
const worksheet = workbook.worksheets[0]; // First sheet
|
||||||
|
|
||||||
// Converteer naar 2D array formaat (zoals xlsx.utils.sheet_to_json met header: 1)
|
// Converteer naar 2D array formaat (zoals xlsx.utils.sheet_to_json met header: 1)
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class CMDBService {
|
|||||||
|
|
||||||
if (result.objects.length === 0) return null;
|
if (result.objects.length === 0) return null;
|
||||||
|
|
||||||
const parsed = jiraAssetsClient.parseObject<T>(result.objects[0]);
|
const parsed = await jiraAssetsClient.parseObject<T>(result.objects[0]);
|
||||||
if (parsed) {
|
if (parsed) {
|
||||||
await cacheStore.upsertObject(typeName, parsed);
|
await cacheStore.upsertObject(typeName, parsed);
|
||||||
await cacheStore.extractAndStoreRelations(typeName, parsed);
|
await cacheStore.extractAndStoreRelations(typeName, parsed);
|
||||||
@@ -212,7 +212,7 @@ class CMDBService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const batchResults = await Promise.all(batchPromises);
|
const batchResults = await Promise.all(batchPromises);
|
||||||
const validResults = batchResults.filter((obj): obj is T => obj !== null);
|
const validResults = batchResults.filter((obj): obj is NonNullable<typeof obj> => obj !== null) as T[];
|
||||||
results.push(...validResults);
|
results.push(...validResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
import { logger } from '../logger.js';
|
import { logger } from '../logger.js';
|
||||||
import { normalizedCacheStore } from '../normalizedCacheStore.js';
|
import { normalizedCacheStore } from '../normalizedCacheStore.js';
|
||||||
|
import type { DatabaseAdapter } from './interface.js';
|
||||||
|
|
||||||
export async function migrateToNormalizedSchema(): Promise<void> {
|
export async function migrateToNormalizedSchema(): Promise<void> {
|
||||||
const db = (normalizedCacheStore as any).db;
|
const db = (normalizedCacheStore as any).db;
|
||||||
@@ -23,7 +24,7 @@ export async function migrateToNormalizedSchema(): Promise<void> {
|
|||||||
logger.info('Migration: Starting migration to normalized schema structure...');
|
logger.info('Migration: Starting migration to normalized schema structure...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.transaction(async (txDb) => {
|
await db.transaction(async (txDb: DatabaseAdapter) => {
|
||||||
// Step 1: Check if configured_object_types table exists
|
// Step 1: Check if configured_object_types table exists
|
||||||
let configuredTableExists = false;
|
let configuredTableExists = false;
|
||||||
try {
|
try {
|
||||||
@@ -172,14 +173,14 @@ export async function migrateToNormalizedSchema(): Promise<void> {
|
|||||||
FROM information_schema.columns
|
FROM information_schema.columns
|
||||||
WHERE table_schema = 'public' AND table_name = 'object_types'
|
WHERE table_schema = 'public' AND table_name = 'object_types'
|
||||||
`);
|
`);
|
||||||
hasSchemaId = columns.some(c => c.column_name === 'schema_id');
|
hasSchemaId = columns.some((c: { column_name: string }) => c.column_name === 'schema_id');
|
||||||
hasEnabled = columns.some(c => c.column_name === 'enabled');
|
hasEnabled = columns.some((c: { column_name: string }) => c.column_name === 'enabled');
|
||||||
} else {
|
} else {
|
||||||
const tableInfo = await txDb.query<{ name: string }>(`
|
const tableInfo = await txDb.query<{ name: string }>(`
|
||||||
PRAGMA table_info(object_types)
|
PRAGMA table_info(object_types)
|
||||||
`);
|
`);
|
||||||
hasSchemaId = tableInfo.some(c => c.name === 'schema_id');
|
hasSchemaId = tableInfo.some((c: { name: string }) => c.name === 'schema_id');
|
||||||
hasEnabled = tableInfo.some(c => c.name === 'enabled');
|
hasEnabled = tableInfo.some((c: { name: string }) => c.name === 'enabled');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn('Migration: Could not check object_types columns', error);
|
logger.warn('Migration: Could not check object_types columns', error);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import type {
|
|||||||
ApplicationUpdateRequest,
|
ApplicationUpdateRequest,
|
||||||
TeamDashboardData,
|
TeamDashboardData,
|
||||||
} from '../types/index.js';
|
} from '../types/index.js';
|
||||||
|
import type { CMDBObjectTypeName } from '../generated/jira-types.js';
|
||||||
|
|
||||||
// Attribute name mappings (these should match your Jira Assets schema)
|
// Attribute name mappings (these should match your Jira Assets schema)
|
||||||
const ATTRIBUTE_NAMES = {
|
const ATTRIBUTE_NAMES = {
|
||||||
@@ -149,7 +150,7 @@ class JiraAssetsService {
|
|||||||
`SELECT DISTINCT jira_schema_id FROM schemas WHERE search_enabled = ? ORDER BY jira_schema_id`,
|
`SELECT DISTINCT jira_schema_id FROM schemas WHERE search_enabled = ? ORDER BY jira_schema_id`,
|
||||||
[db.isPostgres ? true : 1]
|
[db.isPostgres ? true : 1]
|
||||||
);
|
);
|
||||||
return schemaRows.map(row => row.jira_schema_id);
|
return schemaRows.map((row: { jira_schema_id: string }) => row.jira_schema_id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn('JiraAssets: Failed to get all schema IDs', error);
|
logger.warn('JiraAssets: Failed to get all schema IDs', error);
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { schemaDiscoveryService } from './schemaDiscoveryService.js';
|
|||||||
import { queryBuilder } from './queryBuilder.js';
|
import { queryBuilder } from './queryBuilder.js';
|
||||||
import { NORMALIZED_SCHEMA_POSTGRES, NORMALIZED_SCHEMA_SQLITE } from './database/normalized-schema.js';
|
import { NORMALIZED_SCHEMA_POSTGRES, NORMALIZED_SCHEMA_SQLITE } from './database/normalized-schema.js';
|
||||||
import type { AttributeDefinition } from '../generated/jira-schema.js';
|
import type { AttributeDefinition } from '../generated/jira-schema.js';
|
||||||
|
import { jiraAssetsClient } from './jiraAssetsClient.js';
|
||||||
|
|
||||||
// Re-export interfaces for compatibility
|
// Re-export interfaces for compatibility
|
||||||
export interface CacheStats {
|
export interface CacheStats {
|
||||||
@@ -246,7 +247,7 @@ class NormalizedCacheStore {
|
|||||||
rows.map(row => this.reconstructObject<T>(row.id, typeName))
|
rows.map(row => this.reconstructObject<T>(row.id, typeName))
|
||||||
);
|
);
|
||||||
|
|
||||||
const validObjects = objects.filter((obj): obj is T => obj !== null);
|
const validObjects = objects.filter((obj): obj is NonNullable<typeof obj> => obj !== null) as T[];
|
||||||
logger.debug(`NormalizedCacheStore: Successfully reconstructed ${validObjects.length}/${rows.length} objects of type ${typeName}`);
|
logger.debug(`NormalizedCacheStore: Successfully reconstructed ${validObjects.length}/${rows.length} objects of type ${typeName}`);
|
||||||
|
|
||||||
return validObjects;
|
return validObjects;
|
||||||
@@ -294,7 +295,7 @@ class NormalizedCacheStore {
|
|||||||
rows.map(row => this.reconstructObject<T>(row.id, typeName))
|
rows.map(row => this.reconstructObject<T>(row.id, typeName))
|
||||||
);
|
);
|
||||||
|
|
||||||
return objects.filter((obj): obj is T => obj !== null);
|
return objects.filter((obj): obj is NonNullable<typeof obj> => obj !== null) as T[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1129,8 +1130,9 @@ class NormalizedCacheStore {
|
|||||||
`, [id]);
|
`, [id]);
|
||||||
|
|
||||||
if (row) {
|
if (row) {
|
||||||
this.referenceCache.set(id, row);
|
const cached = { id: row.id, objectKey: row.object_key, label: row.label };
|
||||||
return row;
|
this.referenceCache.set(id, cached);
|
||||||
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -1190,7 +1192,7 @@ class NormalizedCacheStore {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
objects: objects.filter((obj): obj is T => obj !== null),
|
objects: objects.filter((obj): obj is NonNullable<typeof obj> => obj !== null) as T[],
|
||||||
total
|
total
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1363,7 +1365,7 @@ class NormalizedCacheStore {
|
|||||||
rows.map(row => this.reconstructObject<T>(row.id, targetTypeName))
|
rows.map(row => this.reconstructObject<T>(row.id, targetTypeName))
|
||||||
);
|
);
|
||||||
|
|
||||||
return objects.filter((obj): obj is T => obj !== null);
|
return objects.filter((obj): obj is NonNullable<typeof obj> => obj !== null) as T[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1424,7 +1426,7 @@ class NormalizedCacheStore {
|
|||||||
rows.map(row => this.reconstructObject<T>(row.id, sourceTypeName))
|
rows.map(row => this.reconstructObject<T>(row.id, sourceTypeName))
|
||||||
);
|
);
|
||||||
|
|
||||||
const validObjects = objects.filter((obj): obj is T => obj !== null);
|
const validObjects = objects.filter((obj): obj is NonNullable<typeof obj> => obj !== null) as T[];
|
||||||
logger.debug(`NormalizedCacheStore: Successfully reconstructed ${validObjects.length} objects`);
|
logger.debug(`NormalizedCacheStore: Successfully reconstructed ${validObjects.length} objects`);
|
||||||
|
|
||||||
return validObjects;
|
return validObjects;
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class SchemaMappingService {
|
|||||||
ORDER BY object_type_name
|
ORDER BY object_type_name
|
||||||
`);
|
`);
|
||||||
|
|
||||||
return rows.map(row => ({
|
return rows.map((row: { object_type_name: string; schema_id: string; enabled: boolean | number; created_at: string; updated_at: string }) => ({
|
||||||
objectTypeName: row.object_type_name,
|
objectTypeName: row.object_type_name,
|
||||||
schemaId: row.schema_id,
|
schemaId: row.schema_id,
|
||||||
enabled: typeof row.enabled === 'boolean' ? row.enabled : row.enabled === 1,
|
enabled: typeof row.enabled === 'boolean' ? row.enabled : row.enabled === 1,
|
||||||
@@ -283,7 +283,7 @@ class SchemaMappingService {
|
|||||||
logger.warn('SchemaMapping: Failed to get default schema ID from database', error);
|
logger.warn('SchemaMapping: Failed to get default schema ID from database', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rows.map(row => ({
|
return rows.map((row: { type_name: string; display_name: string; description: string | null; schema_id: string; enabled: boolean | number; created_at: string; updated_at: string }) => ({
|
||||||
typeName: row.type_name,
|
typeName: row.type_name,
|
||||||
displayName: row.display_name,
|
displayName: row.display_name,
|
||||||
description: row.description,
|
description: row.description,
|
||||||
@@ -291,8 +291,8 @@ class SchemaMappingService {
|
|||||||
enabled: row.enabled === null
|
enabled: row.enabled === null
|
||||||
? true // Default: enabled if no mapping exists
|
? true // Default: enabled if no mapping exists
|
||||||
: (typeof row.enabled === 'boolean' ? row.enabled : row.enabled === 1),
|
: (typeof row.enabled === 'boolean' ? row.enabled : row.enabled === 1),
|
||||||
objectCount: row.object_count,
|
objectCount: (row as any).object_count || 0,
|
||||||
syncPriority: row.sync_priority,
|
syncPriority: (row as any).sync_priority || 0,
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('SchemaMappingService: Failed to get object types with config', error);
|
logger.error('SchemaMappingService: Failed to get object types with config', error);
|
||||||
|
|||||||
Reference in New Issue
Block a user