@hamak/smart-data-dico 1.0.4 → 1.1.1
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/backend/dist/server.mjs +82213 -0
- package/bin/cli.js +28 -17
- package/package.json +28 -27
- package/backend/package.json +0 -51
- package/backend/src/__tests__/integration/api.test.ts +0 -149
- package/backend/src/__tests__/setup.ts +0 -24
- package/backend/src/__tests__/utils/testUtils.ts +0 -76
- package/backend/src/adapters/EntityFileAdapter.ts +0 -154
- package/backend/src/adapters/YamlFileInfoEnricher.ts +0 -52
- package/backend/src/controllers/authController.ts +0 -131
- package/backend/src/controllers/diagramController.ts +0 -143
- package/backend/src/controllers/dictionaryController.ts +0 -306
- package/backend/src/controllers/importExportController.ts +0 -64
- package/backend/src/controllers/perspectiveController.ts +0 -90
- package/backend/src/controllers/serviceController.ts +0 -418
- package/backend/src/controllers/stereotypeController.ts +0 -59
- package/backend/src/controllers/versionController.ts +0 -226
- package/backend/src/kernel/config.ts +0 -43
- package/backend/src/middleware/auth.ts +0 -128
- package/backend/src/middleware/jwtAuth.ts +0 -100
- package/backend/src/models/Dictionary.ts +0 -38
- package/backend/src/models/EntitySchema.ts +0 -393
- package/backend/src/models/__tests__/Dictionary.test.ts +0 -92
- package/backend/src/models/__tests__/EntitySchema.test.ts +0 -119
- package/backend/src/routes/index.ts +0 -120
- package/backend/src/scripts/migrate-to-uuid.ts +0 -24
- package/backend/src/server.ts +0 -158
- package/backend/src/services/__mocks__/entityService.ts +0 -38
- package/backend/src/services/__mocks__/serviceService.ts +0 -88
- package/backend/src/services/__mocks__/versionService.ts +0 -38
- package/backend/src/services/__tests__/dictionaryService.test.ts +0 -74
- package/backend/src/services/diagramService.ts +0 -165
- package/backend/src/services/dictionaryService.ts +0 -582
- package/backend/src/services/entityService.ts +0 -102
- package/backend/src/services/exportService.ts +0 -172
- package/backend/src/services/importService.ts +0 -208
- package/backend/src/services/perspectiveService.ts +0 -276
- package/backend/src/services/qualityService.ts +0 -121
- package/backend/src/services/serviceService.ts +0 -763
- package/backend/src/services/stereotypeService.ts +0 -98
- package/backend/src/services/versionService.ts +0 -135
- package/backend/src/setupTests.ts +0 -12
- package/backend/src/utils/__mocks__/fileOperations.ts +0 -116
- package/backend/src/utils/fileOperations.ts +0 -602
- package/backend/src/utils/logger.ts +0 -38
- package/backend/src/utils/migration.ts +0 -254
- package/backend/src/utils/swagger.ts +0 -358
- package/backend/src/utils/uuid.ts +0 -41
- package/backend/tsconfig.json +0 -20
|
@@ -1,582 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import YAML from 'yaml';
|
|
4
|
-
|
|
5
|
-
import { Dictionary, Package } from '../models/Dictionary.js';
|
|
6
|
-
import { Entity, Relationship } from '../models/EntitySchema.js';
|
|
7
|
-
import { ensureDirectoryStructure, listAllDictionaries, listAllEntities, listMicroserviceEntities, listMicroservices, readEntityFile, readRelationshipsFile, writeDictionaryMetadata } from '../utils/fileOperations.js';
|
|
8
|
-
import { logger } from '../utils/logger.js';
|
|
9
|
-
|
|
10
|
-
// Base directory for data dictionaries - use the same path as in fileOperations.ts
|
|
11
|
-
const DATA_DICTIONARIES_DIR = path.join(process.cwd(), '..', 'data-dictionaries');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Dictionary Service
|
|
15
|
-
* Provides functionality for managing data dictionaries
|
|
16
|
-
*/
|
|
17
|
-
export class DictionaryService {
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Create a new package (subpackage) at the given path.
|
|
21
|
-
*/
|
|
22
|
-
private validatePackageName(name: string): string | null {
|
|
23
|
-
if (!name) return 'Package name is required';
|
|
24
|
-
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) return 'Package name must be kebab-case (lowercase letters, numbers, hyphens)';
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
public async createPackageAtPath(rootPackageName: string, packagePath: string[], packageData: Partial<Package>): Promise<{ success: boolean; errors?: string[]; package?: Package }> {
|
|
29
|
-
try {
|
|
30
|
-
// Validate package name
|
|
31
|
-
const nameToValidate = packagePath.length > 0 ? packagePath[packagePath.length - 1] : rootPackageName;
|
|
32
|
-
const nameError = this.validatePackageName(nameToValidate);
|
|
33
|
-
if (nameError) return { success: false, errors: [nameError] };
|
|
34
|
-
|
|
35
|
-
const baseDir = path.join(DATA_DICTIONARIES_DIR, 'microservices', rootPackageName, ...packagePath);
|
|
36
|
-
if (!fs.existsSync(baseDir)) {
|
|
37
|
-
fs.mkdirSync(baseDir, { recursive: true });
|
|
38
|
-
} else {
|
|
39
|
-
return { success: false, errors: ['Package directory already exists'] };
|
|
40
|
-
}
|
|
41
|
-
const metaPath = path.join(baseDir, 'metadata.yaml');
|
|
42
|
-
const metaContent = YAML.stringify({
|
|
43
|
-
id: packageData.id || packagePath[packagePath.length - 1],
|
|
44
|
-
name: packageData.name || packagePath[packagePath.length - 1],
|
|
45
|
-
description: packageData.description,
|
|
46
|
-
type: packageData.type,
|
|
47
|
-
metadata: packageData.metadata || []
|
|
48
|
-
});
|
|
49
|
-
fs.writeFileSync(metaPath, metaContent, 'utf8');
|
|
50
|
-
return {
|
|
51
|
-
success: true,
|
|
52
|
-
package: {
|
|
53
|
-
id: packageData.id || packagePath[packagePath.length - 1],
|
|
54
|
-
name: packageData.name || packagePath[packagePath.length - 1],
|
|
55
|
-
description: packageData.description,
|
|
56
|
-
type: packageData.type,
|
|
57
|
-
entities: [],
|
|
58
|
-
subPackages: [],
|
|
59
|
-
relationships: [],
|
|
60
|
-
metadata: packageData.metadata || []
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
} catch (error: any) {
|
|
64
|
-
logger.error('Error creating package at path', error);
|
|
65
|
-
return { success: false, errors: [error.message || String(error)] };
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Update a package's metadata at the given path.
|
|
71
|
-
*/
|
|
72
|
-
public async updatePackageAtPath(rootPackageName: string, packagePath: string[], packageData: Partial<Package>): Promise<{ success: boolean; errors?: string[]; package?: Package }> {
|
|
73
|
-
try {
|
|
74
|
-
const baseDir = path.join(DATA_DICTIONARIES_DIR, 'microservices', rootPackageName, ...packagePath);
|
|
75
|
-
if (!fs.existsSync(baseDir)) {
|
|
76
|
-
return { success: false, errors: ['Package directory does not exist'] };
|
|
77
|
-
}
|
|
78
|
-
const metaPath = path.join(baseDir, 'metadata.yaml');
|
|
79
|
-
if (!fs.existsSync(metaPath)) {
|
|
80
|
-
return { success: false, errors: ['metadata.yaml does not exist'] };
|
|
81
|
-
}
|
|
82
|
-
const oldMeta = YAML.parse(fs.readFileSync(metaPath, 'utf8')) || {};
|
|
83
|
-
const newMeta = {
|
|
84
|
-
...oldMeta,
|
|
85
|
-
...packageData,
|
|
86
|
-
id: packageData.id || oldMeta.id,
|
|
87
|
-
name: packageData.name || oldMeta.name,
|
|
88
|
-
description: packageData.description ?? oldMeta.description,
|
|
89
|
-
type: packageData.type ?? oldMeta.type,
|
|
90
|
-
metadata: packageData.metadata ?? oldMeta.metadata
|
|
91
|
-
};
|
|
92
|
-
fs.writeFileSync(metaPath, YAML.stringify(newMeta), 'utf8');
|
|
93
|
-
return {
|
|
94
|
-
success: true,
|
|
95
|
-
package: {
|
|
96
|
-
id: newMeta.id,
|
|
97
|
-
name: newMeta.name,
|
|
98
|
-
description: newMeta.description,
|
|
99
|
-
type: newMeta.type,
|
|
100
|
-
entities: [],
|
|
101
|
-
subPackages: [],
|
|
102
|
-
relationships: [],
|
|
103
|
-
metadata: newMeta.metadata
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
} catch (error: any) {
|
|
107
|
-
logger.error('Error updating package at path', error);
|
|
108
|
-
return { success: false, errors: [error.message || String(error)] };
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Delete a package (and all its contents) at the given path.
|
|
114
|
-
*/
|
|
115
|
-
public async deletePackageAtPath(rootPackageName: string, packagePath: string[], force = false): Promise<{ success: boolean; errors?: string[] }> {
|
|
116
|
-
try {
|
|
117
|
-
const baseDir = path.join(DATA_DICTIONARIES_DIR, 'microservices', rootPackageName, ...packagePath);
|
|
118
|
-
if (!fs.existsSync(baseDir)) {
|
|
119
|
-
return { success: false, errors: ['Package directory does not exist'] };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Non-empty check unless force=true
|
|
123
|
-
if (!force) {
|
|
124
|
-
const entries = fs.readdirSync(baseDir);
|
|
125
|
-
const hasEntities = entries.some(e => e.endsWith('.yaml') && e !== 'metadata.yaml' && e !== 'relationships.yaml');
|
|
126
|
-
const hasSubPackages = entries.some(e => fs.statSync(path.join(baseDir, e)).isDirectory());
|
|
127
|
-
if (hasEntities || hasSubPackages) {
|
|
128
|
-
return { success: false, errors: ['Package is not empty. Delete its entities and sub-packages first, or use force=true.'] };
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
fs.rmSync(baseDir, { recursive: true, force: true });
|
|
133
|
-
return { success: true };
|
|
134
|
-
} catch (error: any) {
|
|
135
|
-
logger.error('Error deleting package at path', error);
|
|
136
|
-
return { success: false, errors: [error.message || String(error)] };
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Recursively builds a Package hierarchy from a directory.
|
|
142
|
-
* Now also reads relationships.yaml at each package level.
|
|
143
|
-
*/
|
|
144
|
-
private async buildPackageHierarchy(dirPath: string, packageName: string): Promise<Package> {
|
|
145
|
-
if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
|
|
146
|
-
return {
|
|
147
|
-
id: packageName,
|
|
148
|
-
name: packageName,
|
|
149
|
-
description: undefined,
|
|
150
|
-
type: undefined,
|
|
151
|
-
entities: [],
|
|
152
|
-
subPackages: [],
|
|
153
|
-
relationships: [],
|
|
154
|
-
metadata: undefined
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Read package metadata if present
|
|
159
|
-
let packageMeta: Partial<Package> = {};
|
|
160
|
-
const metaPath = path.join(dirPath, 'metadata.yaml');
|
|
161
|
-
if (fs.existsSync(metaPath)) {
|
|
162
|
-
const metaContent = fs.readFileSync(metaPath, 'utf8');
|
|
163
|
-
packageMeta = YAML.parse(metaContent) as Partial<Package>;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Read relationships from package-level file
|
|
167
|
-
const relationships = await readRelationshipsFile(dirPath);
|
|
168
|
-
|
|
169
|
-
// List all files and subdirectories
|
|
170
|
-
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
171
|
-
const entities: Entity[] = [];
|
|
172
|
-
const subPackages: Package[] = [];
|
|
173
|
-
|
|
174
|
-
for (const entry of entries) {
|
|
175
|
-
const entryPath = path.join(dirPath, entry.name);
|
|
176
|
-
if (entry.isDirectory()) {
|
|
177
|
-
const subpkg = await this.buildPackageHierarchy(entryPath, entry.name);
|
|
178
|
-
subPackages.push(subpkg);
|
|
179
|
-
} else if (
|
|
180
|
-
entry.isFile() &&
|
|
181
|
-
entry.name.endsWith('.yaml') &&
|
|
182
|
-
entry.name !== 'metadata.yaml' &&
|
|
183
|
-
entry.name !== 'relationships.yaml'
|
|
184
|
-
) {
|
|
185
|
-
try {
|
|
186
|
-
const fileContent = fs.readFileSync(entryPath, 'utf8');
|
|
187
|
-
const entity = YAML.parse(fileContent) as Entity;
|
|
188
|
-
entities.push(entity);
|
|
189
|
-
} catch (e) {
|
|
190
|
-
logger.warn(`Failed to parse entity YAML: ${entryPath}: ${e}`);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return {
|
|
196
|
-
id: packageMeta.id || packageName,
|
|
197
|
-
name: packageMeta.name || packageName,
|
|
198
|
-
description: packageMeta.description,
|
|
199
|
-
type: packageMeta.type,
|
|
200
|
-
entities,
|
|
201
|
-
subPackages,
|
|
202
|
-
relationships,
|
|
203
|
-
metadata: packageMeta.metadata
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// --- IMPLEMENTED METHODS ---
|
|
208
|
-
|
|
209
|
-
public async getAllDictionaries(): Promise<Dictionary[]> {
|
|
210
|
-
try {
|
|
211
|
-
const dictionaryIds = await listAllDictionaries();
|
|
212
|
-
const dictionaries: Dictionary[] = [];
|
|
213
|
-
|
|
214
|
-
for (const id of dictionaryIds) {
|
|
215
|
-
const dictionary = await this.getDictionaryById(id);
|
|
216
|
-
if (dictionary) {
|
|
217
|
-
dictionaries.push(dictionary);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return dictionaries;
|
|
222
|
-
} catch (error) {
|
|
223
|
-
logger.error('Error getting all dictionaries', error);
|
|
224
|
-
return [];
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
public async getDictionaryById(id: string): Promise<Dictionary | null> {
|
|
229
|
-
try {
|
|
230
|
-
if (id.includes('microservices')) {
|
|
231
|
-
const rootPackage: Package = {
|
|
232
|
-
id: id,
|
|
233
|
-
name: id.split('/').pop() || id,
|
|
234
|
-
description: `Microservice: ${id}`,
|
|
235
|
-
entities: [],
|
|
236
|
-
subPackages: [],
|
|
237
|
-
relationships: []
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
id,
|
|
242
|
-
name: id.split('/').pop() || id,
|
|
243
|
-
description: `Microservice: ${id}`,
|
|
244
|
-
createdAt: new Date(),
|
|
245
|
-
updatedAt: new Date(),
|
|
246
|
-
rootPackage
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const metadataPath = path.join(DATA_DICTIONARIES_DIR, id, 'metadata.yaml');
|
|
251
|
-
|
|
252
|
-
if (!fs.existsSync(metadataPath)) {
|
|
253
|
-
return null;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const metadataContent = fs.readFileSync(metadataPath, 'utf8');
|
|
257
|
-
const metadata = YAML.parse(metadataContent) as Dictionary;
|
|
258
|
-
|
|
259
|
-
return metadata;
|
|
260
|
-
} catch (error) {
|
|
261
|
-
logger.error(`Error getting dictionary by ID: ${id}`, error);
|
|
262
|
-
return null;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
public async getDictionaryEntries(id: string): Promise<any[]> {
|
|
267
|
-
try {
|
|
268
|
-
if (id.startsWith('microservices/')) {
|
|
269
|
-
const microservice = id.replace('microservices/', '');
|
|
270
|
-
const entityNames = await listMicroserviceEntities(microservice);
|
|
271
|
-
const entries: any[] = [];
|
|
272
|
-
|
|
273
|
-
for (const entityName of entityNames) {
|
|
274
|
-
const entity = await readEntityFile(microservice, entityName);
|
|
275
|
-
if (entity) {
|
|
276
|
-
for (const attr of entity.attributes || []) {
|
|
277
|
-
entries.push({
|
|
278
|
-
id: `${entity.uuid || ''}_${attr.name}`,
|
|
279
|
-
name: attr.name,
|
|
280
|
-
description: attr.description || '',
|
|
281
|
-
type: attr.type || 'string',
|
|
282
|
-
format: attr.constraints?.format,
|
|
283
|
-
required: attr.required || false
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return entries;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return [];
|
|
293
|
-
} catch (error) {
|
|
294
|
-
logger.error(`Error getting dictionary entries: ${id}`, error);
|
|
295
|
-
return [];
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
public async getEntityAttributes(microservice: string, entityName: string): Promise<any[]> {
|
|
300
|
-
try {
|
|
301
|
-
const entity = await readEntityFile(microservice, entityName);
|
|
302
|
-
|
|
303
|
-
if (!entity || !entity.attributes) {
|
|
304
|
-
return [];
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return entity.attributes.map((attr: any) => ({
|
|
308
|
-
id: `${entity.uuid || ''}_${attr.name}`,
|
|
309
|
-
name: attr.name,
|
|
310
|
-
description: attr.description || '',
|
|
311
|
-
type: attr.type || 'string',
|
|
312
|
-
format: attr.constraints?.format,
|
|
313
|
-
required: attr.required || false
|
|
314
|
-
}));
|
|
315
|
-
} catch (error) {
|
|
316
|
-
logger.error(`Error getting entity attributes: ${microservice}.${entityName}`, error);
|
|
317
|
-
return [];
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
public async createDictionary(dictionaryData: Dictionary): Promise<Dictionary | { error: string; code: string }> {
|
|
322
|
-
try {
|
|
323
|
-
if (!dictionaryData.name) {
|
|
324
|
-
return { error: 'Dictionary name is required', code: 'MISSING_NAME' };
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if (!dictionaryData.id) {
|
|
328
|
-
dictionaryData.id = dictionaryData.name.toLowerCase().replace(/\s+/g, '-');
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const existingDictionaries = await listAllDictionaries();
|
|
332
|
-
if (existingDictionaries.includes(dictionaryData.id)) {
|
|
333
|
-
return { error: `Dictionary with ID ${dictionaryData.id} already exists`, code: 'DUPLICATE_NAME' };
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
dictionaryData.createdAt = new Date();
|
|
337
|
-
dictionaryData.updatedAt = new Date();
|
|
338
|
-
|
|
339
|
-
const success = await writeDictionaryMetadata(dictionaryData);
|
|
340
|
-
|
|
341
|
-
if (!success) {
|
|
342
|
-
return { error: 'Failed to write dictionary metadata', code: 'WRITE_ERROR' };
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return dictionaryData;
|
|
346
|
-
} catch (error) {
|
|
347
|
-
logger.error('Error creating dictionary', error);
|
|
348
|
-
return { error: 'Internal server error', code: 'INTERNAL_ERROR' };
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
public async getPackageHierarchy(rootPackage: string): Promise<Package | null> {
|
|
353
|
-
try {
|
|
354
|
-
const dirPath = path.join(DATA_DICTIONARIES_DIR, 'microservices', rootPackage);
|
|
355
|
-
if (!fs.existsSync(dirPath)) {
|
|
356
|
-
return null;
|
|
357
|
-
}
|
|
358
|
-
return await this.buildPackageHierarchy(dirPath, rootPackage);
|
|
359
|
-
} catch (error) {
|
|
360
|
-
logger.error('Error in getPackageHierarchy', error);
|
|
361
|
-
return null;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
public async getTabularData(rootPackage: string): Promise<any[]> {
|
|
366
|
-
try {
|
|
367
|
-
const hierarchy = await this.getPackageHierarchy(rootPackage);
|
|
368
|
-
if (!hierarchy) {
|
|
369
|
-
return [];
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const result: any[] = [];
|
|
373
|
-
this.flattenHierarchy(hierarchy, result, '');
|
|
374
|
-
|
|
375
|
-
return result;
|
|
376
|
-
} catch (error) {
|
|
377
|
-
logger.error(`Error getting tabular data: ${rootPackage}`, error);
|
|
378
|
-
return [];
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
private flattenHierarchy(pkg: Package, result: any[], parentPath: string): void {
|
|
383
|
-
const currentPath = parentPath ? `${parentPath}/${pkg.name}` : pkg.name;
|
|
384
|
-
|
|
385
|
-
result.push({
|
|
386
|
-
type: 'package',
|
|
387
|
-
id: pkg.id,
|
|
388
|
-
name: pkg.name,
|
|
389
|
-
description: pkg.description,
|
|
390
|
-
path: currentPath,
|
|
391
|
-
level: parentPath.split('/').length
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
for (const entity of pkg.entities || []) {
|
|
395
|
-
result.push({
|
|
396
|
-
type: 'entity',
|
|
397
|
-
id: entity.uuid,
|
|
398
|
-
name: entity.name,
|
|
399
|
-
description: entity.description,
|
|
400
|
-
path: `${currentPath}/${entity.name}`,
|
|
401
|
-
level: parentPath.split('/').length + 1
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
for (const subpkg of pkg.subPackages || []) {
|
|
406
|
-
this.flattenHierarchy(subpkg, result, currentPath);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
public async getPackageByPath(rootPackage: string, packagePath: string[]): Promise<Package | null> {
|
|
411
|
-
try {
|
|
412
|
-
const rootHierarchy = await this.getPackageHierarchy(rootPackage);
|
|
413
|
-
if (!rootHierarchy) {
|
|
414
|
-
return null;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (!packagePath.length) {
|
|
418
|
-
return rootHierarchy;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
let currentPackage = rootHierarchy;
|
|
422
|
-
|
|
423
|
-
for (const segment of packagePath) {
|
|
424
|
-
const subPackage = currentPackage.subPackages?.find(p => p.name === segment);
|
|
425
|
-
|
|
426
|
-
if (!subPackage) {
|
|
427
|
-
return null;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
currentPackage = subPackage;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
return currentPackage;
|
|
434
|
-
} catch (error) {
|
|
435
|
-
logger.error(`Error getting package by path: ${rootPackage}/${packagePath.join('/')}`, error);
|
|
436
|
-
return null;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
public async listAllPackagesAndEntities(): Promise<Package[]> {
|
|
441
|
-
try {
|
|
442
|
-
const microservices = await listMicroservices();
|
|
443
|
-
const result: Package[] = [];
|
|
444
|
-
for (const ms of microservices) {
|
|
445
|
-
const pkg = await this.getPackageHierarchy(ms);
|
|
446
|
-
if (pkg) {
|
|
447
|
-
result.push(pkg);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
return result;
|
|
451
|
-
} catch (error) {
|
|
452
|
-
logger.error('Error in listAllPackagesAndEntities', error);
|
|
453
|
-
return [];
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
public async getFlatEntitiesAndAttributes(filters: any): Promise<any[]> {
|
|
458
|
-
try {
|
|
459
|
-
const microservices = await listMicroservices();
|
|
460
|
-
const result: any[] = [];
|
|
461
|
-
|
|
462
|
-
for (const microservice of microservices) {
|
|
463
|
-
const entityNames = await listMicroserviceEntities(microservice);
|
|
464
|
-
|
|
465
|
-
for (const entityName of entityNames) {
|
|
466
|
-
const entity = await readEntityFile(microservice, entityName);
|
|
467
|
-
|
|
468
|
-
if (!entity) continue;
|
|
469
|
-
|
|
470
|
-
if (filters.name && !entity.name.toLowerCase().includes(filters.name.toLowerCase())) {
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
result.push({
|
|
475
|
-
type: 'entity',
|
|
476
|
-
id: entity.uuid,
|
|
477
|
-
name: entity.name,
|
|
478
|
-
description: entity.description,
|
|
479
|
-
package: microservice
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
for (const attr of entity.attributes || []) {
|
|
483
|
-
if (filters.type && attr.type !== filters.type) {
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
result.push({
|
|
488
|
-
type: 'attribute',
|
|
489
|
-
id: `${entity.uuid}_${attr.name}`,
|
|
490
|
-
name: attr.name,
|
|
491
|
-
description: attr.description,
|
|
492
|
-
dataType: attr.type,
|
|
493
|
-
required: attr.required,
|
|
494
|
-
primaryKey: attr.primaryKey,
|
|
495
|
-
entity: entity.name,
|
|
496
|
-
package: microservice
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
return result;
|
|
503
|
-
} catch (error) {
|
|
504
|
-
logger.error('Error getting flat entities and attributes', error);
|
|
505
|
-
return [];
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
public async getEntityHierarchy(microservice: string, entityName: string): Promise<any> {
|
|
510
|
-
try {
|
|
511
|
-
const entity = await readEntityFile(microservice, entityName);
|
|
512
|
-
|
|
513
|
-
if (!entity) {
|
|
514
|
-
return null;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
const hierarchy: any = {
|
|
518
|
-
id: entity.uuid,
|
|
519
|
-
name: entity.name,
|
|
520
|
-
description: entity.description,
|
|
521
|
-
type: 'entity',
|
|
522
|
-
package: microservice,
|
|
523
|
-
attributes: entity.attributes || [],
|
|
524
|
-
children: [] as any[]
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
// Read relationships from package-level file
|
|
528
|
-
const packagePath = path.join(DATA_DICTIONARIES_DIR, 'microservices', microservice);
|
|
529
|
-
const relationships = await readRelationshipsFile(packagePath);
|
|
530
|
-
|
|
531
|
-
const children: any[] = [];
|
|
532
|
-
for (const rel of relationships) {
|
|
533
|
-
let targetUuid: string | null = null;
|
|
534
|
-
let relLabel: string = '';
|
|
535
|
-
|
|
536
|
-
if (rel.source.entity === entity.uuid) {
|
|
537
|
-
targetUuid = rel.target.entity;
|
|
538
|
-
relLabel = rel.target.name || '';
|
|
539
|
-
} else if (rel.target.entity === entity.uuid) {
|
|
540
|
-
targetUuid = rel.source.entity;
|
|
541
|
-
relLabel = rel.source.name || '';
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
if (targetUuid) {
|
|
545
|
-
// Find the target entity
|
|
546
|
-
const entities = await this.getServiceEntities(microservice);
|
|
547
|
-
const relatedEntity = entities.find(e => e.uuid === targetUuid);
|
|
548
|
-
|
|
549
|
-
if (relatedEntity) {
|
|
550
|
-
children.push({
|
|
551
|
-
id: relatedEntity.uuid,
|
|
552
|
-
name: relatedEntity.name,
|
|
553
|
-
description: relatedEntity.description,
|
|
554
|
-
type: 'entity',
|
|
555
|
-
package: microservice,
|
|
556
|
-
sourceCardinality: rel.source.entity === entity.uuid ? rel.source.cardinality : rel.target.cardinality,
|
|
557
|
-
targetCardinality: rel.source.entity === entity.uuid ? rel.target.cardinality : rel.source.cardinality,
|
|
558
|
-
relationshipName: relLabel
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
hierarchy.children = children;
|
|
564
|
-
|
|
565
|
-
return hierarchy;
|
|
566
|
-
} catch (error) {
|
|
567
|
-
logger.error(`Error getting entity hierarchy: ${microservice}.${entityName}`, error);
|
|
568
|
-
return null;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
private async getServiceEntities(service: string): Promise<Entity[]> {
|
|
573
|
-
const entityNames = await listMicroserviceEntities(service);
|
|
574
|
-
const entities: Entity[] = [];
|
|
575
|
-
for (const name of entityNames) {
|
|
576
|
-
const entity = await readEntityFile(service, name);
|
|
577
|
-
if (entity) entities.push(entity);
|
|
578
|
-
}
|
|
579
|
-
return entities;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
export const dictionaryService = new DictionaryService();
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { Entity, Relationship, validateEntity, validateRelationship } from '../models/EntitySchema.js';
|
|
2
|
-
import { readEntityFile, writeEntityFile, listAllEntities, readRelationshipsFile, getPackagePath } from '../utils/fileOperations.js';
|
|
3
|
-
import { logger } from '../utils/logger.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Service for managing entities and their relationships
|
|
7
|
-
*/
|
|
8
|
-
export class EntityService {
|
|
9
|
-
/**
|
|
10
|
-
* Validates an entity's structure and data
|
|
11
|
-
*/
|
|
12
|
-
validateEntity(entity: Entity): { valid: boolean; errors: string[] } {
|
|
13
|
-
return validateEntity(entity);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Validates relationships at the package level
|
|
18
|
-
*/
|
|
19
|
-
async validateRelationships(packageName: string, relationships: Relationship[]): Promise<{ valid: boolean; errors: string[] }> {
|
|
20
|
-
const errors: string[] = [];
|
|
21
|
-
|
|
22
|
-
for (const rel of relationships) {
|
|
23
|
-
const relValidation = validateRelationship(rel);
|
|
24
|
-
if (!relValidation.valid) {
|
|
25
|
-
errors.push(...relValidation.errors);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
valid: errors.length === 0,
|
|
31
|
-
errors
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Creates or updates an entity
|
|
37
|
-
*/
|
|
38
|
-
async saveEntity(entity: Entity, packageName: string): Promise<{ success: boolean; errors: string[] }> {
|
|
39
|
-
const structureValidation = this.validateEntity(entity);
|
|
40
|
-
if (!structureValidation.valid) {
|
|
41
|
-
return {
|
|
42
|
-
success: false,
|
|
43
|
-
errors: structureValidation.errors
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const saved = await writeEntityFile(entity, packageName);
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
success: saved,
|
|
51
|
-
errors: saved ? [] : ['Failed to write entity file']
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Gets an entity by package and name
|
|
57
|
-
*/
|
|
58
|
-
async getEntity(packageName: string, entityName: string): Promise<Entity | null> {
|
|
59
|
-
return await readEntityFile(packageName, entityName);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Gets all entities that have a relationship with the specified entity
|
|
64
|
-
* Uses package-level relationships.yaml files
|
|
65
|
-
*/
|
|
66
|
-
async getRelatedEntities(packageName: string, entityName: string): Promise<Entity[]> {
|
|
67
|
-
const entity = await readEntityFile(packageName, entityName);
|
|
68
|
-
if (!entity) return [];
|
|
69
|
-
|
|
70
|
-
const entityUuid = entity.uuid;
|
|
71
|
-
const packagePath = getPackagePath(packageName);
|
|
72
|
-
const relationships = await readRelationshipsFile(packagePath);
|
|
73
|
-
const relatedEntities: Entity[] = [];
|
|
74
|
-
|
|
75
|
-
// Find relationships involving this entity
|
|
76
|
-
for (const rel of relationships) {
|
|
77
|
-
let targetUuid: string | null = null;
|
|
78
|
-
|
|
79
|
-
if (rel.source.entity === entityUuid) {
|
|
80
|
-
targetUuid = rel.target.entity;
|
|
81
|
-
} else if (rel.target.entity === entityUuid) {
|
|
82
|
-
targetUuid = rel.source.entity;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (targetUuid) {
|
|
86
|
-
// Find the entity by UUID across all entities in the package
|
|
87
|
-
const allEntities = await listAllEntities();
|
|
88
|
-
for (const entityInfo of allEntities) {
|
|
89
|
-
const candidate = await readEntityFile(entityInfo.microservice, entityInfo.name);
|
|
90
|
-
if (candidate && candidate.uuid === targetUuid && !relatedEntities.some(e => e.uuid === candidate.uuid)) {
|
|
91
|
-
relatedEntities.push(candidate);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return relatedEntities;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Export a singleton instance
|
|
102
|
-
export const entityService = new EntityService();
|