@hamak/smart-data-dico 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +292 -0
  2. package/backend/package.json +51 -0
  3. package/backend/src/__tests__/integration/api.test.ts +149 -0
  4. package/backend/src/__tests__/setup.ts +24 -0
  5. package/backend/src/__tests__/utils/testUtils.ts +76 -0
  6. package/backend/src/adapters/EntityFileAdapter.ts +154 -0
  7. package/backend/src/adapters/YamlFileInfoEnricher.ts +52 -0
  8. package/backend/src/controllers/authController.ts +131 -0
  9. package/backend/src/controllers/diagramController.ts +143 -0
  10. package/backend/src/controllers/dictionaryController.ts +306 -0
  11. package/backend/src/controllers/importExportController.ts +64 -0
  12. package/backend/src/controllers/perspectiveController.ts +90 -0
  13. package/backend/src/controllers/serviceController.ts +418 -0
  14. package/backend/src/controllers/stereotypeController.ts +59 -0
  15. package/backend/src/controllers/versionController.ts +226 -0
  16. package/backend/src/kernel/config.ts +43 -0
  17. package/backend/src/middleware/auth.ts +128 -0
  18. package/backend/src/middleware/jwtAuth.ts +100 -0
  19. package/backend/src/models/Dictionary.ts +38 -0
  20. package/backend/src/models/EntitySchema.ts +393 -0
  21. package/backend/src/models/__tests__/Dictionary.test.ts +92 -0
  22. package/backend/src/models/__tests__/EntitySchema.test.ts +119 -0
  23. package/backend/src/routes/index.ts +120 -0
  24. package/backend/src/scripts/migrate-to-uuid.ts +24 -0
  25. package/backend/src/server.ts +140 -0
  26. package/backend/src/services/__mocks__/entityService.ts +38 -0
  27. package/backend/src/services/__mocks__/serviceService.ts +88 -0
  28. package/backend/src/services/__mocks__/versionService.ts +38 -0
  29. package/backend/src/services/__tests__/dictionaryService.test.ts +74 -0
  30. package/backend/src/services/diagramService.ts +165 -0
  31. package/backend/src/services/dictionaryService.ts +582 -0
  32. package/backend/src/services/entityService.ts +102 -0
  33. package/backend/src/services/exportService.ts +172 -0
  34. package/backend/src/services/importService.ts +208 -0
  35. package/backend/src/services/perspectiveService.ts +276 -0
  36. package/backend/src/services/qualityService.ts +121 -0
  37. package/backend/src/services/serviceService.ts +763 -0
  38. package/backend/src/services/stereotypeService.ts +98 -0
  39. package/backend/src/services/versionService.ts +135 -0
  40. package/backend/src/setupTests.ts +12 -0
  41. package/backend/src/utils/__mocks__/fileOperations.ts +116 -0
  42. package/backend/src/utils/fileOperations.ts +602 -0
  43. package/backend/src/utils/logger.ts +38 -0
  44. package/backend/src/utils/migration.ts +254 -0
  45. package/backend/src/utils/swagger.ts +358 -0
  46. package/backend/src/utils/uuid.ts +41 -0
  47. package/backend/tsconfig.json +20 -0
  48. package/bin/cli.js +129 -0
  49. package/data-dictionaries/stereotypes.yaml +91 -0
  50. package/package.json +42 -0
