/** * ObjectsController - API handlers for object operations * * NO SQL, NO parsing - delegates to services. */ import { Request, Response } from 'express'; import { logger } from '../../services/logger.js'; import { getServices } from '../../services/ServiceFactory.js'; import type { CMDBObject, CMDBObjectTypeName } from '../../generated/jira-types.js'; import { getParamString, getQueryString, getQueryNumber } from '../../utils/queryHelpers.js'; export class ObjectsController { /** * Get a single object by ID or objectKey * GET /api/v2/objects/:type/:id?refresh=true * Supports both object ID and objectKey (checks objectKey if ID lookup fails) */ async getObject(req: Request, res: Response): Promise { try { const type = getParamString(req, 'type'); const idOrKey = getParamString(req, 'id'); const forceRefresh = getQueryString(req, 'refresh') === 'true'; const services = getServices(); // Try to find object ID if idOrKey might be an objectKey let objectId = idOrKey; let objRecord = await services.cacheRepo.getObject(idOrKey); if (!objRecord) { // Try as objectKey objRecord = await services.cacheRepo.getObjectByKey(idOrKey); if (objRecord) { objectId = objRecord.id; } } // Force refresh if requested if (forceRefresh && objectId) { const enabledTypes = await services.schemaRepo.getEnabledObjectTypes(); const enabledTypeSet = new Set(enabledTypes.map(t => t.typeName)); const refreshResult = await services.refreshService.refreshObject(objectId, enabledTypeSet); if (!refreshResult.success) { res.status(500).json({ error: refreshResult.error || 'Failed to refresh object' }); return; } } // Get from cache if (!objectId) { res.status(404).json({ error: 'Object not found (by ID or key)' }); return; } const object = await services.queryService.getObject(type as CMDBObjectTypeName, objectId); if (!object) { res.status(404).json({ error: 'Object not found' }); return; } res.json(object); } catch (error) { logger.error('ObjectsController: Failed to get object', error); res.status(500).json({ error: 'Failed to get object' }); } } /** * Get all objects of a type * GET /api/v2/objects/:type?limit=100&offset=0&search=term */ async getObjects(req: Request, res: Response): Promise { try { const type = getParamString(req, 'type'); const limit = getQueryNumber(req, 'limit', 1000); const offset = getQueryNumber(req, 'offset', 0); const search = getQueryString(req, 'search'); const services = getServices(); logger.info(`ObjectsController.getObjects: Querying for type="${type}" with limit=${limit}, offset=${offset}, search=${search || 'none'}`); let objects: CMDBObject[]; if (search) { objects = await services.queryService.searchByLabel( type as CMDBObjectTypeName, search, { limit, offset } ); } else { objects = await services.queryService.getObjects( type as CMDBObjectTypeName, { limit, offset } ); } const totalCount = await services.queryService.countObjects(type as CMDBObjectTypeName); logger.info(`ObjectsController.getObjects: Found ${objects.length} objects of type "${type}" (total count: ${totalCount})`); // If no objects found, provide diagnostic information if (objects.length === 0) { // Check what object types actually exist in the database const db = services.cacheRepo.db; try { const availableTypes = await db.query<{ object_type_name: string; count: number }>( `SELECT object_type_name, COUNT(*) as count FROM objects GROUP BY object_type_name ORDER BY count DESC LIMIT 10` ); if (availableTypes.length > 0) { logger.warn(`ObjectsController.getObjects: No objects found for type "${type}". Available types in database:`, { requestedType: type, availableTypes: availableTypes.map(t => ({ typeName: t.object_type_name, count: t.count })), }); } } catch (error) { logger.debug('ObjectsController.getObjects: Failed to query available types', error); } } res.json({ objectType: type, objects, count: objects.length, totalCount, offset, limit, }); } catch (error) { logger.error('ObjectsController: Failed to get objects', error); res.status(500).json({ error: 'Failed to get objects' }); } } /** * Update an object * PUT /api/v2/objects/:type/:id */ async updateObject(req: Request, res: Response): Promise { try { const type = getParamString(req, 'type'); const id = getParamString(req, 'id'); const updates = req.body as Record; const services = getServices(); const result = await services.writeThroughService.updateObject( type as CMDBObjectTypeName, id, updates ); if (!result.success) { res.status(400).json({ error: result.error || 'Failed to update object' }); return; } // Fetch updated object const updated = await services.queryService.getObject( type as CMDBObjectTypeName, id ); res.json(updated || { success: true }); } catch (error) { logger.error('ObjectsController: Failed to update object', error); res.status(500).json({ error: 'Failed to update object' }); } } }