import { Router } from 'express'; import { schemaCacheService } from '../services/schemaCacheService.js'; import { schemaSyncService } from '../services/SchemaSyncService.js'; import { schemaMappingService } from '../services/schemaMappingService.js'; import { logger } from '../services/logger.js'; import { jiraAssetsClient } from '../services/jiraAssetsClient.js'; import { requireAuth, requirePermission } from '../middleware/authorization.js'; const router = Router(); // All routes require authentication and search permission router.use(requireAuth); router.use(requirePermission('search')); /** * GET /api/schema * Returns the complete Jira Assets schema with object types, attributes, and links * Data is fetched from database (via cache service) */ router.get('/', async (req, res) => { try { // Get schema from cache (which fetches from database) const schema = await schemaCacheService.getSchema(); // Optionally fetch Jira counts for comparison (can be slow, so make it optional) let jiraCounts: Record | undefined; const includeJiraCounts = req.query.includeJiraCounts === 'true'; if (includeJiraCounts) { const typeNames = Object.keys(schema.objectTypes); logger.info(`Schema: Fetching object counts from Jira Assets for ${typeNames.length} object types...`); jiraCounts = {}; // Fetch counts in parallel for better performance, using schema mappings const countPromises = typeNames.map(async (typeName) => { try { // Get schema ID for this type const schemaId = await schemaMappingService.getSchemaId(typeName); const count = await jiraAssetsClient.getObjectCount(typeName, schemaId); jiraCounts![typeName] = count; return { typeName, count }; } catch (error) { logger.warn(`Schema: Failed to get count for ${typeName}`, error); // Use 0 as fallback if API call fails jiraCounts![typeName] = 0; return { typeName, count: 0 }; } }); await Promise.all(countPromises); logger.info(`Schema: Fetched counts for ${Object.keys(jiraCounts).length} object types from Jira Assets`); } const response = { ...schema, jiraCounts, }; res.json(response); } catch (error) { logger.error('Failed to get schema:', error); res.status(500).json({ error: 'Failed to get schema' }); } }); /** * GET /api/schema/object-type/:typeName * Returns details for a specific object type */ router.get('/object-type/:typeName', async (req, res) => { try { const { typeName } = req.params; // Get schema from cache const schema = await schemaCacheService.getSchema(); const typeDef = schema.objectTypes[typeName]; if (!typeDef) { return res.status(404).json({ error: `Object type '${typeName}' not found` }); } res.json(typeDef); } catch (error) { logger.error('Failed to get object type:', error); res.status(500).json({ error: 'Failed to get object type' }); } }); /** * POST /api/schema/discover * Manually trigger schema synchronization from Jira API * Requires manage_settings permission */ router.post('/discover', requirePermission('manage_settings'), async (req, res) => { try { logger.info('Schema: Manual schema sync triggered'); const result = await schemaSyncService.syncAll(); schemaCacheService.invalidate(); // Invalidate cache res.json({ message: 'Schema synchronization completed', ...result, }); } catch (error) { logger.error('Failed to sync schema:', error); res.status(500).json({ error: 'Failed to sync schema', details: error instanceof Error ? error.message : String(error), }); } }); /** * GET /api/schema/sync-progress * Get current sync progress */ router.get('/sync-progress', requirePermission('manage_settings'), async (req, res) => { try { const progress = schemaSyncService.getProgress(); res.json(progress); } catch (error) { logger.error('Failed to get sync progress:', error); res.status(500).json({ error: 'Failed to get sync progress' }); } }); export default router;