- Restore blue PageHeader on Dashboard (/app-components) - Update homepage (/) with subtle header design without blue bar - Add uniform PageHeader styling to application edit page - Fix Rapporten link on homepage to point to /reports overview - Improve header descriptions spacing for better readability
177 lines
5.7 KiB
TypeScript
177 lines
5.7 KiB
TypeScript
/**
|
|
* 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<void> {
|
|
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<CMDBObject>(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<void> {
|
|
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<CMDBObject>(
|
|
type as CMDBObjectTypeName,
|
|
search,
|
|
{ limit, offset }
|
|
);
|
|
} else {
|
|
objects = await services.queryService.getObjects<CMDBObject>(
|
|
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<void> {
|
|
try {
|
|
const type = getParamString(req, 'type');
|
|
const id = getParamString(req, 'id');
|
|
const updates = req.body as Record<string, unknown>;
|
|
|
|
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<CMDBObject>(
|
|
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' });
|
|
}
|
|
}
|
|
}
|