@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.
- package/README.md +292 -0
- package/backend/package.json +51 -0
- package/backend/src/__tests__/integration/api.test.ts +149 -0
- package/backend/src/__tests__/setup.ts +24 -0
- package/backend/src/__tests__/utils/testUtils.ts +76 -0
- package/backend/src/adapters/EntityFileAdapter.ts +154 -0
- package/backend/src/adapters/YamlFileInfoEnricher.ts +52 -0
- package/backend/src/controllers/authController.ts +131 -0
- package/backend/src/controllers/diagramController.ts +143 -0
- package/backend/src/controllers/dictionaryController.ts +306 -0
- package/backend/src/controllers/importExportController.ts +64 -0
- package/backend/src/controllers/perspectiveController.ts +90 -0
- package/backend/src/controllers/serviceController.ts +418 -0
- package/backend/src/controllers/stereotypeController.ts +59 -0
- package/backend/src/controllers/versionController.ts +226 -0
- package/backend/src/kernel/config.ts +43 -0
- package/backend/src/middleware/auth.ts +128 -0
- package/backend/src/middleware/jwtAuth.ts +100 -0
- package/backend/src/models/Dictionary.ts +38 -0
- package/backend/src/models/EntitySchema.ts +393 -0
- package/backend/src/models/__tests__/Dictionary.test.ts +92 -0
- package/backend/src/models/__tests__/EntitySchema.test.ts +119 -0
- package/backend/src/routes/index.ts +120 -0
- package/backend/src/scripts/migrate-to-uuid.ts +24 -0
- package/backend/src/server.ts +140 -0
- package/backend/src/services/__mocks__/entityService.ts +38 -0
- package/backend/src/services/__mocks__/serviceService.ts +88 -0
- package/backend/src/services/__mocks__/versionService.ts +38 -0
- package/backend/src/services/__tests__/dictionaryService.test.ts +74 -0
- package/backend/src/services/diagramService.ts +165 -0
- package/backend/src/services/dictionaryService.ts +582 -0
- package/backend/src/services/entityService.ts +102 -0
- package/backend/src/services/exportService.ts +172 -0
- package/backend/src/services/importService.ts +208 -0
- package/backend/src/services/perspectiveService.ts +276 -0
- package/backend/src/services/qualityService.ts +121 -0
- package/backend/src/services/serviceService.ts +763 -0
- package/backend/src/services/stereotypeService.ts +98 -0
- package/backend/src/services/versionService.ts +135 -0
- package/backend/src/setupTests.ts +12 -0
- package/backend/src/utils/__mocks__/fileOperations.ts +116 -0
- package/backend/src/utils/fileOperations.ts +602 -0
- package/backend/src/utils/logger.ts +38 -0
- package/backend/src/utils/migration.ts +254 -0
- package/backend/src/utils/swagger.ts +358 -0
- package/backend/src/utils/uuid.ts +41 -0
- package/backend/tsconfig.json +20 -0
- package/bin/cli.js +129 -0
- package/data-dictionaries/stereotypes.yaml +91 -0
- package/package.json +42 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { listMicroserviceEntities, listAllDictionaries, readEntityFile } from '../../utils/fileOperations.js';
|
|
2
|
+
import { dictionaryService } from '../dictionaryService.js';
|
|
3
|
+
|
|
4
|
+
// Mock dependencies
|
|
5
|
+
jest.mock('../../utils/fileOperations');
|
|
6
|
+
jest.mock('../../utils/logger');
|
|
7
|
+
|
|
8
|
+
describe('DictionaryService', () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
jest.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('getAllDictionaries', () => {
|
|
14
|
+
it('should return all dictionaries', async () => {
|
|
15
|
+
(listAllDictionaries as jest.Mock).mockResolvedValue(['microservices/svc-a', 'microservices/svc-b']);
|
|
16
|
+
|
|
17
|
+
const dictionaries = await dictionaryService.getAllDictionaries();
|
|
18
|
+
|
|
19
|
+
expect(listAllDictionaries).toHaveBeenCalledTimes(1);
|
|
20
|
+
// getDictionaryById creates dictionaries from microservices/ IDs
|
|
21
|
+
expect(dictionaries).toHaveLength(2);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should return empty array on error', async () => {
|
|
25
|
+
(listAllDictionaries as jest.Mock).mockRejectedValueOnce(new Error('Test error'));
|
|
26
|
+
|
|
27
|
+
const dictionaries = await dictionaryService.getAllDictionaries();
|
|
28
|
+
|
|
29
|
+
expect(listAllDictionaries).toHaveBeenCalledTimes(1);
|
|
30
|
+
expect(dictionaries).toHaveLength(0);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('getEntityAttributes', () => {
|
|
35
|
+
it('should return attributes for an entity', async () => {
|
|
36
|
+
(readEntityFile as jest.Mock).mockResolvedValue({
|
|
37
|
+
id: 'User',
|
|
38
|
+
uuid: 'a38d1597-cc4f-4934-bb08-c876c023f693',
|
|
39
|
+
name: 'User',
|
|
40
|
+
microservice: 'user-service',
|
|
41
|
+
version: '1.0.0',
|
|
42
|
+
attributes: [
|
|
43
|
+
{ uuid: 'b49e2608-dd5f-4045-aa09-d464c234e694', name: 'id', type: 'string', description: 'ID', required: true },
|
|
44
|
+
{ uuid: 'c5af3719-ee6f-4156-bb1a-e575d345f7a5', name: 'email', type: 'string', description: 'Email', required: true },
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const attributes = await dictionaryService.getEntityAttributes('user-service', 'User');
|
|
49
|
+
|
|
50
|
+
expect(readEntityFile).toHaveBeenCalledWith('user-service', 'User');
|
|
51
|
+
expect(attributes).toHaveLength(2);
|
|
52
|
+
expect(attributes[0].name).toBe('id');
|
|
53
|
+
expect(attributes[1].name).toBe('email');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should return empty array for non-existent entity', async () => {
|
|
57
|
+
(readEntityFile as jest.Mock).mockResolvedValue(null);
|
|
58
|
+
|
|
59
|
+
const attributes = await dictionaryService.getEntityAttributes('user-service', 'NonExistent');
|
|
60
|
+
|
|
61
|
+
expect(readEntityFile).toHaveBeenCalledWith('user-service', 'NonExistent');
|
|
62
|
+
expect(attributes).toHaveLength(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should return empty array on error', async () => {
|
|
66
|
+
(readEntityFile as jest.Mock).mockRejectedValueOnce(new Error('Test error'));
|
|
67
|
+
|
|
68
|
+
const attributes = await dictionaryService.getEntityAttributes('user-service', 'User');
|
|
69
|
+
|
|
70
|
+
expect(readEntityFile).toHaveBeenCalledWith('user-service', 'User');
|
|
71
|
+
expect(attributes).toHaveLength(0);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { logger } from '../utils/logger.js';
|
|
4
|
+
|
|
5
|
+
// Use the same base directory as in fileOperations.ts
|
|
6
|
+
const DATA_DICTIONARIES_BASE = path.join(process.cwd(), '..', 'data-dictionaries');
|
|
7
|
+
|
|
8
|
+
export interface DiagramLayout {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
service?: string;
|
|
12
|
+
entities: {
|
|
13
|
+
[entityUuid: string]: {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
showProperties: boolean;
|
|
17
|
+
name?: string; // Include name for readability
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
zoom: number;
|
|
21
|
+
pan: {
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
};
|
|
25
|
+
createdAt: string;
|
|
26
|
+
updatedAt: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const DIAGRAMS_DIR = path.join(DATA_DICTIONARIES_BASE, 'diagrams');
|
|
30
|
+
|
|
31
|
+
export class DiagramService {
|
|
32
|
+
private async ensureDiagramsDirectory(): Promise<void> {
|
|
33
|
+
try {
|
|
34
|
+
await fs.access(DIAGRAMS_DIR);
|
|
35
|
+
} catch {
|
|
36
|
+
await fs.mkdir(DIAGRAMS_DIR, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async saveDiagramLayout(layout: Omit<DiagramLayout, 'createdAt' | 'updatedAt'>): Promise<DiagramLayout> {
|
|
41
|
+
try {
|
|
42
|
+
await this.ensureDiagramsDirectory();
|
|
43
|
+
|
|
44
|
+
const now = new Date().toISOString();
|
|
45
|
+
const diagramLayout: DiagramLayout = {
|
|
46
|
+
...layout,
|
|
47
|
+
createdAt: now,
|
|
48
|
+
updatedAt: now
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const filename = `${layout.id}.json`;
|
|
52
|
+
const filepath = path.join(DIAGRAMS_DIR, filename);
|
|
53
|
+
|
|
54
|
+
await fs.writeFile(filepath, JSON.stringify(diagramLayout, null, 2));
|
|
55
|
+
|
|
56
|
+
logger.info(`Saved diagram layout: ${layout.id}`);
|
|
57
|
+
return diagramLayout;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
logger.error('Error saving diagram layout:', error);
|
|
60
|
+
throw new Error('Failed to save diagram layout');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async loadDiagramLayout(id: string): Promise<DiagramLayout | null> {
|
|
65
|
+
try {
|
|
66
|
+
const filename = `${id}.json`;
|
|
67
|
+
const filepath = path.join(DIAGRAMS_DIR, filename);
|
|
68
|
+
|
|
69
|
+
const data = await fs.readFile(filepath, 'utf-8');
|
|
70
|
+
const layout = JSON.parse(data) as DiagramLayout;
|
|
71
|
+
|
|
72
|
+
logger.info(`Loaded diagram layout: ${id}`);
|
|
73
|
+
return layout;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if ((error as any).code === 'ENOENT') {
|
|
76
|
+
logger.warn(`Diagram layout not found: ${id}`);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
logger.error('Error loading diagram layout:', error);
|
|
80
|
+
throw new Error('Failed to load diagram layout');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async updateDiagramLayout(id: string, updates: Partial<Omit<DiagramLayout, 'id' | 'createdAt' | 'updatedAt'>>): Promise<DiagramLayout> {
|
|
85
|
+
try {
|
|
86
|
+
const existingLayout = await this.loadDiagramLayout(id);
|
|
87
|
+
if (!existingLayout) {
|
|
88
|
+
throw new Error('Diagram layout not found');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const updatedLayout: DiagramLayout = {
|
|
92
|
+
...existingLayout,
|
|
93
|
+
...updates,
|
|
94
|
+
id, // Ensure ID doesn't change
|
|
95
|
+
updatedAt: new Date().toISOString()
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const filename = `${id}.json`;
|
|
99
|
+
const filepath = path.join(DIAGRAMS_DIR, filename);
|
|
100
|
+
|
|
101
|
+
await fs.writeFile(filepath, JSON.stringify(updatedLayout, null, 2));
|
|
102
|
+
|
|
103
|
+
logger.info(`Updated diagram layout: ${id}`);
|
|
104
|
+
return updatedLayout;
|
|
105
|
+
} catch (error) {
|
|
106
|
+
logger.error('Error updating diagram layout:', error);
|
|
107
|
+
throw new Error('Failed to update diagram layout');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async deleteDiagramLayout(id: string): Promise<void> {
|
|
112
|
+
try {
|
|
113
|
+
const filename = `${id}.json`;
|
|
114
|
+
const filepath = path.join(DIAGRAMS_DIR, filename);
|
|
115
|
+
|
|
116
|
+
await fs.unlink(filepath);
|
|
117
|
+
|
|
118
|
+
logger.info(`Deleted diagram layout: ${id}`);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if ((error as any).code === 'ENOENT') {
|
|
121
|
+
logger.warn(`Diagram layout not found for deletion: ${id}`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
logger.error('Error deleting diagram layout:', error);
|
|
125
|
+
throw new Error('Failed to delete diagram layout');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async listDiagramLayouts(service?: string): Promise<DiagramLayout[]> {
|
|
130
|
+
try {
|
|
131
|
+
await this.ensureDiagramsDirectory();
|
|
132
|
+
|
|
133
|
+
const files = await fs.readdir(DIAGRAMS_DIR);
|
|
134
|
+
const jsonFiles = files.filter(file => file.endsWith('.json'));
|
|
135
|
+
|
|
136
|
+
const layouts: DiagramLayout[] = [];
|
|
137
|
+
|
|
138
|
+
for (const file of jsonFiles) {
|
|
139
|
+
try {
|
|
140
|
+
const filepath = path.join(DIAGRAMS_DIR, file);
|
|
141
|
+
const data = await fs.readFile(filepath, 'utf-8');
|
|
142
|
+
const layout = JSON.parse(data) as DiagramLayout;
|
|
143
|
+
|
|
144
|
+
// Filter by service if specified
|
|
145
|
+
if (!service || layout.service === service) {
|
|
146
|
+
layouts.push(layout);
|
|
147
|
+
}
|
|
148
|
+
} catch (error) {
|
|
149
|
+
logger.warn(`Error reading diagram layout file ${file}:`, error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Sort by updatedAt descending
|
|
154
|
+
layouts.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
155
|
+
|
|
156
|
+
logger.info(`Listed ${layouts.length} diagram layouts${service ? ` for service ${service}` : ''}`);
|
|
157
|
+
return layouts;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
logger.error('Error listing diagram layouts:', error);
|
|
160
|
+
throw new Error('Failed to list diagram layouts');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const diagramService = new DiagramService();
|