@@ -0,0 +1,92 @@
1
+ import { Dictionary, DictionaryEntry } from '../Dictionary.js';
2
+
3
+ describe('Dictionary Model', () => {
4
+ describe('Dictionary Interface', () => {
5
+ it('should create a valid Dictionary object', () => {
6
+ const dictionary: Dictionary = {
7
+ id: 'test-dict-1',
8
+ name: 'Test Dictionary',
9
+ description: 'A test dictionary',
10
+ version: '1.0.0',
11
+ createdAt: new Date(),
12
+ updatedAt: new Date(),
13
+ rootPackage: { id: 'root', name: 'Root', entities: [], subPackages: [] },
14
+ };
15
+
16
+ expect(dictionary).toBeDefined();
17
+ expect(dictionary.id).toBe('test-dict-1');
18
+ expect(dictionary.name).toBe('Test Dictionary');
19
+ expect(dictionary.description).toBe('A test dictionary');
20
+ expect(dictionary.version).toBe('1.0.0');
21
+ expect(dictionary.createdAt).toBeInstanceOf(Date);
22
+ expect(dictionary.updatedAt).toBeInstanceOf(Date);
23
+ });
24
+
25
+ it('should create a Dictionary with only required fields', () => {
26
+ const dictionary: Dictionary = {
27
+ id: 'test-dict-2',
28
+ name: 'Minimal Dictionary',
29
+ rootPackage: { id: 'root', name: 'Root', entities: [], subPackages: [] },
30
+ };
31
+
32
+ expect(dictionary).toBeDefined();
33
+ expect(dictionary.id).toBe('test-dict-2');
34
+ expect(dictionary.name).toBe('Minimal Dictionary');
35
+ expect(dictionary.description).toBeUndefined();
36
+ expect(dictionary.version).toBeUndefined();
37
+ expect(dictionary.createdAt).toBeUndefined();
38
+ expect(dictionary.updatedAt).toBeUndefined();
39
+ });
40
+ });
41
+
42
+ describe('DictionaryEntry Interface', () => {
43
+ it('should create a valid DictionaryEntry object', () => {
44
+ const entry: DictionaryEntry = {
45
+ id: 'entry-1',
46
+ name: 'Test Entry',
47
+ description: 'A test entry',
48
+ type: 'string',
49
+ format: 'email',
50
+ required: true,
51
+ defaultValue: 'test@example.com',
52
+ examples: ['user@example.com', 'admin@example.com'],
53
+ metadata: {
54
+ source: 'user',
55
+ lastUpdated: '2023-01-01',
56
+ },
57
+ };
58
+
59
+ expect(entry).toBeDefined();
60
+ expect(entry.id).toBe('entry-1');
61
+ expect(entry.name).toBe('Test Entry');
62
+ expect(entry.description).toBe('A test entry');
63
+ expect(entry.type).toBe('string');
64
+ expect(entry.format).toBe('email');
65
+ expect(entry.required).toBe(true);
66
+ expect(entry.defaultValue).toBe('test@example.com');
67
+ expect(entry.examples).toHaveLength(2);
68
+ expect(entry.examples).toContain('user@example.com');
69
+ expect(entry.metadata).toHaveProperty('source', 'user');
70
+ });
71
+
72
+ it('should create a DictionaryEntry with only required fields', () => {
73
+ const entry: DictionaryEntry = {
74
+ id: 'entry-2',
75
+ name: 'Minimal Entry',
76
+ description: 'A minimal entry',
77
+ type: 'number',
78
+ };
79
+
80
+ expect(entry).toBeDefined();
81
+ expect(entry.id).toBe('entry-2');
82
+ expect(entry.name).toBe('Minimal Entry');
83
+ expect(entry.description).toBe('A minimal entry');
84
+ expect(entry.type).toBe('number');
85
+ expect(entry.format).toBeUndefined();
86
+ expect(entry.required).toBeUndefined();
87
+ expect(entry.defaultValue).toBeUndefined();
88
+ expect(entry.examples).toBeUndefined();
89
+ expect(entry.metadata).toBeUndefined();
90
+ });
91
+ });
92
+ });
@@ -0,0 +1,119 @@
1
+ import { AttributeType, validateEntity } from '../EntitySchema.js';
2
+
3
+ describe('EntitySchema', () => {
4
+ describe('validateEntity', () => {
5
+ it('should validate a valid entity', () => {
6
+ const validEntity = {
7
+ uuid: 'a38d1597-cc4f-4934-bb08-c876c023f693',
8
+ name: 'Test Entity',
9
+ attributes: [
10
+ {
11
+ uuid: 'b49e2608-dd5f-4045-aa09-d464c234e694',
12
+ name: 'id',
13
+ description: 'Primary identifier',
14
+ type: AttributeType.STRING,
15
+ required: true,
16
+ primaryKey: true,
17
+ },
18
+ {
19
+ uuid: 'c5af3719-ee6f-4156-bb1a-e575d345f7a5',
20
+ name: 'name',
21
+ description: 'Entity name',
22
+ type: AttributeType.STRING,
23
+ required: true,
24
+ },
25
+ ],
26
+ };
27
+
28
+ const result = validateEntity(validEntity);
29
+ expect(result.valid).toBe(true);
30
+ expect(result.errors).toHaveLength(0);
31
+ });
32
+
33
+ it('should invalidate an entity with missing required fields', () => {
34
+ const invalidEntity = {
35
+ // Missing uuid
36
+ // Missing name
37
+ description: 'A test entity',
38
+ attributes: [],
39
+ };
40
+
41
+ const result = validateEntity(invalidEntity as any);
42
+ expect(result.valid).toBe(false);
43
+ expect(result.errors.length).toBeGreaterThan(0);
44
+ });
45
+
46
+ it('should validate an entity with constraints', () => {
47
+ const entityWithConstraints = {
48
+ uuid: 'd6b0482a-ff70-4267-8c2b-f686e456f8b6',
49
+ name: 'Test Entity',
50
+ description: 'A test entity',
51
+ attributes: [
52
+ {
53
+ uuid: 'e7c1593b-aa81-4378-9d3c-a797f567a9c7',
54
+ name: 'email',
55
+ description: 'Email address',
56
+ type: AttributeType.STRING,
57
+ required: true,
58
+ constraints: {
59
+ maxLength: 255,
60
+ pattern: '^[^@]+@[^@]+$',
61
+ format: 'email',
62
+ },
63
+ },
64
+ ],
65
+ };
66
+
67
+ const result = validateEntity(entityWithConstraints);
68
+ expect(result.valid).toBe(true);
69
+ expect(result.errors).toHaveLength(0);
70
+ });
71
+
72
+ it('should invalidate an entity with invalid attribute type', () => {
73
+ const entityWithInvalidAttribute = {
74
+ uuid: 'a38d1597-cc4f-4934-bb08-c876c023f693',
75
+ name: 'Test Entity',
76
+ description: 'A test entity',
77
+ attributes: [
78
+ {
79
+ uuid: 'b49e2608-dd5f-4045-aa09-d464c234e694',
80
+ name: 'id',
81
+ description: 'Primary identifier',
82
+ type: 'invalid-type', // Invalid type
83
+ required: true,
84
+ },
85
+ ],
86
+ };
87
+
88
+ const result = validateEntity(entityWithInvalidAttribute as any);
89
+ expect(result.valid).toBe(false);
90
+ expect(result.errors.length).toBeGreaterThan(0);
91
+ });
92
+
93
+ it('should validate an entity with metadata entries', () => {
94
+ const entityWithMetadata = {
95
+ uuid: 'a38d1597-cc4f-4934-bb08-c876c023f693',
96
+ name: 'Test Entity',
97
+ attributes: [
98
+ {
99
+ uuid: 'b49e2608-dd5f-4045-aa09-d464c234e694',
100
+ name: 'id',
101
+ description: 'Primary identifier',
102
+ type: AttributeType.STRING,
103
+ required: true,
104
+ metadata: [
105
+ { name: 'sensitive', value: true },
106
+ ],
107
+ },
108
+ ],
109
+ metadata: [
110
+ { name: 'owner', value: 'team-a' },
111
+ ],
112
+ };
113
+
114
+ const result = validateEntity(entityWithMetadata);
115
+ expect(result.valid).toBe(true);
116
+ expect(result.errors).toHaveLength(0);
117
+ });
118
+ });
119
+ });
@@ -0,0 +1,120 @@
1
+ import { Router } from 'express';
2
+
3
+ import { getCurrentUser, login } from '../controllers/authController.js';
4
+ import { diagramController } from '../controllers/diagramController.js';
5
+ import { createDictionary, getDictionaries, getDictionaryById, getDictionaryEntries, getEntityAttributes, getPackageByPath, getPackageHierarchy, getRelatedEntities, getTabularData, saveEntity, listAllPackagesAndEntities, getFlatEntitiesAndAttributes, getEntityHierarchy, createRootPackage, createPackageAtPath, updatePackageAtPath, deletePackageAtPath } from '../controllers/dictionaryController.js';
6
+ import { createEntity, deleteEntity, getAllServices, getEntitySchema, getGraphData, getServiceEntities, searchEntities, updateEntity, getPackageRelationships, createRelationship, updateRelationship, deleteRelationship, getImpactAnalysis, getLineage, submitEntity, approveEntity, returnEntity, getEntityComments, addEntityComment, resolveEntityComment } from '../controllers/serviceController.js';
7
+ import { getAllStereotypes, getStereotype, createStereotype, updateStereotype, deleteStereotype } from '../controllers/stereotypeController.js';
8
+ import { getAllPerspectives, getPerspective, createPerspective, updatePerspective, deletePerspective, resolvePerspective, getPerspectiveGraph, upsertPerspectiveNode } from '../controllers/perspectiveController.js';
9
+ import { commitChanges, getCommitHistory, revertToCommit } from '../controllers/versionController.js';
10
+ import { importJsonSchema, importSqlDdl, exportJsonSchema, exportMarkdown, getQualityReport } from '../controllers/importExportController.js';
11
+ import { authenticate, UserRole } from '../middleware/auth.js';
12
+ import { authorizeJwt, verifyToken } from '../middleware/jwtAuth.js';
13
+
14
+ const router = Router();
15
+
16
+ // API status route
17
+ router.get('/api/status', (req, res) => {
18
+ res.json({ status: 'operational' });
19
+ });
20
+
21
+ router.get('/api/packages/hierarchy/:rootPackage', getPackageHierarchy);
22
+ router.get('/api/packages/tabular/:rootPackage', getTabularData);
23
+
24
+ // Package CRUD routes
25
+ router.post('/api/packages', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), createRootPackage);
26
+ router.post('/api/packages/:rootPackage/subpackages/*', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), createPackageAtPath);
27
+ router.put('/api/packages/:rootPackage/path/*', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), updatePackageAtPath);
28
+ router.delete('/api/packages/:rootPackage/path/*', authorizeJwt([UserRole.ADMIN]), deletePackageAtPath);
29
+
30
+ router.get('/api/packages/:rootPackage/path/*', getPackageByPath);
31
+
32
+ // Auth routes
33
+ router.post('/api/auth/login', login);
34
+ router.get('/api/auth/me', verifyToken, getCurrentUser);
35
+
36
+ // Legacy Dictionary routes
37
+ router.get('/api/dictionaries', getDictionaries);
38
+ router.post('/api/dictionaries', createDictionary);
39
+ router.get('/api/dictionaries/:id', getDictionaryById);
40
+ router.get('/api/dictionaries/:id/entries', getDictionaryEntries);
41
+ router.get('/api/entities/:microservice/:entityName/attributes', getEntityAttributes);
42
+ router.get('/api/entities/:microservice/:entityName/related', getRelatedEntities);
43
+ router.post('/api/entities', saveEntity);
44
+
45
+ // Data Dictionary/Entity/Package API extensions
46
+ router.get('/api/packages/all', listAllPackagesAndEntities);
47
+ router.get('/api/entities/flat', getFlatEntitiesAndAttributes);
48
+ router.get('/api/entities/hierarchy/:microservice/:entityName', getEntityHierarchy);
49
+
50
+ // New Service/Entity API routes
51
+ router.get('/api/services', getAllServices);
52
+ router.get('/api/services/:service/entities', getServiceEntities);
53
+ router.get('/api/services/:service/entities/:entity', getEntitySchema);
54
+ router.post('/api/services/:service/entities', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), createEntity);
55
+ router.put('/api/services/:service/entities/:entity', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), updateEntity);
56
+ router.delete('/api/services/:service/entities/:entity', authorizeJwt([UserRole.ADMIN]), deleteEntity);
57
+
58
+ // Entity review workflow
59
+ router.post('/api/services/:service/entities/:entity/submit', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), submitEntity);
60
+ router.post('/api/services/:service/entities/:entity/approve', authorizeJwt([UserRole.ADMIN]), approveEntity);
61
+ router.post('/api/services/:service/entities/:entity/return', authorizeJwt([UserRole.ADMIN]), returnEntity);
62
+ router.get('/api/services/:service/entities/:entity/comments', getEntityComments);
63
+ router.post('/api/services/:service/entities/:entity/comments', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), addEntityComment);
64
+ router.put('/api/services/:service/entities/:entity/comments/:id', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), resolveEntityComment);
65
+
66
+ // Package-level relationship CRUD routes
67
+ router.get('/api/packages/:packageName/relationships', getPackageRelationships);
68
+ router.post('/api/packages/:packageName/relationships', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), createRelationship);
69
+ router.put('/api/packages/:packageName/relationships/:uuid', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), updateRelationship);
70
+ router.delete('/api/packages/:packageName/relationships/:uuid', authorizeJwt([UserRole.ADMIN]), deleteRelationship);
71
+
72
+ // Stereotype API
73
+ router.get('/api/stereotypes', getAllStereotypes);
74
+ router.get('/api/stereotypes/:id', getStereotype);
75
+ router.post('/api/stereotypes', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), createStereotype);
76
+ router.put('/api/stereotypes/:id', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), updateStereotype);
77
+ router.delete('/api/stereotypes/:id', authorizeJwt([UserRole.ADMIN]), deleteStereotype);
78
+
79
+ // Perspective API
80
+ router.get('/api/perspectives', getAllPerspectives);
81
+ router.get('/api/perspectives/:id', getPerspective);
82
+ router.post('/api/perspectives', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), createPerspective);
83
+ router.put('/api/perspectives/:id', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), updatePerspective);
84
+ router.delete('/api/perspectives/:id', authorizeJwt([UserRole.ADMIN]), deletePerspective);
85
+ router.get('/api/perspectives/:id/resolve', resolvePerspective);
86
+ router.get('/api/perspectives/:id/graph', getPerspectiveGraph);
87
+ router.put('/api/perspectives/:id/nodes', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), upsertPerspectiveNode);
88
+
89
+ // Search API
90
+ router.get('/api/search', searchEntities);
91
+
92
+ // Impact analysis & lineage
93
+ router.get('/api/entities/:uuid/impact', getImpactAnalysis);
94
+ router.get('/api/entities/:uuid/lineage', getLineage);
95
+
96
+ // Import/Export
97
+ router.post('/api/import/json-schema', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), importJsonSchema);
98
+ router.post('/api/import/sql-ddl', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), importSqlDdl);
99
+ router.get('/api/export/json-schema/:service', exportJsonSchema);
100
+ router.get('/api/export/markdown/:service', exportMarkdown);
101
+
102
+ // Quality
103
+ router.get('/api/quality/report', getQualityReport);
104
+
105
+ // Graph API for visualization
106
+ router.get('/api/graph/:service', getGraphData);
107
+
108
+ // Version control API
109
+ router.post('/api/commit', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), commitChanges);
110
+ router.get('/api/history', getCommitHistory);
111
+ router.post('/api/revert', authorizeJwt([UserRole.ADMIN]), revertToCommit);
112
+
113
+ // Diagram layout API
114
+ router.get('/api/diagrams', diagramController.listDiagramLayouts.bind(diagramController));
115
+ router.post('/api/diagrams', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), diagramController.saveDiagramLayout.bind(diagramController));
116
+ router.get('/api/diagrams/:id', diagramController.loadDiagramLayout.bind(diagramController));
117
+ router.put('/api/diagrams/:id', authorizeJwt([UserRole.ADMIN, UserRole.EDITOR]), diagramController.updateDiagramLayout.bind(diagramController));
118
+ router.delete('/api/diagrams/:id', authorizeJwt([UserRole.ADMIN]), diagramController.deleteDiagramLayout.bind(diagramController));
119
+
120
+ export default router;
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ts-node
2
+
3
+ import { runAllMigrations } from '../utils/migration.js';
4
+ import { logger } from '../utils/logger.js';
5
+
6
+ /**
7
+ * Migration script to convert existing entities to UUID format
8
+ */
9
+ async function main() {
10
+ try {
11
+ logger.info('Starting migration to UUID format...');
12
+ await runAllMigrations();
13
+ logger.info('Migration completed successfully!');
14
+ process.exit(0);
15
+ } catch (error) {
16
+ logger.error('Migration failed:', error);
17
+ process.exit(1);
18
+ }
19
+ }
20
+
21
+ // Run the migration if this script is executed directly
22
+ if (require.main === module) {
23
+ main();
24
+ }
@@ -0,0 +1,140 @@
1
+ import express, { Request, Response } from 'express';
2
+ import path from 'path';
3
+ import cors from 'cors';
4
+ import dotenv from 'dotenv';
5
+ import routes from './routes/index.js';
6
+ import { setupSwagger } from './utils/swagger.js';
7
+ import { logger } from './utils/logger.js';
8
+ import { config } from './kernel/config.js';
9
+ import { initializeFileSystem, getFileRouter } from './adapters/EntityFileAdapter.js';
10
+ import { createYamlFileInfoEnricher } from './adapters/YamlFileInfoEnricher.js';
11
+
12
+ // Load environment variables
13
+ dotenv.config();
14
+
15
+ // Initialize Express app
16
+ const app = express();
17
+ const port = config.port;
18
+
19
+ // Middleware
20
+ app.use(cors());
21
+ // Access logging middleware
22
+ app.use((req: Request, res: Response, next) => {
23
+ const start = process.hrtime();
24
+ res.on('finish', () => {
25
+ const diff = process.hrtime(start);
26
+ const executionTimeMs = Number((diff[0] * 1e3 + diff[1] / 1e6).toFixed(2));
27
+ logger.info('HTTP Access', {
28
+ method: req.method,
29
+ path: req.originalUrl,
30
+ status: res.statusCode,
31
+ executionTimeMs
32
+ });
33
+ });
34
+ next();
35
+ });
36
+
37
+ app.use(express.json());
38
+
39
+ // Basic route
40
+ app.get('/', (req: Request, res: Response) => {
41
+ res.json({ message: 'Welcome to the Data Dictionary Management System API' });
42
+ });
43
+
44
+ // Health check endpoint
45
+ app.get('/health', (req: Request, res: Response) => {
46
+ res.json({ status: 'ok' });
47
+ });
48
+
49
+ // API routes
50
+ app.use(routes);
51
+
52
+ // Setup Swagger documentation
53
+ setupSwagger(app);
54
+
55
+ // =============================================================================
56
+ // Framework Filesystem & Git Routes (Phase 1)
57
+ // =============================================================================
58
+
59
+ async function mountFrameworkRoutes() {
60
+ try {
61
+ // Initialize filesystem framework
62
+ const { enricherRegistry } = await initializeFileSystem();
63
+
64
+ // Register YAML entity enricher
65
+ const yamlEnricher = createYamlFileInfoEnricher();
66
+ enricherRegistry.register(yamlEnricher);
67
+
68
+ // Initialize git service and routes
69
+ try {
70
+ const gitModule = await import('@hamak/ui-remote-git-fs-backend');
71
+ const workspaceRoots = new Map<string, string>([
72
+ ['dictionaries', config.dataDir],
73
+ ]);
74
+
75
+ const gitService = gitModule.createGitService(workspaceRoots);
76
+ const gitEnricher = gitModule.createGitFileInfoEnricher({
77
+ gitService,
78
+ workspaceRoots,
79
+ });
80
+ enricherRegistry.register(gitEnricher);
81
+
82
+ const gitRoutes = gitModule.createGitRoutes({ gitService, debug: !config.isProduction });
83
+ app.use('/api/git', gitRoutes as any);
84
+ app.use('/api/git', gitModule.gitErrorHandler as any);
85
+
86
+ logger.info('Git routes mounted at /api/git');
87
+ } catch (gitError) {
88
+ logger.warn(`Git integration not available: ${gitError}`);
89
+ }
90
+
91
+ // Mount filesystem routes
92
+ const fsRouter = getFileRouter();
93
+ app.use('/fs', fsRouter);
94
+
95
+ logger.info('Filesystem routes mounted at /fs');
96
+ } catch (error) {
97
+ logger.warn(`Framework filesystem not initialized: ${error}`);
98
+ }
99
+ }
100
+
101
+ // Mount framework routes (non-blocking — existing routes work regardless)
102
+ mountFrameworkRoutes().catch((err) => {
103
+ logger.warn(`Failed to mount framework routes: ${err}`);
104
+ });
105
+
106
+
107
+ // Error handling middleware
108
+ app.use((err: any, req: Request, res: Response, next: any) => {
109
+ logger.error(`Unhandled error: ${err.message}`);
110
+ res.status(500).json({
111
+ message: 'Internal server error',
112
+ error: config.isProduction ? undefined : err.message
113
+ });
114
+ });
115
+
116
+ // Serve frontend static files in production
117
+ if (config.isProduction) {
118
+ const publicDir = path.join(process.cwd(), 'public');
119
+ app.use(express.static(publicDir));
120
+ app.get('*', (req, res) => {
121
+ if (!req.path.startsWith('/api') && !req.path.startsWith('/fs') && !req.path.startsWith('/api-docs')) {
122
+ res.sendFile(path.join(publicDir, 'index.html'));
123
+ }
124
+ });
125
+ logger.info(`Serving frontend from ${publicDir}`);
126
+ }
127
+
128
+ // Start server only when run directly (not when imported by tests)
129
+ const isMainModule = process.argv[1] && (
130
+ process.argv[1].endsWith('server.ts') || process.argv[1].endsWith('server.js')
131
+ );
132
+
133
+ if (isMainModule) {
134
+ app.listen(port, () => {
135
+ logger.info(`Server running on port ${port}`);
136
+ logger.info(`API documentation available at http://localhost:${port}/api-docs`);
137
+ });
138
+ }
139
+
140
+ export default app;
@@ -0,0 +1,38 @@
1
+ import { Entity, AttributeType } from '../../models/EntitySchema.js';
2
+
3
+ const mockEntities: Record<string, Entity> = {
4
+ 'user-service.User': {
5
+ uuid: 'a38d1597-cc4f-4934-bb08-c876c023f693',
6
+ name: 'User',
7
+ description: 'User entity',
8
+ attributes: [
9
+ { uuid: 'b49e2608-dd5f-4045-aa09-d464c234e694', name: 'id', description: 'User ID', type: AttributeType.STRING, required: true, primaryKey: true },
10
+ { uuid: 'c5af3719-ee6f-4156-bb1a-e575d345f7a5', name: 'email', description: 'User email', type: AttributeType.STRING, required: true },
11
+ ],
12
+ } as Entity,
13
+ };
14
+
15
+ // Mock entity service matching actual EntityService API
16
+ class EntityServiceMock {
17
+ validateEntity(entity: Entity) {
18
+ return { valid: true, errors: [] as string[] };
19
+ }
20
+
21
+ async validateRelationships(packageName: string, relationships: any[]) {
22
+ return { valid: true, errors: [] as string[] };
23
+ }
24
+
25
+ async saveEntity(entity: Entity, packageName: string) {
26
+ return { success: true, errors: [] as string[] };
27
+ }
28
+
29
+ async getEntity(packageName: string, entityName: string): Promise<Entity | null> {
30
+ return mockEntities[`${packageName}.${entityName}`] || null;
31
+ }
32
+
33
+ async getRelatedEntities(packageName: string, entityName: string): Promise<Entity[]> {
34
+ return [];
35
+ }
36
+ }
37
+
38
+ export const entityService = new EntityServiceMock();
@@ -0,0 +1,88 @@
1
+ import { AttributeType } from '../../models/EntitySchema.js';
2
+
3
+ // Mock service service matching actual ServiceService API
4
+ class ServiceServiceMock {
5
+ async getAllServices(): Promise<string[]> {
6
+ return ['user-service', 'product-service', 'order-service'];
7
+ }
8
+
9
+ async getServiceEntities(service: string) {
10
+ const serviceEntities: Record<string, any[]> = {
11
+ 'user-service': [
12
+ {
13
+ uuid: 'a38d1597-cc4f-4934-bb08-c876c023f693',
14
+ name: 'User', description: 'User entity',
15
+ attributes: [
16
+ { uuid: 'b49e2608-dd5f-4045-aa09-d464c234e694', name: 'id', description: 'User ID', type: AttributeType.STRING, required: true, primaryKey: true },
17
+ { uuid: 'c5af3719-ee6f-4156-bb1a-e575d345f7a5', name: 'email', description: 'User email', type: AttributeType.STRING, required: true },
18
+ ],
19
+ },
20
+ ],
21
+ 'product-service': [],
22
+ 'order-service': [],
23
+ };
24
+ return serviceEntities[service] || [];
25
+ }
26
+
27
+ async getEntitySchema(service: string, entity: string) {
28
+ if (service === 'user-service' && entity === 'User') {
29
+ return {
30
+ uuid: 'a38d1597-cc4f-4934-bb08-c876c023f693',
31
+ name: 'User', description: 'User entity',
32
+ attributes: [
33
+ { uuid: 'b49e2608-dd5f-4045-aa09-d464c234e694', name: 'id', description: 'User ID', type: AttributeType.STRING, required: true, primaryKey: true },
34
+ { uuid: 'c5af3719-ee6f-4156-bb1a-e575d345f7a5', name: 'email', description: 'User email', type: AttributeType.STRING, required: true },
35
+ ],
36
+ };
37
+ }
38
+ return null;
39
+ }
40
+
41
+ async createEntity(service: string, entity: any) {
42
+ return { success: true, errors: [] };
43
+ }
44
+
45
+ async updateEntity(service: string, entity: any) {
46
+ return { success: true, errors: [] };
47
+ }
48
+
49
+ async deleteEntity(service: string, entityName: string) {
50
+ return { success: true, errors: [] };
51
+ }
52
+
53
+ async searchEntities(query: string) {
54
+ return [
55
+ { entity: 'User', service: 'user-service', matches: ['name', 'description'] },
56
+ ];
57
+ }
58
+
59
+ async getGraphData(service: string) {
60
+ return {
61
+ nodes: [
62
+ { id: 'User', label: 'User', type: 'entity' },
63
+ { id: 'Profile', label: 'Profile', type: 'entity' },
64
+ ],
65
+ edges: [
66
+ { source: 'User', target: 'Profile', label: 'hasOne' },
67
+ ],
68
+ };
69
+ }
70
+
71
+ async getPackageRelationships(packageName: string) {
72
+ return [];
73
+ }
74
+
75
+ async createRelationship(packageName: string, relationship: any) {
76
+ return { success: true, errors: [], relationship };
77
+ }
78
+
79
+ async updateRelationship(packageName: string, uuid: string, relationship: any) {
80
+ return { success: true, errors: [] };
81
+ }
82
+
83
+ async deleteRelationship(packageName: string, uuid: string) {
84
+ return { success: true, errors: [] };
85
+ }
86
+ }
87
+
88
+ export const serviceService = new ServiceServiceMock();
@@ -0,0 +1,38 @@
1
+ // Mock version service matching actual VersionService API
2
+ class VersionServiceMock {
3
+ async commitChanges(message: string) {
4
+ return {
5
+ success: true,
6
+ errors: [] as string[],
7
+ commitHash: 'mock-commit-hash-abc123',
8
+ timestamp: new Date('2026-01-01T12:00:00Z'),
9
+ };
10
+ }
11
+
12
+ async getCommitHistory(limit: number = 10) {
13
+ return [
14
+ {
15
+ hash: 'mock-commit-1',
16
+ message: 'Initial commit',
17
+ author: 'Test User',
18
+ date: '2023-01-01T12:00:00Z',
19
+ },
20
+ {
21
+ hash: 'mock-commit-2',
22
+ message: 'Update User entity',
23
+ author: 'Test User',
24
+ date: '2023-01-02T12:00:00Z',
25
+ },
26
+ ];
27
+ }
28
+
29
+ async revertToCommit(commitHash: string) {
30
+ return {
31
+ success: true,
32
+ errors: [] as string[],
33
+ newCommitHash: 'mock-revert-hash-def456',
34
+ };
35
+ }
36
+ }
37
+
38
+ export const versionService = new VersionServiceMock();