@solidxai/core 0.1.11-beta.4 → 0.1.11-beta.6
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/dist/commands/migrate-removed-fields.command.d.ts +19 -0
- package/dist/commands/migrate-removed-fields.command.d.ts.map +1 -0
- package/dist/commands/migrate-removed-fields.command.js +77 -0
- package/dist/commands/migrate-removed-fields.command.js.map +1 -0
- package/dist/dtos/datasource-introspection-mapping.dto.d.ts +1 -0
- package/dist/dtos/datasource-introspection-mapping.dto.d.ts.map +1 -1
- package/dist/dtos/datasource-introspection-mapping.dto.js +7 -1
- package/dist/dtos/datasource-introspection-mapping.dto.js.map +1 -1
- package/dist/helpers/command.service.d.ts +2 -0
- package/dist/helpers/command.service.d.ts.map +1 -1
- package/dist/helpers/command.service.js +6 -2
- package/dist/helpers/command.service.js.map +1 -1
- package/dist/helpers/nodemon-heartbeat.d.ts +5 -0
- package/dist/helpers/nodemon-heartbeat.d.ts.map +1 -0
- package/dist/helpers/nodemon-heartbeat.js +67 -0
- package/dist/helpers/nodemon-heartbeat.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts +7 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +102 -1
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +109 -31
- package/dist/services/api-key.service.d.ts.map +1 -1
- package/dist/services/api-key.service.js.map +1 -1
- package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.d.ts +4 -0
- package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.d.ts.map +1 -1
- package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.js +18 -1
- package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.js.map +1 -1
- package/dist/services/crud.service.d.ts +1 -0
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js +27 -0
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/datasource-introspection.service.d.ts +1 -0
- package/dist/services/datasource-introspection.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection.service.js +27 -16
- package/dist/services/datasource-introspection.service.js.map +1 -1
- package/dist/services/import-transaction.service.d.ts.map +1 -1
- package/dist/services/import-transaction.service.js +3 -0
- package/dist/services/import-transaction.service.js.map +1 -1
- package/dist/services/model-metadata.service.d.ts +1 -0
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +28 -0
- package/dist/services/model-metadata.service.js.map +1 -1
- package/dist/services/module-metadata.service.d.ts.map +1 -1
- package/dist/services/module-metadata.service.js +27 -0
- package/dist/services/module-metadata.service.js.map +1 -1
- package/dist/services/removed-field-migration.service.d.ts +30 -0
- package/dist/services/removed-field-migration.service.d.ts.map +1 -0
- package/dist/services/removed-field-migration.service.js +319 -0
- package/dist/services/removed-field-migration.service.js.map +1 -0
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +4 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist/subscribers/audit.subscriber.d.ts +4 -1
- package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
- package/dist/subscribers/audit.subscriber.js +54 -6
- package/dist/subscribers/audit.subscriber.js.map +1 -1
- package/dist-tests/api/authenticate.spec.js +119 -0
- package/dist-tests/api/authenticate.spec.js.map +1 -0
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +97 -0
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +1 -0
- package/dist-tests/api/ping.spec.js +21 -0
- package/dist-tests/api/ping.spec.js.map +1 -0
- package/dist-tests/helpers/auth.js +41 -0
- package/dist-tests/helpers/auth.js.map +1 -0
- package/dist-tests/helpers/env.js +11 -0
- package/dist-tests/helpers/env.js.map +1 -0
- package/docs/agent-hub-grooming.md +301 -0
- package/docs/dashboards/AGENTIC_DASHBOARD_IMPLEMENTATION_PLAN.md +438 -0
- package/docs/dashboards/dashboard-curl-smoke-tests.txt +146 -0
- package/docs/dashboards/delete-legacy-dashboard-metadata.sql +172 -0
- package/docs/datasource-introspection-ddl-analysis.md +326 -0
- package/docs/datasource-introspection-implementation-plan.md +306 -0
- package/docs/grouping-enhancements.md +89 -0
- package/docs/java-spring/README.md +3 -0
- package/docs/java-spring/solid-core-module-deep-dive-report.md +1317 -0
- package/docs/module-package-import-handoff.md +691 -0
- package/docs/seed-changes.md +65 -0
- package/docs/test-data-workflow.md +200 -0
- package/docs/type-declaration-import-issue.md +24 -0
- package/package.json +1 -1
- package/src/commands/migrate-removed-fields.command.ts +81 -0
- package/src/dtos/datasource-introspection-mapping.dto.ts +5 -0
- package/src/helpers/command.service.ts +10 -2
- package/src/helpers/nodemon-heartbeat.ts +59 -0
- package/src/index.ts +1 -0
- package/src/seeders/module-metadata-seeder.service.ts +119 -0
- package/src/seeders/seed-data/solid-core-metadata.json +109 -31
- package/src/services/api-key.service.ts +36 -36
- package/src/services/computed-fields/entity/concat-entity-computed-field-provider.service.ts +15 -1
- package/src/services/crud.service.ts +36 -11
- package/src/services/datasource-introspection.service.ts +29 -16
- package/src/services/import-transaction.service.ts +5 -0
- package/src/services/model-metadata.service.ts +45 -1
- package/src/services/module-metadata.service.ts +35 -0
- package/src/services/removed-field-migration.service.ts +334 -0
- package/src/solid-core.module.ts +4 -0
- package/src/subscribers/audit.subscriber.ts +70 -5
- package/.claude/settings.local.json +0 -16
- package/CLAUDE.md +0 -26
- package/src/services/1.js +0 -6
|
@@ -8746,12 +8746,15 @@
|
|
|
8746
8746
|
48
|
|
8747
8747
|
],
|
|
8748
8748
|
"enableGlobalSearch": true,
|
|
8749
|
-
"create":
|
|
8750
|
-
"edit":
|
|
8749
|
+
"create": false,
|
|
8750
|
+
"edit": false,
|
|
8751
8751
|
"delete": true,
|
|
8752
|
+
"import": false,
|
|
8753
|
+
"export": false,
|
|
8752
8754
|
"allowedViews": [
|
|
8753
8755
|
"list",
|
|
8754
|
-
"card"
|
|
8756
|
+
"card",
|
|
8757
|
+
"tree"
|
|
8755
8758
|
]
|
|
8756
8759
|
},
|
|
8757
8760
|
"children": [
|
|
@@ -9012,7 +9015,13 @@
|
|
|
9012
9015
|
"enableGlobalSearch": true,
|
|
9013
9016
|
"create": false,
|
|
9014
9017
|
"edit": false,
|
|
9015
|
-
"delete": false
|
|
9018
|
+
"delete": false,
|
|
9019
|
+
"import": false,
|
|
9020
|
+
"export": false,
|
|
9021
|
+
"allowedViews": [
|
|
9022
|
+
"list",
|
|
9023
|
+
"tree"
|
|
9024
|
+
]
|
|
9016
9025
|
},
|
|
9017
9026
|
"children": [
|
|
9018
9027
|
{
|
|
@@ -10104,15 +10113,11 @@
|
|
|
10104
10113
|
"swimlanesCount": 5,
|
|
10105
10114
|
"recordsInSwimlane": 10,
|
|
10106
10115
|
"enableGlobalSearch": true,
|
|
10107
|
-
"create":
|
|
10108
|
-
"edit":
|
|
10109
|
-
"delete":
|
|
10116
|
+
"create": false,
|
|
10117
|
+
"edit": false,
|
|
10118
|
+
"delete": false,
|
|
10110
10119
|
"groupBy": "stage",
|
|
10111
|
-
"draggable": false
|
|
10112
|
-
"allowedViews": [
|
|
10113
|
-
"list",
|
|
10114
|
-
"kanban"
|
|
10115
|
-
]
|
|
10120
|
+
"draggable": false
|
|
10116
10121
|
},
|
|
10117
10122
|
"children": [
|
|
10118
10123
|
{
|
|
@@ -11938,7 +11943,11 @@
|
|
|
11938
11943
|
"edit": false,
|
|
11939
11944
|
"delete": false,
|
|
11940
11945
|
"import": false,
|
|
11941
|
-
"export": false
|
|
11946
|
+
"export": false,
|
|
11947
|
+
"allowedViews": [
|
|
11948
|
+
"list",
|
|
11949
|
+
"tree"
|
|
11950
|
+
]
|
|
11942
11951
|
},
|
|
11943
11952
|
"children": [
|
|
11944
11953
|
{
|
|
@@ -13433,7 +13442,13 @@
|
|
|
13433
13442
|
"enableGlobalSearch": true,
|
|
13434
13443
|
"create": false,
|
|
13435
13444
|
"edit": true,
|
|
13436
|
-
"delete": false
|
|
13445
|
+
"delete": false,
|
|
13446
|
+
"import": false,
|
|
13447
|
+
"export": false,
|
|
13448
|
+
"allowedViews": [
|
|
13449
|
+
"list",
|
|
13450
|
+
"tree"
|
|
13451
|
+
]
|
|
13437
13452
|
},
|
|
13438
13453
|
"children": [
|
|
13439
13454
|
{
|
|
@@ -13508,7 +13523,13 @@
|
|
|
13508
13523
|
"enableGlobalSearch": true,
|
|
13509
13524
|
"create": false,
|
|
13510
13525
|
"edit": false,
|
|
13511
|
-
"delete": false
|
|
13526
|
+
"delete": false,
|
|
13527
|
+
"import": false,
|
|
13528
|
+
"export": false,
|
|
13529
|
+
"allowedViews": [
|
|
13530
|
+
"list",
|
|
13531
|
+
"tree"
|
|
13532
|
+
]
|
|
13512
13533
|
},
|
|
13513
13534
|
"children": [
|
|
13514
13535
|
{
|
|
@@ -13592,7 +13613,13 @@
|
|
|
13592
13613
|
"enableGlobalSearch": true,
|
|
13593
13614
|
"create": false,
|
|
13594
13615
|
"edit": false,
|
|
13595
|
-
"delete": false
|
|
13616
|
+
"delete": false,
|
|
13617
|
+
"import": false,
|
|
13618
|
+
"export": false,
|
|
13619
|
+
"allowedViews": [
|
|
13620
|
+
"list",
|
|
13621
|
+
"tree"
|
|
13622
|
+
]
|
|
13596
13623
|
},
|
|
13597
13624
|
"children": [
|
|
13598
13625
|
{
|
|
@@ -13654,7 +13681,13 @@
|
|
|
13654
13681
|
"enableGlobalSearch": true,
|
|
13655
13682
|
"create": false,
|
|
13656
13683
|
"edit": false,
|
|
13657
|
-
"delete": false
|
|
13684
|
+
"delete": false,
|
|
13685
|
+
"import": false,
|
|
13686
|
+
"export": false,
|
|
13687
|
+
"allowedViews": [
|
|
13688
|
+
"list",
|
|
13689
|
+
"tree"
|
|
13690
|
+
]
|
|
13658
13691
|
},
|
|
13659
13692
|
"children": [
|
|
13660
13693
|
{
|
|
@@ -13723,7 +13756,13 @@
|
|
|
13723
13756
|
"enableGlobalSearch": true,
|
|
13724
13757
|
"create": false,
|
|
13725
13758
|
"edit": false,
|
|
13726
|
-
"delete":
|
|
13759
|
+
"delete": true,
|
|
13760
|
+
"import": false,
|
|
13761
|
+
"export": false,
|
|
13762
|
+
"allowedViews": [
|
|
13763
|
+
"list",
|
|
13764
|
+
"tree"
|
|
13765
|
+
]
|
|
13727
13766
|
},
|
|
13728
13767
|
"children": [
|
|
13729
13768
|
{
|
|
@@ -13793,7 +13832,14 @@
|
|
|
13793
13832
|
"enableGlobalSearch": true,
|
|
13794
13833
|
"create": false,
|
|
13795
13834
|
"edit": false,
|
|
13796
|
-
"delete":
|
|
13835
|
+
"delete": true,
|
|
13836
|
+
"import": false,
|
|
13837
|
+
"export": false,
|
|
13838
|
+
"allowedViews": [
|
|
13839
|
+
"list",
|
|
13840
|
+
"card",
|
|
13841
|
+
"tree"
|
|
13842
|
+
]
|
|
13797
13843
|
},
|
|
13798
13844
|
"children": [
|
|
13799
13845
|
{
|
|
@@ -13859,11 +13905,14 @@
|
|
|
13859
13905
|
50
|
|
13860
13906
|
],
|
|
13861
13907
|
"enableGlobalSearch": true,
|
|
13862
|
-
"create":
|
|
13863
|
-
"edit":
|
|
13864
|
-
"delete":
|
|
13865
|
-
"import":
|
|
13866
|
-
"export":
|
|
13908
|
+
"create": true,
|
|
13909
|
+
"edit": true,
|
|
13910
|
+
"delete": true,
|
|
13911
|
+
"import": true,
|
|
13912
|
+
"export": true,
|
|
13913
|
+
"allowedViews": [
|
|
13914
|
+
"list"
|
|
13915
|
+
]
|
|
13867
13916
|
},
|
|
13868
13917
|
"children": [
|
|
13869
13918
|
{
|
|
@@ -13898,7 +13947,11 @@
|
|
|
13898
13947
|
"edit": false,
|
|
13899
13948
|
"delete": false,
|
|
13900
13949
|
"import": false,
|
|
13901
|
-
"export": false
|
|
13950
|
+
"export": false,
|
|
13951
|
+
"allowedViews": [
|
|
13952
|
+
"list",
|
|
13953
|
+
"tree"
|
|
13954
|
+
]
|
|
13902
13955
|
},
|
|
13903
13956
|
"children": [
|
|
13904
13957
|
{
|
|
@@ -13960,7 +14013,11 @@
|
|
|
13960
14013
|
"edit": false,
|
|
13961
14014
|
"delete": false,
|
|
13962
14015
|
"import": false,
|
|
13963
|
-
"export": false
|
|
14016
|
+
"export": false,
|
|
14017
|
+
"allowedViews": [
|
|
14018
|
+
"list",
|
|
14019
|
+
"tree"
|
|
14020
|
+
]
|
|
13964
14021
|
},
|
|
13965
14022
|
"children": [
|
|
13966
14023
|
{
|
|
@@ -14029,7 +14086,11 @@
|
|
|
14029
14086
|
"edit": true,
|
|
14030
14087
|
"delete": true,
|
|
14031
14088
|
"import": true,
|
|
14032
|
-
"export": true
|
|
14089
|
+
"export": true,
|
|
14090
|
+
"allowedViews": [
|
|
14091
|
+
"list",
|
|
14092
|
+
"tree"
|
|
14093
|
+
]
|
|
14033
14094
|
},
|
|
14034
14095
|
"children": [
|
|
14035
14096
|
{
|
|
@@ -14089,7 +14150,11 @@
|
|
|
14089
14150
|
"edit": false,
|
|
14090
14151
|
"delete": false,
|
|
14091
14152
|
"import": false,
|
|
14092
|
-
"export": false
|
|
14153
|
+
"export": false,
|
|
14154
|
+
"allowedViews": [
|
|
14155
|
+
"list",
|
|
14156
|
+
"tree"
|
|
14157
|
+
]
|
|
14093
14158
|
},
|
|
14094
14159
|
"children": [
|
|
14095
14160
|
{
|
|
@@ -14149,7 +14214,11 @@
|
|
|
14149
14214
|
"edit": false,
|
|
14150
14215
|
"delete": false,
|
|
14151
14216
|
"import": false,
|
|
14152
|
-
"export": false
|
|
14217
|
+
"export": false,
|
|
14218
|
+
"allowedViews": [
|
|
14219
|
+
"list",
|
|
14220
|
+
"tree"
|
|
14221
|
+
]
|
|
14153
14222
|
},
|
|
14154
14223
|
"children": [
|
|
14155
14224
|
{
|
|
@@ -14219,7 +14288,11 @@
|
|
|
14219
14288
|
"edit": false,
|
|
14220
14289
|
"delete": false,
|
|
14221
14290
|
"import": false,
|
|
14222
|
-
"export": false
|
|
14291
|
+
"export": false,
|
|
14292
|
+
"allowedViews": [
|
|
14293
|
+
"list",
|
|
14294
|
+
"tree"
|
|
14295
|
+
]
|
|
14223
14296
|
},
|
|
14224
14297
|
"children": [
|
|
14225
14298
|
{
|
|
@@ -14288,7 +14361,12 @@
|
|
|
14288
14361
|
"enableGlobalSearch": true,
|
|
14289
14362
|
"create": true,
|
|
14290
14363
|
"edit": true,
|
|
14291
|
-
"delete": true
|
|
14364
|
+
"delete": true,
|
|
14365
|
+
"import": true,
|
|
14366
|
+
"export": true,
|
|
14367
|
+
"allowedViews": [
|
|
14368
|
+
"list"
|
|
14369
|
+
]
|
|
14292
14370
|
},
|
|
14293
14371
|
"children": [
|
|
14294
14372
|
{
|
|
@@ -26,53 +26,53 @@ export class ApiKeyService {
|
|
|
26
26
|
private readonly permissionMetadataService: PermissionMetadataService,
|
|
27
27
|
private readonly authenticationService: AuthenticationService,
|
|
28
28
|
private readonly userRepository: UserRepository,
|
|
29
|
-
) {}
|
|
29
|
+
) { }
|
|
30
30
|
|
|
31
|
-
async generate(userId: number, dto: CreateApiKeyDto): Promise
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
async generate(userId: number, dto: CreateApiKeyDto): Promise<{ apiKey: string; record: UserApiKey }> {
|
|
32
|
+
const user = await this.apiKeyRepository.manager.findOne(User, {
|
|
33
|
+
where: { id: userId },
|
|
34
|
+
select: ['id', 'isAllowedToGenerateApiKeys'],
|
|
35
|
+
});
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
if (!user?.isAllowedToGenerateApiKeys) {
|
|
38
|
+
throw new ForbiddenException('You are not allowed to generate API keys');
|
|
39
|
+
}
|
|
40
40
|
|
|
41
41
|
const rawKey = 'sldx_' + randomBytes(32).toString('hex');
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
const hashedKey = this.hash(rawKey);
|
|
43
|
+
const maskedKey = 'sldx_****' + rawKey.slice(-4);
|
|
44
|
+
|
|
45
|
+
const record = this.apiKeyRepository.create({
|
|
46
|
+
name: dto.name,
|
|
47
|
+
hashedKey,
|
|
48
|
+
maskedKey,
|
|
49
|
+
isActive: true,
|
|
50
|
+
expiresAt: dto.expiresAt ? new Date(dto.expiresAt) : null,
|
|
51
|
+
user,
|
|
52
|
+
});
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
await this.apiKeyRepository.save(record);
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
// Strip hashedKey from the returned record — maskedKey is all the UI needs
|
|
57
|
+
delete (record as any).hashedKey;
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
return { apiKey: rawKey, record };
|
|
60
|
+
}
|
|
61
61
|
|
|
62
|
-
async validate(rawKey: string): Promise
|
|
63
|
-
|
|
62
|
+
async validate(rawKey: string): Promise<ActiveUserData> {
|
|
63
|
+
const hashedKey = this.hash(rawKey);
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
// Bypass security rules for auth validation — must find the key regardless of caller context
|
|
66
|
+
const keyRecord = await this.apiKeyRepository.findOne({
|
|
67
|
+
where: { hashedKey, isActive: true },
|
|
68
|
+
relations: ['user', 'user.roles'],
|
|
69
|
+
});
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
if (!keyRecord) {
|
|
72
|
+
throw new UnauthorizedException();
|
|
73
|
+
}
|
|
74
74
|
|
|
75
|
-
if(keyRecord.expiresAt && keyRecord.expiresAt < new Date()) {
|
|
75
|
+
if (keyRecord.expiresAt && keyRecord.expiresAt < new Date()) {
|
|
76
76
|
throw new UnauthorizedException('API key expired');
|
|
77
77
|
}
|
|
78
78
|
|
package/src/services/computed-fields/entity/concat-entity-computed-field-provider.service.ts
CHANGED
|
@@ -1,21 +1,29 @@
|
|
|
1
|
+
import dayjs from "dayjs";
|
|
1
2
|
import { Injectable } from "@nestjs/common";
|
|
2
3
|
import { kebabCase, get } from "lodash";
|
|
3
4
|
import { ComputedFieldProvider } from "src/decorators/computed-field-provider.decorator";
|
|
4
5
|
import { CommonEntity } from "src/entities/common.entity";
|
|
5
6
|
import { ComputedFieldMetadata } from "src/helpers/solid-registry";
|
|
6
7
|
import { IEntityPreComputeFieldProvider } from "src/interfaces";
|
|
8
|
+
import { SettingService } from "src/services/setting.service";
|
|
9
|
+
import type { SolidCoreSetting } from "src/services/settings/default-settings-provider.service";
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
export interface ConcatComputedFieldContext {
|
|
10
13
|
separator: string; // The separator to use between concatenated values
|
|
11
14
|
fields: string[]; // The fields to concatenate
|
|
12
15
|
slugify?: boolean; // Optional: if true, slugify each field value before concatenation
|
|
16
|
+
dateFormat?: string; // Optional: format to use for Date values. Defaults to the configured dateFormat setting
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
@ComputedFieldProvider()
|
|
16
20
|
@Injectable()
|
|
17
21
|
export class ConcatEntityComputedFieldProvider<T extends CommonEntity> implements IEntityPreComputeFieldProvider<T, ConcatComputedFieldContext> {
|
|
18
22
|
|
|
23
|
+
constructor(
|
|
24
|
+
private readonly settingService: SettingService,
|
|
25
|
+
) { }
|
|
26
|
+
|
|
19
27
|
name(): string {
|
|
20
28
|
return "ConcatEntityComputedFieldProvider";
|
|
21
29
|
}
|
|
@@ -29,6 +37,8 @@ export class ConcatEntityComputedFieldProvider<T extends CommonEntity> implement
|
|
|
29
37
|
const separator = computedFieldValueProviderCtxt.separator ?? ' ';
|
|
30
38
|
const fields: string[] = computedFieldValueProviderCtxt.fields ?? [];
|
|
31
39
|
const slugify = computedFieldValueProviderCtxt.slugify ?? false;
|
|
40
|
+
const dateFormat = computedFieldValueProviderCtxt.dateFormat
|
|
41
|
+
?? this.settingService.getConfigValue<SolidCoreSetting>("dateFormat");
|
|
32
42
|
|
|
33
43
|
const parts: string[] = [];
|
|
34
44
|
|
|
@@ -38,6 +48,10 @@ export class ConcatEntityComputedFieldProvider<T extends CommonEntity> implement
|
|
|
38
48
|
// normalize to string (skip null/undefined)
|
|
39
49
|
if (val == null) continue;
|
|
40
50
|
|
|
51
|
+
if (val instanceof Date) {
|
|
52
|
+
val = dayjs(val).format(dateFormat);
|
|
53
|
+
}
|
|
54
|
+
|
|
41
55
|
if (typeof val !== 'string') {
|
|
42
56
|
val = String(val);
|
|
43
57
|
}
|
|
@@ -58,4 +72,4 @@ export class ConcatEntityComputedFieldProvider<T extends CommonEntity> implement
|
|
|
58
72
|
(triggerEntity as any)[computedFieldMetadata.fieldName] = concatenatedString;
|
|
59
73
|
}
|
|
60
74
|
|
|
61
|
-
}
|
|
75
|
+
}
|
|
@@ -34,6 +34,7 @@ import { SelectionDynamicFieldCrudManager } from "../helpers/field-crud-managers
|
|
|
34
34
|
import { SelectionStaticFieldCrudManager } from "../helpers/field-crud-managers/SelectionStaticFieldCrudManager";
|
|
35
35
|
import { ShortTextFieldCrudManager } from "../helpers/field-crud-managers/ShortTextFieldCrudManager";
|
|
36
36
|
import { UUIDFieldCrudManager } from "../helpers/field-crud-managers/UUIDFieldCrudManager";
|
|
37
|
+
import { ModelMetadataHelperService } from "src/helpers/model-metadata-helper.service";
|
|
37
38
|
import { FieldCrudManager, MediaWithFullUrl } from "../interfaces";
|
|
38
39
|
import { CrudHelperService, FilterCombinator, UserIdFields } from "./crud-helper.service";
|
|
39
40
|
import { HashingService } from "./hashing.service";
|
|
@@ -42,6 +43,7 @@ import { getMediaStorageProvider } from "./mediaStorageProviders";
|
|
|
42
43
|
import { ModelMetadataService } from "./model-metadata.service";
|
|
43
44
|
import { RequestContextService } from "./request-context.service";
|
|
44
45
|
|
|
46
|
+
const AUDIT_BEFORE_SNAPSHOT = '__auditBeforeSnapshot';
|
|
45
47
|
|
|
46
48
|
export class CRUDService<T extends CommonEntity> { // Add two generic value i.e Person,CreatePersonDto, so we get the proper types in our service
|
|
47
49
|
|
|
@@ -185,7 +187,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
|
|
|
185
187
|
}));
|
|
186
188
|
}
|
|
187
189
|
|
|
188
|
-
|
|
190
|
+
//TODO: Will the updates be partial i.e PATCH or full i.e PUT
|
|
189
191
|
async update(id: number, updateDto: any, files: Express.Multer.File[] = [], isPartialUpdate: boolean = false, solidRequestContext: any = {}, isUpdate: boolean = false): Promise<T> {
|
|
190
192
|
if (!id) {
|
|
191
193
|
throw new Error(ERROR_MESSAGES.ID_REQUIRED_FOR_UPDATE);
|
|
@@ -213,16 +215,8 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
|
|
|
213
215
|
);
|
|
214
216
|
}
|
|
215
217
|
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
// const idFieldMetadata = model.fields.find(f => f.name === 'id');
|
|
219
|
-
// updateDto.id = idFieldMetadata?.type === 'bigint' ? BigInt(id) : id;
|
|
220
|
-
|
|
221
|
-
// This class will be extended by the generated service class i.e PersonService
|
|
222
|
-
// The data required to identify the model and module name will be passed from the generate CrudService subclass
|
|
223
|
-
//TODO: Algorithm to create the entity
|
|
224
|
-
// 1. Fire a query and load all the fields in the provided model name for a particular module
|
|
225
|
-
// FIXME This can be optimized to take in module name i.e (handle scenario wherein same model exists in multiple modules)
|
|
218
|
+
// Capture pre-update many-to-many relation state for audit tracking
|
|
219
|
+
await this.prepareManyToManyAuditSnapshot(entity,id,model.singularName);
|
|
226
220
|
let hasMediaFields = false;
|
|
227
221
|
|
|
228
222
|
const fieldsToProcess = [...model.fields];
|
|
@@ -249,6 +243,37 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
|
|
|
249
243
|
return savedEntity;
|
|
250
244
|
}
|
|
251
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Captures the current state of audit-enabled many-to-many relations
|
|
248
|
+
* before the entity is updated. The snapshot is attached to the entity
|
|
249
|
+
* and can be used later for audit comparison.
|
|
250
|
+
*/
|
|
251
|
+
private async prepareManyToManyAuditSnapshot(entity: T,id: number,modelSingularName: string): Promise<void> {
|
|
252
|
+
const modelMetadataHelperService = this.moduleRef.get(ModelMetadataHelperService, { strict: false });
|
|
253
|
+
const auditRelationFields = (await modelMetadataHelperService.loadFieldHierarchy(modelSingularName)).filter(field =>
|
|
254
|
+
field.enableAuditTracking &&
|
|
255
|
+
field.type === 'relation' &&
|
|
256
|
+
field.relationType !== 'one-to-many'
|
|
257
|
+
);
|
|
258
|
+
if (auditRelationFields.length > 0) {
|
|
259
|
+
const relations: any = {};
|
|
260
|
+
auditRelationFields.forEach(field => relations[field.name] = true);
|
|
261
|
+
const auditBeforeEntity = await this.repo.findOne({
|
|
262
|
+
where: {
|
|
263
|
+
id: id,
|
|
264
|
+
} as unknown as FindOptionsWhere<T>,
|
|
265
|
+
relations: relations as any,
|
|
266
|
+
});
|
|
267
|
+
if (auditBeforeEntity) {
|
|
268
|
+
Object.defineProperty(entity, AUDIT_BEFORE_SNAPSHOT, {
|
|
269
|
+
configurable: true,
|
|
270
|
+
enumerable: false,
|
|
271
|
+
value: auditBeforeEntity,
|
|
272
|
+
writable: true,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
252
277
|
//TODO: Will the updates be partial i.e PATCH or full i.e PUT
|
|
253
278
|
async delete(id: number, solidRequestContext: any = {}) {
|
|
254
279
|
if (!id) {
|
|
@@ -157,7 +157,7 @@ const DATASOURCE_TYPE_FAMILY_ALIASES: Record<DatasourceTypeFamily, string[]> = {
|
|
|
157
157
|
int: ["int", "integer", "smallint", "tinyint"],
|
|
158
158
|
numeric: ["numeric"],
|
|
159
159
|
decimal: ["decimal", "money", "smallmoney"],
|
|
160
|
-
float: ["float", "double", "real"],
|
|
160
|
+
float: ["float", "double", "double precision", "real", "float4", "float8"],
|
|
161
161
|
date: ["date"],
|
|
162
162
|
time: ["time", "time without time zone", "time with time zone"],
|
|
163
163
|
datetime: ["datetime", "datetime2", "smalldatetime", "timestamp", "timestamp without time zone"],
|
|
@@ -818,6 +818,14 @@ export class DatasourceIntrospectionService {
|
|
|
818
818
|
return rest;
|
|
819
819
|
}
|
|
820
820
|
|
|
821
|
+
private sanitizeConfiguredFieldPayload(fieldConfig: Record<string, any> | undefined) {
|
|
822
|
+
if (!fieldConfig || typeof fieldConfig !== "object" || Array.isArray(fieldConfig)) {
|
|
823
|
+
return {};
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
return this.sanitizeExistingFieldPayload(fieldConfig);
|
|
827
|
+
}
|
|
828
|
+
|
|
821
829
|
private normalizeReviewedMetadataModel(
|
|
822
830
|
reviewedModel: Record<string, any> | undefined,
|
|
823
831
|
mappingDto: DatasourceIntrospectionMappingDto,
|
|
@@ -972,34 +980,39 @@ export class DatasourceIntrospectionService {
|
|
|
972
980
|
existingField?: any,
|
|
973
981
|
) {
|
|
974
982
|
const preserved = existingField ? this.sanitizeExistingFieldPayload(existingField) : {};
|
|
983
|
+
const configured = this.sanitizeConfiguredFieldPayload(column.fieldConfig);
|
|
984
|
+
const merged = {
|
|
985
|
+
...preserved,
|
|
986
|
+
...configured,
|
|
987
|
+
};
|
|
975
988
|
const maxLength =
|
|
976
989
|
column.characterMaximumLength && ["shortText", "longText"].includes(column.solidFieldType)
|
|
977
990
|
? column.characterMaximumLength
|
|
978
|
-
:
|
|
991
|
+
: merged.max;
|
|
979
992
|
|
|
980
993
|
return {
|
|
981
|
-
...
|
|
994
|
+
...merged,
|
|
982
995
|
...(column.fieldId ? { id: column.fieldId } : {}),
|
|
983
996
|
name: column.fieldName,
|
|
984
|
-
displayName: column.displayName?.trim() ||
|
|
985
|
-
description:
|
|
997
|
+
displayName: column.displayName?.trim() || merged.displayName || startCase(column.fieldName),
|
|
998
|
+
description: merged.description ?? "",
|
|
986
999
|
type: column.solidFieldType,
|
|
987
1000
|
ormType: column.ormType,
|
|
988
|
-
length:
|
|
989
|
-
defaultValue:
|
|
990
|
-
required:
|
|
991
|
-
unique:
|
|
992
|
-
encrypt:
|
|
993
|
-
index:
|
|
1001
|
+
length: merged.length ?? column.characterMaximumLength ?? undefined,
|
|
1002
|
+
defaultValue: merged.defaultValue ?? column.defaultValue ?? undefined,
|
|
1003
|
+
required: merged.required ?? (!column.nullable || column.isPrimaryKey),
|
|
1004
|
+
unique: merged.unique ?? false,
|
|
1005
|
+
encrypt: merged.encrypt ?? false,
|
|
1006
|
+
index: merged.index ?? false,
|
|
994
1007
|
max: maxLength ?? undefined,
|
|
995
|
-
min:
|
|
996
|
-
private:
|
|
997
|
-
isSystem:
|
|
1008
|
+
min: merged.min ?? undefined,
|
|
1009
|
+
private: merged.private ?? false,
|
|
1010
|
+
isSystem: merged.isSystem ?? false,
|
|
998
1011
|
isMarkedForRemoval: false,
|
|
999
1012
|
columnName: column.columnName,
|
|
1000
1013
|
isUserKey: mappingDto.userKeyField === column.fieldName,
|
|
1001
|
-
enableAuditTracking:
|
|
1002
|
-
isMultiSelect:
|
|
1014
|
+
enableAuditTracking: merged.enableAuditTracking ?? mappingDto.enableAuditTracking,
|
|
1015
|
+
isMultiSelect: merged.isMultiSelect ?? false,
|
|
1003
1016
|
isPrimaryKey: column.isPrimaryKey,
|
|
1004
1017
|
};
|
|
1005
1018
|
}
|
|
@@ -204,6 +204,11 @@ export class ImportTransactionService extends CRUDService<ImportTransaction> {
|
|
|
204
204
|
|
|
205
205
|
// Iterate through the fields and populate the standard instructions
|
|
206
206
|
for (const field of allFields) {
|
|
207
|
+
|
|
208
|
+
// Skip Computed fields
|
|
209
|
+
if (field.type === SolidFieldType.computed) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
207
212
|
// Skip system fields
|
|
208
213
|
if (systemFieldNames.includes(field.name)) {
|
|
209
214
|
continue;
|
|
@@ -27,6 +27,7 @@ import { PermissionMetadata } from '../entities/permission-metadata.entity';
|
|
|
27
27
|
import { RoleMetadata } from '../entities/role-metadata.entity';
|
|
28
28
|
import { ViewMetadata } from '../entities/view-metadata.entity';
|
|
29
29
|
import { CommandService } from '../helpers/command.service';
|
|
30
|
+
import { startNodemonHeartbeat } from '../helpers/nodemon-heartbeat';
|
|
30
31
|
import {
|
|
31
32
|
REFRESH_MODEL_COMMAND,
|
|
32
33
|
REMOVE_FIELDS_COMMAND,
|
|
@@ -967,6 +968,26 @@ export class ModelMetadataService {
|
|
|
967
968
|
@DisallowInProduction()
|
|
968
969
|
async generateCodeViaCtl(modelId: number): Promise<string> {
|
|
969
970
|
const model = await this.findOne(modelId);
|
|
971
|
+
const isEmbedded = process.env.DEFAULT_DATABASE_DRIVER === 'pglite';
|
|
972
|
+
|
|
973
|
+
// When using an embedded PGlite database, the single-connection limit means
|
|
974
|
+
// a spawned `solid refresh-model` subprocess cannot get its own DB connection
|
|
975
|
+
// while the API server is already holding it. Fall back to in-process code
|
|
976
|
+
// generation (which reuses the existing connection).
|
|
977
|
+
if (isEmbedded) {
|
|
978
|
+
this.logger.log('Embedded database detected — generating API code in-process');
|
|
979
|
+
// Prevent nodemon from restarting the server while schematics write files.
|
|
980
|
+
const heartbeat = startNodemonHeartbeat(path.join(process.cwd(), 'src'));
|
|
981
|
+
try {
|
|
982
|
+
return await this.handleGenerateCode({
|
|
983
|
+
modelUserKey: model.singularName,
|
|
984
|
+
dryRun: false,
|
|
985
|
+
});
|
|
986
|
+
} finally {
|
|
987
|
+
heartbeat.stop();
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
970
991
|
return this.commandService.executeCommandWithArgs({
|
|
971
992
|
command: 'npx',
|
|
972
993
|
args: ['@solidxai/solidctl@latest', 'generate', 'model', `--name=${model.singularName}`],
|
|
@@ -1478,7 +1499,7 @@ export class ModelMetadataService {
|
|
|
1478
1499
|
//Filter out the fields by id
|
|
1479
1500
|
const fieldsForRemoval = model.fields.filter((field) => options.fieldIdsForRemoval.includes(+field.id));
|
|
1480
1501
|
const removeOutput = await this.executeRemoveFieldsCommand(model, fieldsForRemoval, options.dryRun);
|
|
1481
|
-
|
|
1502
|
+
const migrationOutput = await this.executeMigrationRemoveFieldsCommand(model, fieldsForRemoval, options.dryRun);
|
|
1482
1503
|
// Remove the fields from the database as well. This also checks, if the field is marked for removal
|
|
1483
1504
|
for (const field of fieldsForRemoval) {
|
|
1484
1505
|
if (field.isMarkedForRemoval) {
|
|
@@ -1515,6 +1536,13 @@ export class ModelMetadataService {
|
|
|
1515
1536
|
|
|
1516
1537
|
return removeOutput;
|
|
1517
1538
|
}
|
|
1539
|
+
// model-metadata.service.ts mein naya method add karo
|
|
1540
|
+
// async executeRemoveFieldsWithModel(model: ModelMetadata, fieldNames: string[], dryRun = false,) {
|
|
1541
|
+
// const fieldsForRemoval = model.fields.filter(
|
|
1542
|
+
// field => fieldNames.includes(field.name)
|
|
1543
|
+
// );
|
|
1544
|
+
// return this.executeRemoveFieldsCommand(model, fieldsForRemoval, dryRun);
|
|
1545
|
+
// }
|
|
1518
1546
|
|
|
1519
1547
|
async generateModelCode(options: CodeGenerationOptions): Promise<string> {
|
|
1520
1548
|
if (!options.modelId && !options.modelUserKey) {
|
|
@@ -1563,6 +1591,22 @@ export class ModelMetadataService {
|
|
|
1563
1591
|
return output;
|
|
1564
1592
|
}
|
|
1565
1593
|
|
|
1594
|
+
private async executeMigrationRemoveFieldsCommand(model: ModelMetadata, fieldsForRemoval: FieldMetadata[], dryRun: boolean = false,): Promise<string> {
|
|
1595
|
+
if (!fieldsForRemoval || fieldsForRemoval.length === 0) {
|
|
1596
|
+
return "";
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
const output = await this.commandService.executeCommandWithArgs({
|
|
1600
|
+
command: 'npx',
|
|
1601
|
+
args: ['@solidxai/solidctl@latest', 'migration', '-n', model.singularName, 'remove-field', ...(dryRun ? [] : ['--apply'])],
|
|
1602
|
+
cwd: path.join(process.cwd(), '..'),
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
this.logger.debug(`Schematic output : ${output}`);
|
|
1606
|
+
return output;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
|
|
1566
1610
|
async updateUserKey(data: any) {
|
|
1567
1611
|
const { modelName, fieldName } = data;
|
|
1568
1612
|
|