@solidxai/core 0.1.11-beta.0 → 0.1.11-beta.11
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/.claude/settings.local.json +2 -1
- package/CLAUDE.md +26 -0
- 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/controllers/datasource-introspection.controller.d.ts +46 -1
- package/dist/controllers/datasource-introspection.controller.d.ts.map +1 -1
- package/dist/controllers/datasource-introspection.controller.js +38 -0
- package/dist/controllers/datasource-introspection.controller.js.map +1 -1
- package/dist/controllers/service.controller.d.ts +1 -0
- package/dist/controllers/service.controller.d.ts.map +1 -1
- package/dist/controllers/service.controller.js +2 -2
- package/dist/controllers/service.controller.js.map +1 -1
- package/dist/dtos/datasource-introspection-mapping.dto.d.ts +5 -0
- package/dist/dtos/datasource-introspection-mapping.dto.d.ts.map +1 -1
- package/dist/dtos/datasource-introspection-mapping.dto.js +24 -2
- package/dist/dtos/datasource-introspection-mapping.dto.js.map +1 -1
- package/dist/entities/{legacy-common-with-id.entity.d.ts → legacy-common-with-generated-id.entity.d.ts} +2 -2
- package/dist/entities/legacy-common-with-generated-id.entity.d.ts.map +1 -0
- package/dist/entities/{legacy-common-with-id.entity.js → legacy-common-with-generated-id.entity.js} +4 -4
- package/dist/entities/legacy-common-with-generated-id.entity.js.map +1 -0
- package/dist/entities/{legacy-common.entity.d.ts → legacy-common.entity-with-existing-id.entity.d.ts} +1 -1
- package/dist/entities/legacy-common.entity-with-existing-id.entity.d.ts.map +1 -0
- package/dist/entities/{legacy-common.entity.js → legacy-common.entity-with-existing-id.entity.js} +1 -1
- package/dist/entities/legacy-common.entity-with-existing-id.entity.js.map +1 -0
- 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/environment.helper.d.ts +1 -0
- package/dist/helpers/environment.helper.d.ts.map +1 -1
- package/dist/helpers/environment.helper.js +4 -0
- package/dist/helpers/environment.helper.js.map +1 -1
- package/dist/helpers/field-crud-managers/IntFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/IntFieldCrudManager.js +4 -1
- package/dist/helpers/field-crud-managers/IntFieldCrudManager.js.map +1 -1
- package/dist/helpers/model-metadata-helper.service.d.ts +2 -1
- package/dist/helpers/model-metadata-helper.service.d.ts.map +1 -1
- package/dist/helpers/model-metadata-helper.service.js +9 -5
- package/dist/helpers/model-metadata-helper.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/helpers/solid-registry.d.ts +2 -1
- package/dist/helpers/solid-registry.d.ts.map +1 -1
- package/dist/helpers/solid-registry.js.map +1 -1
- package/dist/helpers/typeorm-db-helper.d.ts +1 -0
- package/dist/helpers/typeorm-db-helper.d.ts.map +1 -1
- package/dist/helpers/typeorm-db-helper.js +12 -0
- package/dist/helpers/typeorm-db-helper.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/interfaces/datasource-introspection-provider.interface.d.ts +1 -0
- package/dist/interfaces/datasource-introspection-provider.interface.d.ts.map +1 -1
- package/dist/interfaces/datasource-introspection-provider.interface.js.map +1 -1
- package/dist/interfaces.d.ts +1 -0
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/repository/scheduled-job.repository.d.ts +5 -2
- package/dist/repository/scheduled-job.repository.d.ts.map +1 -1
- package/dist/repository/scheduled-job.repository.js +66 -3
- package/dist/repository/scheduled-job.repository.js.map +1 -1
- package/dist/repository/security-rule.repository.d.ts +2 -2
- package/dist/repository/security-rule.repository.d.ts.map +1 -1
- package/dist/repository/security-rule.repository.js +6 -3
- package/dist/repository/security-rule.repository.js.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts +43 -2
- package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +1173 -232
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +128 -34
- package/dist/services/action-metadata.service.d.ts +7 -0
- package/dist/services/action-metadata.service.d.ts.map +1 -1
- package/dist/services/action-metadata.service.js +76 -3
- package/dist/services/action-metadata.service.js.map +1 -1
- package/dist/services/api-key.service.d.ts.map +1 -1
- package/dist/services/api-key.service.js.map +1 -1
- package/dist/services/authentication.service.d.ts +2 -0
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +32 -11
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.d.ts.map +1 -1
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js +7 -5
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.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-helper.service.d.ts.map +1 -1
- package/dist/services/crud-helper.service.js +25 -12
- package/dist/services/crud-helper.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/mssql-datasource-introspection-provider.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js +12 -0
- package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js.map +1 -1
- package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js +2 -0
- package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js.map +1 -1
- package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js +28 -15
- package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js.map +1 -1
- package/dist/services/datasource-introspection.service.d.ts +62 -2
- package/dist/services/datasource-introspection.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection.service.js +654 -186
- 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/locale.service.d.ts +14 -0
- package/dist/services/locale.service.d.ts.map +1 -1
- package/dist/services/locale.service.js +83 -0
- package/dist/services/locale.service.js.map +1 -1
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +10 -10
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
- package/dist/services/menu-item-metadata.service.d.ts.map +1 -1
- package/dist/services/menu-item-metadata.service.js +1 -0
- package/dist/services/menu-item-metadata.service.js.map +1 -1
- package/dist/services/model-metadata.service.d.ts +3 -0
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +78 -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 +28 -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/services/role-metadata.service.d.ts +5 -0
- package/dist/services/role-metadata.service.d.ts.map +1 -1
- package/dist/services/role-metadata.service.js +95 -33
- package/dist/services/role-metadata.service.js.map +1 -1
- package/dist/services/settings/default-settings-provider.service.d.ts +244 -0
- package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
- package/dist/services/settings/default-settings-provider.service.js +120 -0
- package/dist/services/settings/default-settings-provider.service.js.map +1 -1
- package/dist/services/solid-ts-morph.service.d.ts +6 -0
- package/dist/services/solid-ts-morph.service.d.ts.map +1 -1
- package/dist/services/solid-ts-morph.service.js +43 -0
- package/dist/services/solid-ts-morph.service.js.map +1 -1
- package/dist/services/view-metadata.service.d.ts +6 -0
- package/dist/services/view-metadata.service.d.ts.map +1 -1
- package/dist/services/view-metadata.service.js +70 -8
- package/dist/services/view-metadata.service.js.map +1 -1
- 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 +5 -1
- package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
- package/dist/subscribers/audit.subscriber.js +76 -12
- package/dist/subscribers/audit.subscriber.js.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.d.ts +9 -1
- package/dist/subscribers/computed-entity-field.subscriber.d.ts.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.js +45 -11
- package/dist/subscribers/computed-entity-field.subscriber.js.map +1 -1
- package/dist/subscribers/created-by-updated-by.subscriber.d.ts.map +1 -1
- package/dist/subscribers/created-by-updated-by.subscriber.js.map +1 -1
- package/dist/subscribers/field-metadata.subscriber.d.ts.map +1 -1
- package/dist/subscribers/field-metadata.subscriber.js.map +1 -1
- package/dist/subscribers/scheduled-job.subscriber.d.ts.map +1 -1
- package/dist/subscribers/scheduled-job.subscriber.js +3 -2
- package/dist/subscribers/scheduled-job.subscriber.js.map +1 -1
- package/dist/subscribers/security-rule.subscriber.d.ts.map +1 -1
- package/dist/subscribers/security-rule.subscriber.js +5 -3
- package/dist/subscribers/security-rule.subscriber.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/migrate-removed-fields.command.ts +81 -0
- package/src/controllers/datasource-introspection.controller.ts +27 -1
- package/src/controllers/service.controller.ts +3 -2
- package/src/dtos/datasource-introspection-mapping.dto.ts +15 -0
- package/src/entities/{legacy-common-with-id.entity.ts → legacy-common-with-generated-id.entity.ts} +1 -1
- package/src/helpers/command.service.ts +10 -2
- package/src/helpers/environment.helper.ts +11 -0
- package/src/helpers/field-crud-managers/IntFieldCrudManager.ts +4 -1
- package/src/helpers/model-metadata-helper.service.ts +8 -3
- package/src/helpers/nodemon-heartbeat.ts +59 -0
- package/src/helpers/solid-registry.ts +7 -1
- package/src/helpers/typeorm-db-helper.ts +15 -0
- package/src/index.ts +3 -2
- package/src/interfaces/datasource-introspection-provider.interface.ts +1 -0
- package/src/interfaces.ts +1 -0
- package/src/repository/scheduled-job.repository.ts +83 -4
- package/src/repository/security-rule.repository.ts +10 -4
- package/src/seeders/module-metadata-seeder.service.ts +1415 -264
- package/src/seeders/seed-data/solid-core-metadata.json +128 -34
- package/src/services/action-metadata.service.ts +87 -2
- package/src/services/api-key.service.ts +36 -36
- package/src/services/authentication.service.ts +46 -12
- package/src/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.ts +11 -5
- package/src/services/computed-fields/entity/concat-entity-computed-field-provider.service.ts +15 -1
- package/src/services/crud-helper.service.ts +25 -12
- package/src/services/crud.service.ts +36 -11
- package/src/services/datasource-introspection/mssql-datasource-introspection-provider.service.ts +12 -0
- package/src/services/datasource-introspection/mysql-datasource-introspection-provider.service.ts +2 -0
- package/src/services/datasource-introspection/postgres-datasource-introspection-provider.service.ts +28 -15
- package/src/services/datasource-introspection.service.ts +811 -161
- package/src/services/import-transaction.service.ts +5 -0
- package/src/services/locale.service.ts +111 -3
- package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +2 -2
- package/src/services/menu-item-metadata.service.ts +2 -1
- package/src/services/model-metadata.service.ts +109 -1
- package/src/services/module-metadata.service.ts +36 -0
- package/src/services/removed-field-migration.service.ts +334 -0
- package/src/services/role-metadata.service.ts +112 -49
- package/src/services/settings/default-settings-provider.service.ts +120 -0
- package/src/services/solid-ts-morph.service.ts +61 -1
- package/src/services/view-metadata.service.ts +83 -9
- package/src/solid-core.module.ts +4 -0
- package/src/subscribers/audit.subscriber.ts +101 -18
- package/src/subscribers/computed-entity-field.subscriber.ts +81 -28
- package/src/subscribers/created-by-updated-by.subscriber.ts +3 -0
- package/src/subscribers/field-metadata.subscriber.ts +3 -0
- package/src/subscribers/scheduled-job.subscriber.ts +3 -2
- package/src/subscribers/security-rule.subscriber.ts +8 -3
- package/dist/entities/legacy-common-with-id.entity.d.ts.map +0 -1
- package/dist/entities/legacy-common-with-id.entity.js.map +0 -1
- package/dist/entities/legacy-common.entity.d.ts.map +0 -1
- package/dist/entities/legacy-common.entity.js.map +0 -1
- /package/src/entities/{legacy-common.entity.ts → legacy-common.entity-with-existing-id.entity.ts} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
|
+
import * as os from 'os';
|
|
3
4
|
import * as path from 'path';
|
|
4
5
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
6
|
|
|
@@ -14,12 +15,13 @@ import { ListOfValuesService } from 'src/services/list-of-values.service';
|
|
|
14
15
|
import { SettingService } from 'src/services/setting.service';
|
|
15
16
|
import { SmsTemplateService } from 'src/services/sms-template.service';
|
|
16
17
|
import { UserService } from 'src/services/user.service';
|
|
17
|
-
import { DataSource, In } from 'typeorm';
|
|
18
|
+
import { DataSource, In, Repository } from 'typeorm';
|
|
18
19
|
import { CreateModelMetadataDto } from '../dtos/create-model-metadata.dto';
|
|
19
20
|
import { CreateModuleMetadataDto } from '../dtos/create-module-metadata.dto';
|
|
20
21
|
import { getDynamicModuleNamesBasedOnMetadata } from '../helpers/module.helper';
|
|
21
22
|
import { SolidRegistry } from '../helpers/solid-registry';
|
|
22
23
|
import { ActionMetadataService } from '../services/action-metadata.service';
|
|
24
|
+
import { ApiKeyService } from '../services/api-key.service';
|
|
23
25
|
import { FieldMetadataService } from '../services/field-metadata.service';
|
|
24
26
|
import { MediaStorageProviderMetadataService } from '../services/media-storage-provider-metadata.service';
|
|
25
27
|
import { ModelMetadataService } from '../services/model-metadata.service';
|
|
@@ -33,18 +35,22 @@ import { ActionMetadata } from '../entities/action-metadata.entity';
|
|
|
33
35
|
import { MenuItemMetadata } from '../entities/menu-item-metadata.entity';
|
|
34
36
|
import { ModuleMetadata } from '../entities/module-metadata.entity';
|
|
35
37
|
import { RoleMetadata } from '../entities/role-metadata.entity';
|
|
38
|
+
import { User } from '../entities/user.entity';
|
|
36
39
|
import { MENU_ROLE_JOIN_TABLE_NAME, MENU_ROLE_JOIN_TABLE_NAME_MENU_COL, MENU_ROLE_JOIN_TABLE_NAME_ROLE_COL } from '../dtos/create-menu-item-metadata.dto';
|
|
37
40
|
import { DEFAULT_SA_PASSWORD } from '../dtos/create-user.dto';
|
|
38
41
|
import { SignUpDto } from '../dtos/sign-up.dto';
|
|
39
42
|
import { ADMIN_ROLE_NAME, CreateRoleMetadataDto } from 'src/dtos/create-role-metadata.dto';
|
|
40
43
|
import { CreateSavedFiltersDto } from 'src/dtos/create-saved-filters.dto';
|
|
41
44
|
import { CreateScheduledJobDto } from 'src/dtos/create-scheduled-job.dto';
|
|
45
|
+
import { CreateLocaleDto } from 'src/dtos/create-locale.dto';
|
|
42
46
|
import { PermissionMetadataRepository } from 'src/repository/permission-metadata.repository';
|
|
43
47
|
import { SavedFiltersRepository } from 'src/repository/saved-filters.repository';
|
|
44
48
|
import { ScheduledJobRepository } from 'src/repository/scheduled-job.repository';
|
|
45
49
|
import { SettingRepository } from 'src/repository/setting.repository';
|
|
50
|
+
import { UserApiKeyRepository } from 'src/repository/user-api-key.repository';
|
|
46
51
|
import { CreateModelSequenceDto } from 'src/dtos/create-model-sequence.dto';
|
|
47
52
|
import { ModelSequenceRepository } from 'src/repository/model-sequence.repository';
|
|
53
|
+
import { LocaleRepository } from 'src/repository/locale.repository';
|
|
48
54
|
import { ModelSequence } from 'src/entities/model-sequence.entity';
|
|
49
55
|
import { SavedFilters } from 'src/entities/saved-filters.entity';
|
|
50
56
|
import { ScheduledJob } from 'src/entities/scheduled-job.entity';
|
|
@@ -55,10 +61,37 @@ import { ModelMetadata } from 'src/entities/model-metadata.entity';
|
|
|
55
61
|
import { PermissionMetadata } from 'src/entities/permission-metadata.entity';
|
|
56
62
|
import { ViewMetadata } from 'src/entities/view-metadata.entity';
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Central metadata seeder for both solid-core and consuming modules.
|
|
66
|
+
*
|
|
67
|
+
* Performance notes:
|
|
68
|
+
* - This file now contains the main seed-time optimizations that were added after profiling real FRMS seed runs.
|
|
69
|
+
* - The goal of those changes was to preserve existing behavior while removing avoidable serial database work.
|
|
70
|
+
* - Verbose timing logs are intentionally emitted through Nest logger `debug()` only, so normal CLI runs stay clean.
|
|
71
|
+
* - Every timing line is tagged with `[SEED_TIMING]` and a stable `[ITEM:...]` identifier for grep-friendly analysis.
|
|
72
|
+
*
|
|
73
|
+
* High-impact optimizations currently implemented here:
|
|
74
|
+
* - Batched permission preload/insert, with case-insensitive dedupe to match MSSQL collation behavior.
|
|
75
|
+
* - Process-local lookup caches for module/model/view/provider reads during a single seed run.
|
|
76
|
+
* - Model-level field preload/diff to avoid field-level serial existence checks.
|
|
77
|
+
* - View/action preload + in-memory no-op detection so unchanged rows skip save() entirely.
|
|
78
|
+
* - Menu entity no-op detection, though menu relation lookups are still mostly serial and remain a future candidate.
|
|
79
|
+
*/
|
|
59
80
|
@Injectable()
|
|
60
81
|
export class ModuleMetadataSeederService {
|
|
61
82
|
private readonly logger = new Logger(ModuleMetadataSeederService.name);
|
|
83
|
+
// Stable tag used on all verbose seed timing logs so runs can be grepped quickly.
|
|
84
|
+
private readonly seedTimingTag = 'SEED_TIMING';
|
|
85
|
+
// Keep permission batching bounded while still collapsing thousands of serial checks into a few queries.
|
|
86
|
+
private readonly permissionSeedBatchSize = 500;
|
|
87
|
+
// Restrict no-op detection to the fields that actually matter for persistence.
|
|
88
|
+
private readonly viewComparableFields = ['name', 'displayName', 'type', 'context', 'layout', 'module', 'model'] as const;
|
|
89
|
+
private readonly actionComparableFields = ['name', 'displayName', 'type', 'domain', 'context', 'customComponent', 'customIsModal', 'serverEndpoint', 'module', 'model', 'view'] as const;
|
|
90
|
+
// These caches are intentionally process-local and are reset at the beginning of every seed run.
|
|
91
|
+
private readonly moduleLookupCache = new Map<string, ModuleMetadata | null>();
|
|
92
|
+
private readonly modelLookupCache = new Map<string, ModelMetadata | null>();
|
|
93
|
+
private readonly viewLookupCache = new Map<string, ViewMetadata | null>();
|
|
94
|
+
private readonly mediaStorageProviderLookupCache = new Map<string, any | null>();
|
|
62
95
|
private enablePruning: boolean = false;
|
|
63
96
|
|
|
64
97
|
constructor(
|
|
@@ -70,11 +103,13 @@ export class ModuleMetadataSeederService {
|
|
|
70
103
|
private readonly roleService: RoleMetadataService,
|
|
71
104
|
private readonly userService: UserService,
|
|
72
105
|
private readonly authenticationService: AuthenticationService,
|
|
106
|
+
private readonly apiKeyService: ApiKeyService,
|
|
73
107
|
private readonly solidActionService: ActionMetadataService,
|
|
74
108
|
private readonly solidViewService: ViewMetadataService,
|
|
75
109
|
private readonly emailTemplateService: EmailTemplateService,
|
|
76
110
|
private readonly smsTemplateService: SmsTemplateService,
|
|
77
111
|
private readonly listOfValuesService: ListOfValuesService,
|
|
112
|
+
private readonly userApiKeyRepository: UserApiKeyRepository,
|
|
78
113
|
// @InjectRepository(PermissionMetadata)
|
|
79
114
|
// private readonly permissionRepo: Repository<PermissionMetadata>,
|
|
80
115
|
private readonly permissionRepo: PermissionMetadataRepository,
|
|
@@ -89,6 +124,7 @@ export class ModuleMetadataSeederService {
|
|
|
89
124
|
readonly savedFiltersRepo: SavedFiltersRepository,
|
|
90
125
|
readonly dataSource: DataSource,
|
|
91
126
|
readonly modelSequenceRepo: ModelSequenceRepository,
|
|
127
|
+
readonly localeRepo: LocaleRepository,
|
|
92
128
|
) { }
|
|
93
129
|
|
|
94
130
|
async seed(conf?: any) {
|
|
@@ -99,139 +135,150 @@ export class ModuleMetadataSeederService {
|
|
|
99
135
|
|
|
100
136
|
try {
|
|
101
137
|
this.enablePruning = Boolean(conf?.pruneMetadata);
|
|
138
|
+
// Never reuse cached entity snapshots across seed invocations in the same process.
|
|
139
|
+
this.resetLookupCaches();
|
|
102
140
|
console.log(this.enablePruning ? '▶ Pruning enabled: metadata not present in JSON will be removed.' : '▶ Pruning disabled: existing metadata will be kept.');
|
|
103
141
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// Module specific seeding steps.
|
|
113
|
-
// Get all the module metadata files which needs to be seeded.
|
|
114
|
-
const seedDataFiles = this.seedDataFiles;
|
|
115
|
-
this.logger.debug(`Found seed data for modules: ${seedDataFiles.map(s => s.moduleMetadata?.name)}`);
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* -------------------------------------------------------------
|
|
119
|
-
* Selective module seeding via: solid seed --modules-to-seed onboarding,reports
|
|
120
|
-
* -------------------------------------------------------------
|
|
121
|
-
*/
|
|
122
|
-
currentStep = 'resolveModulesToSeed';
|
|
123
|
-
if (conf && Array.isArray(conf.modulesToSeed)) {
|
|
124
|
-
modulesToSeed = conf.modulesToSeed;
|
|
125
|
-
console.log(`▶ Selective seeding enabled. Modules to seed: ${modulesToSeed.join(', ')}`);
|
|
126
|
-
this.logger.log(`Selective seeding enabled. Modules to seed: ${modulesToSeed.join(', ')}`);
|
|
127
|
-
} else {
|
|
128
|
-
console.log(`▶ No modulesToSeed provided. Seeding ALL modules.`);
|
|
129
|
-
this.logger.log(`No modulesToSeed provided. Seeding ALL modules.`);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Filter modules if needed
|
|
133
|
-
const filteredSeedDataFiles = modulesToSeed ? seedDataFiles.filter((file) => modulesToSeed.includes(file.moduleMetadata?.name)) : seedDataFiles;
|
|
142
|
+
await this.timeOperation('seed-run', async () => {
|
|
143
|
+
// Global seeding steps i.e across all modules
|
|
144
|
+
if (shouldSeedGlobalMetadata) {
|
|
145
|
+
currentStep = 'seedGlobalMetadata';
|
|
146
|
+
await this.timeOperation('global-seed', () => this.seedGlobalMetadata(), { moduleName: 'global', component: 'global-metadata' });
|
|
147
|
+
} else {
|
|
148
|
+
this.logger.log(`Skipping global metadata seeding.`);
|
|
149
|
+
}
|
|
134
150
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
151
|
+
// Module specific seeding steps.
|
|
152
|
+
// Get all the module metadata files which needs to be seeded.
|
|
153
|
+
const seedDataFiles = this.seedDataFiles;
|
|
154
|
+
this.logger.debug(`Found seed data for modules: ${seedDataFiles.map(s => s.moduleMetadata?.name)}`);
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* -------------------------------------------------------------
|
|
158
|
+
* Selective module seeding via: solid seed --modules-to-seed onboarding,reports
|
|
159
|
+
* -------------------------------------------------------------
|
|
160
|
+
*/
|
|
161
|
+
currentStep = 'resolveModulesToSeed';
|
|
162
|
+
if (conf && Array.isArray(conf.modulesToSeed)) {
|
|
163
|
+
modulesToSeed = conf.modulesToSeed;
|
|
164
|
+
console.log(`▶ Selective seeding enabled. Modules to seed: ${modulesToSeed.join(', ')}`);
|
|
165
|
+
this.logger.log(`Selective seeding enabled. Modules to seed: ${modulesToSeed.join(', ')}`);
|
|
166
|
+
} else {
|
|
167
|
+
console.log(`▶ No modulesToSeed provided. Seeding ALL modules.`);
|
|
168
|
+
this.logger.log(`No modulesToSeed provided. Seeding ALL modules.`);
|
|
169
|
+
}
|
|
139
170
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
for (let i = 0; i < filteredSeedDataFiles.length; i++) {
|
|
143
|
-
const overallMetadata = filteredSeedDataFiles[i];
|
|
144
|
-
const moduleMetadata: CreateModuleMetadataDto = overallMetadata.moduleMetadata;
|
|
145
|
-
currentModule = moduleMetadata?.name ?? 'unknown';
|
|
171
|
+
// Filter modules if needed
|
|
172
|
+
const filteredSeedDataFiles = modulesToSeed ? seedDataFiles.filter((file) => modulesToSeed.includes(file.moduleMetadata?.name)) : seedDataFiles;
|
|
146
173
|
|
|
147
|
-
if (
|
|
148
|
-
this.logger.warn(`
|
|
149
|
-
|
|
174
|
+
if (filteredSeedDataFiles.length === 0) {
|
|
175
|
+
this.logger.warn(`No modules matched the provided modulesToSeed list.`);
|
|
176
|
+
return;
|
|
150
177
|
}
|
|
151
178
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
this.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
179
|
+
// let usersDetail;
|
|
180
|
+
// For each module metadata file, we will process the seeding steps one by one.
|
|
181
|
+
for (let i = 0; i < filteredSeedDataFiles.length; i++) {
|
|
182
|
+
const overallMetadata = filteredSeedDataFiles[i];
|
|
183
|
+
const moduleMetadata: CreateModuleMetadataDto = overallMetadata.moduleMetadata;
|
|
184
|
+
currentModule = moduleMetadata?.name ?? 'unknown';
|
|
185
|
+
|
|
186
|
+
if (!moduleMetadata?.name) {
|
|
187
|
+
this.logger.warn(`Skipping seed metadata file because moduleMetadata.name is missing.`);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log(`▶ Seeding Metadata for Module: ${moduleMetadata.name}`);
|
|
192
|
+
this.logger.log(`Seeding Metadata for Module: ${moduleMetadata.name}`);
|
|
193
|
+
|
|
194
|
+
await this.timeOperation('module-total', async () => {
|
|
195
|
+
currentStep = 'seedMediaStorageProviders';
|
|
196
|
+
this.logger.log(`Seeding Media Storage Providers`);
|
|
197
|
+
const mediaStorageCounts = await this.timeOperation('seed-media-storage-providers', () => this.seedMediaStorageProviders(overallMetadata.mediaStorageProviders), { moduleName: moduleMetadata.name, component: 'media-storage-providers' });
|
|
198
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'Media Storage Providers', mediaStorageCounts)}`);
|
|
199
|
+
|
|
200
|
+
// Process module metadata first.
|
|
201
|
+
currentStep = 'seedModuleModelFields';
|
|
202
|
+
this.logger.log(`Seeding Module / Model / Fields`);
|
|
203
|
+
const moduleModelFieldCounts = await this.timeOperation('seed-module-model-fields', () => this.seedModuleModelFields(moduleMetadata), { moduleName: moduleMetadata.name, component: 'module-model-fields' });
|
|
204
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'Module/Model/Fields', moduleModelFieldCounts)}`);
|
|
205
|
+
|
|
206
|
+
currentStep = 'seedLocales';
|
|
207
|
+
this.logger.log(`Seeding Locales`);
|
|
208
|
+
const localeCounts = await this.seedLocales(overallMetadata);
|
|
209
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'Locales', localeCounts)}`);
|
|
210
|
+
|
|
211
|
+
currentStep = 'seedPermissions';
|
|
212
|
+
this.logger.log(`Seeding Permissions`);
|
|
213
|
+
const permissionCounts = await this.timeOperation('seed-permissions', () => this.seedPermissions(overallMetadata), { moduleName: moduleMetadata.name, component: 'permissions' });
|
|
214
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'Permissions', permissionCounts)}`);
|
|
215
|
+
|
|
216
|
+
currentStep = 'seedRoles';
|
|
217
|
+
this.logger.log(`Seeding Roles`);
|
|
218
|
+
const roleCounts = await this.timeOperation('seed-roles', () => this.seedRoles(overallMetadata), { moduleName: moduleMetadata.name, component: 'roles' });
|
|
219
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'Roles', roleCounts)}`);
|
|
220
|
+
|
|
221
|
+
currentStep = 'seedUsers';
|
|
222
|
+
this.logger.log(`Seeding Users`);
|
|
223
|
+
const userCounts = await this.timeOperation('seed-users', () => this.seedUsers(overallMetadata), { moduleName: moduleMetadata.name, component: 'users' });
|
|
224
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'Users', userCounts)}`);
|
|
225
|
+
|
|
226
|
+
currentStep = 'seedViews';
|
|
227
|
+
this.logger.log(`Seeding Views`);
|
|
228
|
+
const viewCounts = await this.timeOperation('seed-views', () => this.seedViews(overallMetadata), { moduleName: moduleMetadata.name, component: 'views' });
|
|
229
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'Views', viewCounts)}`);
|
|
230
|
+
|
|
231
|
+
currentStep = 'seedActions';
|
|
232
|
+
this.logger.log(`Seeding Actions`);
|
|
233
|
+
const actionCounts = await this.timeOperation('seed-actions', () => this.seedActions(overallMetadata), { moduleName: moduleMetadata.name, component: 'actions' });
|
|
234
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'Actions', actionCounts)}`);
|
|
235
|
+
|
|
236
|
+
currentStep = 'seedMenus';
|
|
237
|
+
this.logger.log(`Seeding Menus`);
|
|
238
|
+
const menuCounts = await this.timeOperation('seed-menus', () => this.seedMenus(overallMetadata), { moduleName: moduleMetadata.name, component: 'menus' });
|
|
239
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'Menus', menuCounts)}`);
|
|
240
|
+
|
|
241
|
+
currentStep = 'seedEmailTemplates';
|
|
242
|
+
this.logger.log(`Seeding Email Templates`);
|
|
243
|
+
const emailTemplateCounts = await this.timeOperation('seed-email-templates', () => this.seedEmailTemplates(overallMetadata, moduleMetadata.name), { moduleName: moduleMetadata.name, component: 'email-templates' });
|
|
244
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'Email Templates', emailTemplateCounts)}`);
|
|
245
|
+
|
|
246
|
+
currentStep = 'seedSmsTemplates';
|
|
247
|
+
this.logger.log(`Seeding Sms Templates`);
|
|
248
|
+
const smsTemplateCounts = await this.timeOperation('seed-sms-templates', () => this.seedSmsTemplates(overallMetadata, moduleMetadata.name), { moduleName: moduleMetadata.name, component: 'sms-templates' });
|
|
249
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'Sms Templates', smsTemplateCounts)}`);
|
|
250
|
+
|
|
251
|
+
currentStep = 'seedSecurityRules';
|
|
252
|
+
this.logger.log(`Seeding Security Rules`);
|
|
253
|
+
const securityRuleCounts = await this.timeOperation('seed-security-rules', () => this.seedSecurityRules(overallMetadata), { moduleName: moduleMetadata.name, component: 'security-rules' });
|
|
254
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'Security Rules', securityRuleCounts)}`);
|
|
255
|
+
|
|
256
|
+
currentStep = 'seedListOfValues';
|
|
257
|
+
this.logger.log(`Seeding List Of Values`);
|
|
258
|
+
const lovCounts = await this.timeOperation('seed-list-of-values', () => this.seedListOfValues(moduleMetadata, overallMetadata), { moduleName: moduleMetadata.name, component: 'list-of-values' });
|
|
259
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'List Of Values', lovCounts)}`);
|
|
260
|
+
|
|
261
|
+
currentStep = 'seedScheduledJobs';
|
|
262
|
+
this.logger.log(`Seeding Scheduled Jobs`);
|
|
263
|
+
const scheduledJobCounts = await this.timeOperation('seed-scheduled-jobs', () => this.seedScheduledJobs(moduleMetadata, overallMetadata), { moduleName: moduleMetadata.name, component: 'scheduled-jobs' });
|
|
264
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'Scheduled Jobs', scheduledJobCounts)}`);
|
|
265
|
+
|
|
266
|
+
currentStep = 'seedSavedFilters';
|
|
267
|
+
this.logger.log(`Seeding Saved Filters`);
|
|
268
|
+
const savedFilterCounts = await this.timeOperation('seed-saved-filters', () => this.seedSavedFilters(moduleMetadata, overallMetadata), { moduleName: moduleMetadata.name, component: 'saved-filters' });
|
|
269
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'Saved Filters', savedFilterCounts)}`);
|
|
270
|
+
|
|
271
|
+
currentStep = 'seedModelSequences';
|
|
272
|
+
this.logger.log(`Seeding Model Sequences`);
|
|
273
|
+
const modelSequenceCounts = await this.timeOperation('seed-model-sequences', () => this.seedModelSequences(overallMetadata), { moduleName: moduleMetadata.name, component: 'model-sequences' });
|
|
274
|
+
console.log(`${this.formatSeedResult(moduleMetadata.name, 'Model Sequences', modelSequenceCounts)}`);
|
|
275
|
+
}, { moduleName: moduleMetadata.name, component: 'module-seed' });
|
|
276
|
+
}
|
|
231
277
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
278
|
+
currentModule = 'global';
|
|
279
|
+
currentStep = 'setupDefaultRolesWithPermissions';
|
|
280
|
+
await this.timeOperation('setup-default-roles-with-permissions', () => this.setupDefaultRolesWithPermissions(), { moduleName: 'global', component: 'default-roles' });
|
|
281
|
+
}, { moduleName: 'global', component: 'seed-run' });
|
|
235
282
|
|
|
236
283
|
// Add a console log indicating seeding is finished. This needs to be console.log so that it looks proper when this code is run via CLI.
|
|
237
284
|
console.log(`✔ Seeding completed.`);
|
|
@@ -250,40 +297,213 @@ export class ModuleMetadataSeederService {
|
|
|
250
297
|
}
|
|
251
298
|
}
|
|
252
299
|
|
|
300
|
+
private buildTimingPrefix(itemTag: string, options?: { moduleName?: string; component?: string; serviceCall?: string }): string {
|
|
301
|
+
const parts = [`[${this.seedTimingTag}]`, `[ITEM:${itemTag}]`];
|
|
302
|
+
if (options?.moduleName) {
|
|
303
|
+
parts.push(`[MODULE:${options.moduleName}]`);
|
|
304
|
+
}
|
|
305
|
+
if (options?.component) {
|
|
306
|
+
parts.push(`[COMPONENT:${options.component}]`);
|
|
307
|
+
}
|
|
308
|
+
if (options?.serviceCall) {
|
|
309
|
+
parts.push(`[CALL:${options.serviceCall}]`);
|
|
310
|
+
}
|
|
311
|
+
return parts.join(' ');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private formatDuration(durationMs: number): string {
|
|
315
|
+
return durationMs >= 1000
|
|
316
|
+
? `${(durationMs / 1000).toFixed(2)} s (${durationMs.toFixed(2)} ms)`
|
|
317
|
+
: `${durationMs.toFixed(2)} ms`;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private resetLookupCaches(): void {
|
|
321
|
+
this.moduleLookupCache.clear();
|
|
322
|
+
this.modelLookupCache.clear();
|
|
323
|
+
this.viewLookupCache.clear();
|
|
324
|
+
this.mediaStorageProviderLookupCache.clear();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// These cache-backed helpers now log only cache misses. Earlier versions also logged cache hits and operation
|
|
328
|
+
// starts, but that produced too much verbose noise relative to the debugging value.
|
|
329
|
+
private async getModuleByUserKeyCached(
|
|
330
|
+
moduleUserKey: string,
|
|
331
|
+
options?: { moduleName?: string; component?: string; details?: string },
|
|
332
|
+
): Promise<ModuleMetadata | null> {
|
|
333
|
+
if (!moduleUserKey) {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
if (this.moduleLookupCache.has(moduleUserKey)) {
|
|
337
|
+
return this.moduleLookupCache.get(moduleUserKey) ?? null;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const module = await this.timeOperation('module-find-by-user-key-cache-miss', () => this.moduleMetadataService.findOneByUserKey(moduleUserKey), {
|
|
341
|
+
moduleName: options?.moduleName ?? moduleUserKey,
|
|
342
|
+
component: options?.component,
|
|
343
|
+
serviceCall: 'moduleMetadataService.findOneByUserKey',
|
|
344
|
+
details: options?.details ?? `module=${moduleUserKey}`,
|
|
345
|
+
});
|
|
346
|
+
this.moduleLookupCache.set(moduleUserKey, module ?? null);
|
|
347
|
+
return module ?? null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private async getModelByUserKeyCached(
|
|
351
|
+
modelUserKey: string,
|
|
352
|
+
options?: { moduleName?: string; component?: string; details?: string },
|
|
353
|
+
): Promise<ModelMetadata | null> {
|
|
354
|
+
if (!modelUserKey) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
if (this.modelLookupCache.has(modelUserKey)) {
|
|
358
|
+
return this.modelLookupCache.get(modelUserKey) ?? null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const model = await this.timeOperation('model-find-by-user-key-cache-miss', () => this.modelMetadataService.findOneByUserKey(modelUserKey), {
|
|
362
|
+
moduleName: options?.moduleName,
|
|
363
|
+
component: options?.component,
|
|
364
|
+
serviceCall: 'modelMetadataService.findOneByUserKey',
|
|
365
|
+
details: options?.details ?? `model=${modelUserKey}`,
|
|
366
|
+
});
|
|
367
|
+
this.modelLookupCache.set(modelUserKey, model ?? null);
|
|
368
|
+
return model ?? null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private async getViewByUserKeyCached(
|
|
372
|
+
viewUserKey: string,
|
|
373
|
+
options?: { moduleName?: string; component?: string; details?: string },
|
|
374
|
+
): Promise<ViewMetadata | null> {
|
|
375
|
+
if (!viewUserKey) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
if (this.viewLookupCache.has(viewUserKey)) {
|
|
379
|
+
return this.viewLookupCache.get(viewUserKey) ?? null;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const view = await this.timeOperation('view-find-by-user-key-cache-miss', () => this.solidViewService.findOneByUserKey(viewUserKey), {
|
|
383
|
+
moduleName: options?.moduleName,
|
|
384
|
+
component: options?.component,
|
|
385
|
+
serviceCall: 'solidViewService.findOneByUserKey',
|
|
386
|
+
details: options?.details ?? `view=${viewUserKey}`,
|
|
387
|
+
});
|
|
388
|
+
this.viewLookupCache.set(viewUserKey, view ?? null);
|
|
389
|
+
return view ?? null;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private async getMediaStorageProviderByUserKeyCached(
|
|
393
|
+
mediaStorageProviderUserKey: string,
|
|
394
|
+
options?: { moduleName?: string; component?: string; details?: string },
|
|
395
|
+
): Promise<any | null> {
|
|
396
|
+
if (!mediaStorageProviderUserKey) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
if (this.mediaStorageProviderLookupCache.has(mediaStorageProviderUserKey)) {
|
|
400
|
+
return this.mediaStorageProviderLookupCache.get(mediaStorageProviderUserKey) ?? null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const mediaStorageProvider = await this.timeOperation('media-storage-provider-find-by-user-key-cache-miss', () => this.mediaStorageProviderMetadataService.findOneByUserKey(mediaStorageProviderUserKey), {
|
|
404
|
+
moduleName: options?.moduleName,
|
|
405
|
+
component: options?.component,
|
|
406
|
+
serviceCall: 'mediaStorageProviderMetadataService.findOneByUserKey',
|
|
407
|
+
details: options?.details ?? `provider=${mediaStorageProviderUserKey}`,
|
|
408
|
+
});
|
|
409
|
+
this.mediaStorageProviderLookupCache.set(mediaStorageProviderUserKey, mediaStorageProvider ?? null);
|
|
410
|
+
return mediaStorageProvider ?? null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private async timeOperation<T>(
|
|
414
|
+
itemTag: string,
|
|
415
|
+
operation: () => Promise<T>,
|
|
416
|
+
options?: { moduleName?: string; component?: string; serviceCall?: string; details?: string },
|
|
417
|
+
): Promise<T> {
|
|
418
|
+
const prefix = this.buildTimingPrefix(itemTag, options);
|
|
419
|
+
const detailSuffix = options?.details ? ` ${options.details}` : '';
|
|
420
|
+
const start = process.hrtime.bigint();
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
const result = await operation();
|
|
424
|
+
const durationMs = Number(process.hrtime.bigint() - start) / 1_000_000;
|
|
425
|
+
this.logger.debug(`${prefix} done in ${this.formatDuration(durationMs)}${detailSuffix}`);
|
|
426
|
+
return result;
|
|
427
|
+
} catch (error) {
|
|
428
|
+
const durationMs = Number(process.hrtime.bigint() - start) / 1_000_000;
|
|
429
|
+
this.logger.debug(`${prefix} failed after ${this.formatDuration(durationMs)}${detailSuffix}`);
|
|
430
|
+
throw error;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
253
434
|
private async seedScheduledJobs(moduleMetadata: CreateModuleMetadataDto, overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
|
|
254
|
-
this.logger.debug(`[Start] Processing scheduled jobs for ${moduleMetadata.name}`);
|
|
255
435
|
const scheduledJobs = this.getSeedArray<CreateScheduledJobDto>(overallMetadata?.scheduledJobs);
|
|
256
|
-
const pruned = this.enablePruning ? await this.pruneScheduledJobs(scheduledJobs, moduleMetadata.name)
|
|
436
|
+
const pruned = this.enablePruning ? await this.timeOperation('prune-scheduled-jobs', () => this.pruneScheduledJobs(scheduledJobs, moduleMetadata.name), {
|
|
437
|
+
moduleName: moduleMetadata.name,
|
|
438
|
+
component: 'scheduled-jobs',
|
|
439
|
+
serviceCall: 'pruneScheduledJobs',
|
|
440
|
+
}) : 0;
|
|
257
441
|
if (scheduledJobs.length > 0) {
|
|
258
|
-
await this.handleSeedScheduledJobs(scheduledJobs)
|
|
442
|
+
await this.timeOperation('handle-scheduled-jobs', () => this.handleSeedScheduledJobs(scheduledJobs), {
|
|
443
|
+
moduleName: moduleMetadata.name,
|
|
444
|
+
component: 'scheduled-jobs',
|
|
445
|
+
serviceCall: 'handleSeedScheduledJobs',
|
|
446
|
+
});
|
|
259
447
|
}
|
|
260
|
-
this.logger.debug(`[End] Processing scheduled jobs for ${moduleMetadata.name}`);
|
|
261
448
|
return { pruned, upserted: scheduledJobs.length };
|
|
262
449
|
}
|
|
263
450
|
|
|
264
451
|
private async seedSavedFilters(moduleMetadata: CreateModuleMetadataDto, overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
|
|
265
|
-
this.logger.debug(`[Start] Processing saved filters for ${moduleMetadata.name}`);
|
|
266
452
|
const savedFilters = this.getSeedArray<CreateSavedFiltersDto>(overallMetadata?.savedFilters);
|
|
267
|
-
const pruned = this.enablePruning ? await this.pruneSavedFilters(savedFilters, moduleMetadata.name)
|
|
453
|
+
const pruned = this.enablePruning ? await this.timeOperation('prune-saved-filters', () => this.pruneSavedFilters(savedFilters, moduleMetadata.name), {
|
|
454
|
+
moduleName: moduleMetadata.name,
|
|
455
|
+
component: 'saved-filters',
|
|
456
|
+
serviceCall: 'pruneSavedFilters',
|
|
457
|
+
}) : 0;
|
|
268
458
|
if (savedFilters.length > 0) {
|
|
269
|
-
await this.handleSeedSavedFilters(savedFilters)
|
|
459
|
+
await this.timeOperation('handle-saved-filters', () => this.handleSeedSavedFilters(savedFilters), {
|
|
460
|
+
moduleName: moduleMetadata.name,
|
|
461
|
+
component: 'saved-filters',
|
|
462
|
+
serviceCall: 'handleSeedSavedFilters',
|
|
463
|
+
});
|
|
270
464
|
}
|
|
271
|
-
this.logger.debug(`[End] Processing saved filters for ${moduleMetadata.name}`);
|
|
272
465
|
return { pruned, upserted: savedFilters.length };
|
|
273
466
|
}
|
|
274
467
|
|
|
275
468
|
private async seedListOfValues(moduleMetadata: CreateModuleMetadataDto, overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
|
|
276
|
-
this.logger.debug(`[Start] Processing List Of Values for ${moduleMetadata.name}`);
|
|
277
469
|
const listOfValues = this.getSeedArray<CreateListOfValuesDto>(overallMetadata?.listOfValues);
|
|
278
|
-
const pruned = this.enablePruning ? await this.pruneListOfValues(listOfValues, moduleMetadata.name)
|
|
279
|
-
|
|
280
|
-
|
|
470
|
+
const pruned = this.enablePruning ? await this.timeOperation('prune-list-of-values', () => this.pruneListOfValues(listOfValues, moduleMetadata.name), {
|
|
471
|
+
moduleName: moduleMetadata.name,
|
|
472
|
+
component: 'list-of-values',
|
|
473
|
+
serviceCall: 'pruneListOfValues',
|
|
474
|
+
}) : 0;
|
|
475
|
+
await this.timeOperation('handle-list-of-values', () => this.handleSeedListOfValues(listOfValues), {
|
|
476
|
+
moduleName: moduleMetadata.name,
|
|
477
|
+
component: 'list-of-values',
|
|
478
|
+
serviceCall: 'handleSeedListOfValues',
|
|
479
|
+
});
|
|
281
480
|
return { pruned, upserted: listOfValues.length };
|
|
282
481
|
}
|
|
283
482
|
|
|
483
|
+
private async seedLocales(overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
|
|
484
|
+
this.logger.debug(`[Start] Processing locales`);
|
|
485
|
+
const locales = this.getSeedArray<CreateLocaleDto>(overallMetadata?.locales);
|
|
486
|
+
const existingLocales = await this.localeRepo.find();
|
|
487
|
+
|
|
488
|
+
if (existingLocales.length > 0) {
|
|
489
|
+
this.solidRegistry.registerlocales(existingLocales);
|
|
490
|
+
this.logger.debug(`[End] Skipping locale seed because locales already exist`);
|
|
491
|
+
return { pruned: 0, upserted: 0 };
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
await this.handleSeedLocales(locales);
|
|
495
|
+
this.logger.debug(`[End] Processing locales`);
|
|
496
|
+
return { pruned: 0, upserted: locales.length };
|
|
497
|
+
}
|
|
498
|
+
|
|
284
499
|
private async setupDefaultRolesWithPermissions() {
|
|
285
500
|
this.logger.debug(`About to add all permissions to the Admin role`);
|
|
286
|
-
await this.roleService.addAllPermissionsToRole(ADMIN_ROLE_NAME)
|
|
501
|
+
await this.timeOperation('role-add-all-permissions', () => this.roleService.addAllPermissionsToRole(ADMIN_ROLE_NAME), {
|
|
502
|
+
moduleName: 'global',
|
|
503
|
+
component: 'default-roles',
|
|
504
|
+
serviceCall: 'roleService.addAllPermissionsToRole',
|
|
505
|
+
details: `role=${ADMIN_ROLE_NAME}`,
|
|
506
|
+
});
|
|
287
507
|
|
|
288
508
|
// The below code is commented out for now as we are including permissions for these roles from the seeder json for the Internal and Public role.
|
|
289
509
|
// 2. Give permissions to the Internal / Public role.
|
|
@@ -295,109 +515,155 @@ export class ModuleMetadataSeederService {
|
|
|
295
515
|
}
|
|
296
516
|
|
|
297
517
|
private async seedSecurityRules(overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
|
|
298
|
-
this.logger.debug(`[Start] Processing security rules`);
|
|
299
518
|
const securityRules = this.getSeedArray<CreateSecurityRuleDto>(overallMetadata?.securityRules);
|
|
300
|
-
const pruned = this.enablePruning ? await this.pruneSecurityRules(securityRules, overallMetadata?.moduleMetadata?.name)
|
|
301
|
-
|
|
302
|
-
|
|
519
|
+
const pruned = this.enablePruning ? await this.timeOperation('prune-security-rules', () => this.pruneSecurityRules(securityRules, overallMetadata?.moduleMetadata?.name), {
|
|
520
|
+
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
521
|
+
component: 'security-rules',
|
|
522
|
+
serviceCall: 'pruneSecurityRules',
|
|
523
|
+
}) : 0;
|
|
524
|
+
await this.timeOperation('handle-security-rules', () => this.handleSeedSecurityRules(securityRules), {
|
|
525
|
+
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
526
|
+
component: 'security-rules',
|
|
527
|
+
serviceCall: 'handleSeedSecurityRules',
|
|
528
|
+
});
|
|
303
529
|
return { pruned, upserted: securityRules.length };
|
|
304
530
|
}
|
|
305
531
|
|
|
306
532
|
// Ok
|
|
307
533
|
private async seedDefaultSettings() {
|
|
308
|
-
this.
|
|
309
|
-
|
|
310
|
-
|
|
534
|
+
await this.timeOperation('seed-default-settings-call', () => this.settingService.seedSystemAdminEditableAndAboveSettings(), {
|
|
535
|
+
moduleName: 'global',
|
|
536
|
+
component: 'settings',
|
|
537
|
+
serviceCall: 'settingService.seedSystemAdminEditableAndAboveSettings',
|
|
538
|
+
});
|
|
311
539
|
}
|
|
312
540
|
|
|
313
541
|
private async seedSmsTemplates(overallMetadata: any, moduleName: string): Promise<{ pruned: number; upserted: number }> {
|
|
314
|
-
this.logger.debug(`[Start] Processing sms templates`);
|
|
315
542
|
const smsTemplates = this.getSeedArray<CreateSmsTemplateDto>(overallMetadata?.smsTemplates);
|
|
316
|
-
await this.handleSeedSmsTemplates(smsTemplates, moduleName)
|
|
317
|
-
|
|
543
|
+
await this.timeOperation('handle-sms-templates', () => this.handleSeedSmsTemplates(smsTemplates, moduleName), {
|
|
544
|
+
moduleName,
|
|
545
|
+
component: 'sms-templates',
|
|
546
|
+
serviceCall: 'handleSeedSmsTemplates',
|
|
547
|
+
});
|
|
318
548
|
return { pruned: 0, upserted: smsTemplates.length };
|
|
319
549
|
}
|
|
320
550
|
|
|
321
551
|
// OK
|
|
322
552
|
private async seedEmailTemplates(overallMetadata: any, moduleName: string): Promise<{ pruned: number; upserted: number }> {
|
|
323
|
-
this.logger.debug(`[Start] Processing email templates`);
|
|
324
553
|
const emailTemplates = this.getSeedArray<CreateEmailTemplateDto>(overallMetadata?.emailTemplates);
|
|
325
|
-
await this.handleSeedEmailTemplates(emailTemplates, moduleName)
|
|
326
|
-
|
|
554
|
+
await this.timeOperation('handle-email-templates', () => this.handleSeedEmailTemplates(emailTemplates, moduleName), {
|
|
555
|
+
moduleName,
|
|
556
|
+
component: 'email-templates',
|
|
557
|
+
serviceCall: 'handleSeedEmailTemplates',
|
|
558
|
+
});
|
|
327
559
|
return { pruned: 0, upserted: emailTemplates.length };
|
|
328
560
|
}
|
|
329
561
|
|
|
330
562
|
// Ok
|
|
331
563
|
private async seedMenus(overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
|
|
332
|
-
this.logger.debug(`[Start] Processing menus`);
|
|
333
564
|
const menus = this.getSeedArray<any>(overallMetadata?.menus);
|
|
334
|
-
const pruned = this.enablePruning ? await this.pruneMenus(menus, overallMetadata?.moduleMetadata?.name)
|
|
335
|
-
|
|
336
|
-
|
|
565
|
+
const pruned = this.enablePruning ? await this.timeOperation('prune-menus', () => this.pruneMenus(menus, overallMetadata?.moduleMetadata?.name), {
|
|
566
|
+
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
567
|
+
component: 'menus',
|
|
568
|
+
serviceCall: 'pruneMenus',
|
|
569
|
+
}) : 0;
|
|
570
|
+
await this.timeOperation('handle-menus', () => this.handleSeedMenus(menus), {
|
|
571
|
+
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
572
|
+
component: 'menus',
|
|
573
|
+
serviceCall: 'handleSeedMenus',
|
|
574
|
+
});
|
|
337
575
|
return { pruned, upserted: menus.length };
|
|
338
576
|
}
|
|
339
577
|
|
|
340
578
|
// Ok
|
|
341
579
|
private async seedActions(overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
|
|
342
|
-
this.logger.debug(`[Start] Processing actions`);
|
|
343
580
|
const actions = this.getSeedArray<any>(overallMetadata?.actions);
|
|
344
|
-
const pruned = this.enablePruning ? await this.pruneActions(actions, overallMetadata?.moduleMetadata?.name)
|
|
345
|
-
|
|
346
|
-
|
|
581
|
+
const pruned = this.enablePruning ? await this.timeOperation('prune-actions', () => this.pruneActions(actions, overallMetadata?.moduleMetadata?.name), {
|
|
582
|
+
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
583
|
+
component: 'actions',
|
|
584
|
+
serviceCall: 'pruneActions',
|
|
585
|
+
}) : 0;
|
|
586
|
+
await this.timeOperation('handle-actions', () => this.handleSeedActions(actions), {
|
|
587
|
+
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
588
|
+
component: 'actions',
|
|
589
|
+
serviceCall: 'handleSeedActions',
|
|
590
|
+
});
|
|
347
591
|
return { pruned, upserted: actions.length };
|
|
348
592
|
}
|
|
349
593
|
|
|
350
594
|
// Ok
|
|
351
595
|
private async seedViews(overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
|
|
352
|
-
this.logger.debug(`[Start] Processing views`);
|
|
353
596
|
const views = this.getSeedArray<any>(overallMetadata?.views);
|
|
354
|
-
const pruned = this.enablePruning ? await this.pruneViews(views, overallMetadata?.moduleMetadata?.name)
|
|
355
|
-
|
|
356
|
-
|
|
597
|
+
const pruned = this.enablePruning ? await this.timeOperation('prune-views', () => this.pruneViews(views, overallMetadata?.moduleMetadata?.name), {
|
|
598
|
+
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
599
|
+
component: 'views',
|
|
600
|
+
serviceCall: 'pruneViews',
|
|
601
|
+
}) : 0;
|
|
602
|
+
await this.timeOperation('handle-views', () => this.handleSeedViews(views), {
|
|
603
|
+
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
604
|
+
component: 'views',
|
|
605
|
+
serviceCall: 'handleSeedViews',
|
|
606
|
+
});
|
|
357
607
|
return { pruned, upserted: views.length };
|
|
358
608
|
}
|
|
359
609
|
|
|
360
610
|
// Ok
|
|
361
611
|
private async seedUsers(overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
|
|
362
|
-
this.logger.debug(`[Start] Processing users`);
|
|
363
612
|
const users = this.getSeedArray<SignUpDto>(overallMetadata?.users);
|
|
364
613
|
// usersDetail = users;
|
|
365
|
-
await this.handleSeedUsers(users)
|
|
366
|
-
|
|
614
|
+
await this.timeOperation('handle-users', () => this.handleSeedUsers(users), {
|
|
615
|
+
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
616
|
+
component: 'users',
|
|
617
|
+
serviceCall: 'handleSeedUsers',
|
|
618
|
+
});
|
|
367
619
|
return { pruned: 0, upserted: users.length };
|
|
368
620
|
}
|
|
369
621
|
|
|
370
622
|
// OK
|
|
371
623
|
private async seedRoles(overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
|
|
372
|
-
this.logger.debug(`[Start] Processing roles`);
|
|
373
624
|
const roles = this.getSeedArray<CreateRoleMetadataDto>(overallMetadata?.roles);
|
|
374
|
-
//
|
|
375
|
-
await this.roleService.createRolesIfNotExists(
|
|
625
|
+
// Preserve module linkage metadata so RoleMetadata.module is populated from the defining module.
|
|
626
|
+
await this.timeOperation('roles-create-if-not-exists', () => this.roleService.createRolesIfNotExists(
|
|
376
627
|
roles
|
|
377
628
|
.filter((role) => role?.name)
|
|
378
|
-
.map((role) => ({
|
|
379
|
-
|
|
629
|
+
.map((role) => ({
|
|
630
|
+
name: role.name,
|
|
631
|
+
moduleId: role.moduleId,
|
|
632
|
+
moduleUserKey: role.moduleUserKey ?? overallMetadata?.moduleMetadata?.name,
|
|
633
|
+
} as CreateRoleMetadataDto)),
|
|
634
|
+
), {
|
|
635
|
+
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
636
|
+
component: 'roles',
|
|
637
|
+
serviceCall: 'roleService.createRolesIfNotExists',
|
|
638
|
+
details: `roleCount=${roles.length}`,
|
|
639
|
+
});
|
|
380
640
|
// After roles are created, we iterate over all roles and attach permissions (if specified in the seeder json) to the respective role.
|
|
381
641
|
// Every role configuration in the seeder json can optionally have a permissions attribute.
|
|
382
642
|
for (const role of roles) {
|
|
383
643
|
if (role.permissions) {
|
|
384
|
-
await this.roleService.addPermissionsToRole(
|
|
644
|
+
await this.timeOperation('role-add-permissions', () => this.roleService.addPermissionsToRole(
|
|
385
645
|
role.name,
|
|
386
646
|
role.permissions
|
|
387
647
|
.map((permission: any) => typeof permission === 'string' ? permission : permission?.name)
|
|
388
648
|
.filter(Boolean),
|
|
389
|
-
)
|
|
649
|
+
), {
|
|
650
|
+
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
651
|
+
component: 'roles',
|
|
652
|
+
serviceCall: 'roleService.addPermissionsToRole',
|
|
653
|
+
details: `role=${role.name} permissionCount=${role.permissions.length}`,
|
|
654
|
+
});
|
|
390
655
|
}
|
|
391
656
|
}
|
|
392
|
-
this.logger.debug(`[End] Processing roles`);
|
|
393
657
|
return { pruned: 0, upserted: roles.length };
|
|
394
658
|
}
|
|
395
659
|
|
|
396
660
|
private async seedPermissions(overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
|
|
397
|
-
this.logger.debug(`[Start] Processing permissions`);
|
|
398
661
|
const permissions = overallMetadata.permissions ?? [];
|
|
399
|
-
await this.handleSeedPermissions(permissions)
|
|
400
|
-
|
|
662
|
+
await this.timeOperation('handle-permissions', () => this.handleSeedPermissions(permissions), {
|
|
663
|
+
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
664
|
+
component: 'permissions',
|
|
665
|
+
serviceCall: 'handleSeedPermissions',
|
|
666
|
+
});
|
|
401
667
|
return { pruned: 0, upserted: permissions?.length ?? 0 };
|
|
402
668
|
}
|
|
403
669
|
|
|
@@ -423,24 +689,37 @@ export class ModuleMetadataSeederService {
|
|
|
423
689
|
// OK
|
|
424
690
|
private async seedGlobalMetadata() {
|
|
425
691
|
this.logger.log(`Seeding Permissions`);
|
|
426
|
-
await this.seedControllerPermissions()
|
|
692
|
+
await this.timeOperation('seed-controller-permissions', () => this.seedControllerPermissions(), {
|
|
693
|
+
moduleName: 'global',
|
|
694
|
+
component: 'controller-permissions',
|
|
695
|
+
});
|
|
427
696
|
|
|
428
697
|
// this.logger.log(`Seeding Default Media Storage Providers`);
|
|
429
698
|
// await this.seedDefaultMediaStorageProviders();
|
|
430
699
|
|
|
431
700
|
this.logger.log(`Seeding System Fields Metadata`);
|
|
432
|
-
await this.seedDefaultSystemFields()
|
|
701
|
+
await this.timeOperation('seed-default-system-fields', () => this.seedDefaultSystemFields(), {
|
|
702
|
+
moduleName: 'global',
|
|
703
|
+
component: 'system-fields',
|
|
704
|
+
});
|
|
433
705
|
|
|
434
706
|
// Settings
|
|
435
707
|
this.logger.log(`Seeding Default Settings`);
|
|
436
|
-
await this.seedDefaultSettings()
|
|
708
|
+
await this.timeOperation('seed-default-settings', () => this.seedDefaultSettings(), {
|
|
709
|
+
moduleName: 'global',
|
|
710
|
+
component: 'settings',
|
|
711
|
+
});
|
|
437
712
|
|
|
438
713
|
|
|
439
714
|
this.logger.debug(`Global metadata seeding completed`);
|
|
440
715
|
}
|
|
441
716
|
|
|
442
717
|
private async seedDefaultSystemFields() {
|
|
443
|
-
await this.systemFieldsSeederService.seed()
|
|
718
|
+
await this.timeOperation('system-fields-seed', () => this.systemFieldsSeederService.seed(), {
|
|
719
|
+
moduleName: 'global',
|
|
720
|
+
component: 'system-fields',
|
|
721
|
+
serviceCall: 'systemFieldsSeederService.seed',
|
|
722
|
+
});
|
|
444
723
|
}
|
|
445
724
|
|
|
446
725
|
// OK
|
|
@@ -461,8 +740,6 @@ export class ModuleMetadataSeederService {
|
|
|
461
740
|
const methodName = methods[mId];
|
|
462
741
|
const permissionName = `${controller.name}.${methodName}`;
|
|
463
742
|
permissionNames.push(permissionName);
|
|
464
|
-
|
|
465
|
-
await this.createPermissionIfNotExists(permissionName);
|
|
466
743
|
}
|
|
467
744
|
|
|
468
745
|
} catch (error: any) {
|
|
@@ -470,35 +747,112 @@ export class ModuleMetadataSeederService {
|
|
|
470
747
|
}
|
|
471
748
|
}
|
|
472
749
|
|
|
750
|
+
await this.timeOperation('seed-controller-permissions-bulk', () => this.seedPermissionsInBatches(permissionNames, {
|
|
751
|
+
moduleName: 'global',
|
|
752
|
+
component: 'controller-permissions',
|
|
753
|
+
}), {
|
|
754
|
+
moduleName: 'global',
|
|
755
|
+
component: 'controller-permissions',
|
|
756
|
+
serviceCall: 'seedPermissionsInBatches',
|
|
757
|
+
details: `permissionCount=${permissionNames.length} batchSize=${this.permissionSeedBatchSize}`,
|
|
758
|
+
});
|
|
759
|
+
|
|
473
760
|
if (this.enablePruning) {
|
|
474
|
-
await this.prunePermissions(permissionNames)
|
|
761
|
+
await this.timeOperation('prune-controller-permissions', () => this.prunePermissions(permissionNames), {
|
|
762
|
+
moduleName: 'global',
|
|
763
|
+
component: 'controller-permissions',
|
|
764
|
+
serviceCall: 'prunePermissions',
|
|
765
|
+
});
|
|
475
766
|
}
|
|
476
767
|
}
|
|
477
768
|
|
|
478
|
-
private async
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
769
|
+
private async handleSeedPermissions(permissions: any[]): Promise<void> {
|
|
770
|
+
const permissionNames = permissions
|
|
771
|
+
.map((permission) => typeof permission === 'string' ? permission : permission?.name)
|
|
772
|
+
.filter(Boolean);
|
|
773
|
+
|
|
774
|
+
// This path replaced the old "check one permission at a time" approach that was responsible for one of the
|
|
775
|
+
// biggest seed-time regressions. We now normalize upfront and let the batch helper do set-based existence work.
|
|
776
|
+
await this.seedPermissionsInBatches(permissionNames, {
|
|
777
|
+
component: 'permissions',
|
|
483
778
|
});
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
private async seedPermissionsInBatches(
|
|
782
|
+
permissionNames: string[],
|
|
783
|
+
options?: { moduleName?: string; component?: string },
|
|
784
|
+
): Promise<void> {
|
|
785
|
+
const uniquePermissionNames = this.getUniquePermissionNames(permissionNames);
|
|
786
|
+
if (uniquePermissionNames.length === 0) {
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
484
789
|
|
|
485
|
-
|
|
486
|
-
|
|
790
|
+
// We intentionally process in bounded chunks so we get the performance benefit of set-based reads/writes
|
|
791
|
+
// without loading an unbounded permission list into a single query or save payload.
|
|
792
|
+
for (let index = 0; index < uniquePermissionNames.length; index += this.permissionSeedBatchSize) {
|
|
793
|
+
const batch = uniquePermissionNames.slice(index, index + this.permissionSeedBatchSize);
|
|
794
|
+
const batchNumber = Math.floor(index / this.permissionSeedBatchSize) + 1;
|
|
487
795
|
|
|
488
|
-
const
|
|
489
|
-
|
|
796
|
+
const existingPermissions = await this.timeOperation('permission-batch-find', () => this.permissionRepo.find({
|
|
797
|
+
where: {
|
|
798
|
+
name: In(batch),
|
|
799
|
+
}
|
|
800
|
+
}), {
|
|
801
|
+
moduleName: options?.moduleName,
|
|
802
|
+
component: options?.component ?? 'permissions',
|
|
803
|
+
serviceCall: 'permissionRepo.find',
|
|
804
|
+
details: `batch=${batchNumber} batchSize=${batch.length}`,
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
const existingPermissionNames = new Set(
|
|
808
|
+
existingPermissions.map((permission) => this.normalizePermissionName(permission.name)),
|
|
809
|
+
);
|
|
810
|
+
const missingPermissionNames = batch.filter(
|
|
811
|
+
(name) => !existingPermissionNames.has(this.normalizePermissionName(name)),
|
|
812
|
+
);
|
|
813
|
+
|
|
814
|
+
if (missingPermissionNames.length === 0) {
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
this.logger.log(`Creating ${missingPermissionNames.length} missing permissions in batch ${batchNumber}.`);
|
|
819
|
+
await this.timeOperation('permission-batch-save', () => this.permissionRepo.save(
|
|
820
|
+
missingPermissionNames.map((name) => this.permissionRepo.create({ name })),
|
|
821
|
+
), {
|
|
822
|
+
moduleName: options?.moduleName,
|
|
823
|
+
component: options?.component ?? 'permissions',
|
|
824
|
+
serviceCall: 'permissionRepo.save',
|
|
825
|
+
details: `batch=${batchNumber} createCount=${missingPermissionNames.length}`,
|
|
490
826
|
});
|
|
491
|
-
await this.permissionRepo.save(newPermission);
|
|
492
827
|
}
|
|
493
828
|
}
|
|
494
829
|
|
|
495
|
-
private
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
830
|
+
private getUniquePermissionNames(permissionNames: string[]): string[] {
|
|
831
|
+
const normalizedPermissionNames = new Set<string>();
|
|
832
|
+
const uniquePermissionNames: string[] = [];
|
|
833
|
+
|
|
834
|
+
for (const permissionName of permissionNames) {
|
|
835
|
+
if (!permissionName) {
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Deduping is intentionally case-insensitive because MSSQL commonly compares these names using a
|
|
840
|
+
// case-insensitive collation. Without that, a batch can think `Foo.Bar` is missing even when
|
|
841
|
+
// `foo.bar` already exists in the database.
|
|
842
|
+
const normalizedPermissionName = this.normalizePermissionName(permissionName);
|
|
843
|
+
if (normalizedPermissionNames.has(normalizedPermissionName)) {
|
|
844
|
+
continue;
|
|
500
845
|
}
|
|
846
|
+
|
|
847
|
+
normalizedPermissionNames.add(normalizedPermissionName);
|
|
848
|
+
uniquePermissionNames.push(permissionName.trim());
|
|
501
849
|
}
|
|
850
|
+
|
|
851
|
+
return uniquePermissionNames;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
private normalizePermissionName(permissionName: string): string {
|
|
855
|
+
return permissionName.trim().toLowerCase();
|
|
502
856
|
}
|
|
503
857
|
|
|
504
858
|
// OK
|
|
@@ -508,23 +862,31 @@ export class ModuleMetadataSeederService {
|
|
|
508
862
|
|
|
509
863
|
// OK
|
|
510
864
|
private async seedMediaStorageProviders(mediaStorageProviders: any[]): Promise<{ pruned: number; upserted: number }> {
|
|
511
|
-
this.logger.debug(`[Start] Processing Media Storage Provider`);
|
|
512
865
|
const providers = this.getSeedArray<any>(mediaStorageProviders);
|
|
513
866
|
|
|
514
867
|
for (let i = 0; i < providers.length; i++) {
|
|
515
868
|
const mediaStorageProvider = providers[i];
|
|
516
|
-
await this.mediaStorageProviderMetadataService.upsert(mediaStorageProvider)
|
|
869
|
+
await this.timeOperation('media-storage-provider-upsert', () => this.mediaStorageProviderMetadataService.upsert(mediaStorageProvider), {
|
|
870
|
+
component: 'media-storage-providers',
|
|
871
|
+
serviceCall: 'mediaStorageProviderMetadataService.upsert',
|
|
872
|
+
details: `provider=${mediaStorageProvider?.name ?? mediaStorageProvider?.userKey ?? i}`,
|
|
873
|
+
});
|
|
517
874
|
}
|
|
518
|
-
this.logger.debug(`[End] Processing Media Storage Provider`);
|
|
519
875
|
return { pruned: 0, upserted: providers.length };
|
|
520
876
|
}
|
|
521
877
|
|
|
522
878
|
private async seedModelSequences(overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
|
|
523
|
-
this.logger.debug(`[Start] Processing model sequences`);
|
|
524
879
|
const modelSequences = this.getSeedArray<CreateModelSequenceDto>(overallMetadata?.modelSequences);
|
|
525
|
-
const pruned = this.enablePruning ? await this.pruneModelSequences(modelSequences, overallMetadata?.moduleMetadata?.name)
|
|
526
|
-
|
|
527
|
-
|
|
880
|
+
const pruned = this.enablePruning ? await this.timeOperation('prune-model-sequences', () => this.pruneModelSequences(modelSequences, overallMetadata?.moduleMetadata?.name), {
|
|
881
|
+
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
882
|
+
component: 'model-sequences',
|
|
883
|
+
serviceCall: 'pruneModelSequences',
|
|
884
|
+
}) : 0;
|
|
885
|
+
await this.timeOperation('handle-model-sequences', () => this.handleSeedModelSequences(modelSequences), {
|
|
886
|
+
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
887
|
+
component: 'model-sequences',
|
|
888
|
+
serviceCall: 'handleSeedModelSequences',
|
|
889
|
+
});
|
|
528
890
|
return { pruned, upserted: modelSequences.length };
|
|
529
891
|
}
|
|
530
892
|
|
|
@@ -588,8 +950,18 @@ export class ModuleMetadataSeederService {
|
|
|
588
950
|
}
|
|
589
951
|
|
|
590
952
|
// Save to DB.
|
|
591
|
-
await this.emailTemplateService.removeByName(emailTemplate.name)
|
|
592
|
-
|
|
953
|
+
await this.timeOperation('email-template-remove', () => this.emailTemplateService.removeByName(emailTemplate.name), {
|
|
954
|
+
moduleName,
|
|
955
|
+
component: 'email-templates',
|
|
956
|
+
serviceCall: 'emailTemplateService.removeByName',
|
|
957
|
+
details: `template=${emailTemplate.name}`,
|
|
958
|
+
});
|
|
959
|
+
await this.timeOperation('email-template-create', () => this.emailTemplateService.create(emailTemplate), {
|
|
960
|
+
moduleName,
|
|
961
|
+
component: 'email-templates',
|
|
962
|
+
serviceCall: 'emailTemplateService.create',
|
|
963
|
+
details: `template=${emailTemplate.name}`,
|
|
964
|
+
});
|
|
593
965
|
}
|
|
594
966
|
|
|
595
967
|
}
|
|
@@ -655,8 +1027,18 @@ export class ModuleMetadataSeederService {
|
|
|
655
1027
|
|
|
656
1028
|
|
|
657
1029
|
// Save to DB.
|
|
658
|
-
await this.smsTemplateService.removeByName(smsTemplate.name)
|
|
659
|
-
|
|
1030
|
+
await this.timeOperation('sms-template-remove', () => this.smsTemplateService.removeByName(smsTemplate.name), {
|
|
1031
|
+
moduleName,
|
|
1032
|
+
component: 'sms-templates',
|
|
1033
|
+
serviceCall: 'smsTemplateService.removeByName',
|
|
1034
|
+
details: `template=${smsTemplate.name}`,
|
|
1035
|
+
});
|
|
1036
|
+
await this.timeOperation('sms-template-create', () => this.smsTemplateService.create(smsTemplate), {
|
|
1037
|
+
moduleName,
|
|
1038
|
+
component: 'sms-templates',
|
|
1039
|
+
serviceCall: 'smsTemplateService.create',
|
|
1040
|
+
details: `template=${smsTemplate.name}`,
|
|
1041
|
+
});
|
|
660
1042
|
}
|
|
661
1043
|
}
|
|
662
1044
|
|
|
@@ -666,7 +1048,10 @@ export class ModuleMetadataSeederService {
|
|
|
666
1048
|
return;
|
|
667
1049
|
}
|
|
668
1050
|
|
|
669
|
-
|
|
1051
|
+
// Menu writes are now no-op aware, but this block still performs several serial relation lookups per menu.
|
|
1052
|
+
// That tradeoff is acceptable for now because the highest-value wins were elsewhere, but if menu seeding
|
|
1053
|
+
// becomes the next hotspot the natural next step is bulk-preloading actions/modules/existing menus here too.
|
|
1054
|
+
await this.timeOperation('menus-transaction', () => this.dataSource.transaction(async (trx) => {
|
|
670
1055
|
const menuRepo = trx.getRepository(MenuItemMetadata);
|
|
671
1056
|
const roleRepo = trx.getRepository(RoleMetadata);
|
|
672
1057
|
const actionRepo = trx.getRepository(ActionMetadata);
|
|
@@ -675,21 +1060,41 @@ export class ModuleMetadataSeederService {
|
|
|
675
1060
|
// 1) Upsert menus WITHOUT roles (manual upsert)
|
|
676
1061
|
for (const m of menus) {
|
|
677
1062
|
const action = m.actionUserKey
|
|
678
|
-
? await actionRepo.findOne({ where: { name: m.actionUserKey }, select: ["id"] })
|
|
1063
|
+
? await this.timeOperation('menu-action-find-one', () => actionRepo.findOne({ where: { name: m.actionUserKey }, select: ["id"] }), {
|
|
1064
|
+
component: 'menus',
|
|
1065
|
+
serviceCall: 'actionRepo.findOne',
|
|
1066
|
+
details: `action=${m.actionUserKey}`,
|
|
1067
|
+
})
|
|
679
1068
|
: null;
|
|
680
1069
|
|
|
681
1070
|
const module = m.moduleUserKey
|
|
682
|
-
? await moduleRepo.findOne({ where: { name: m.moduleUserKey }, select: ["id"] })
|
|
1071
|
+
? await this.timeOperation('menu-module-find-one', () => moduleRepo.findOne({ where: { name: m.moduleUserKey }, select: ["id"] }), {
|
|
1072
|
+
component: 'menus',
|
|
1073
|
+
serviceCall: 'moduleRepo.findOne',
|
|
1074
|
+
details: `module=${m.moduleUserKey}`,
|
|
1075
|
+
})
|
|
683
1076
|
: null;
|
|
684
1077
|
|
|
685
1078
|
const parentMenuItem = m.parentMenuItemUserKey
|
|
686
|
-
? await menuRepo.findOne({ where: { name: m.parentMenuItemUserKey }, select: ["id"] })
|
|
1079
|
+
? await this.timeOperation('menu-parent-find-one', () => menuRepo.findOne({ where: { name: m.parentMenuItemUserKey }, select: ["id"] }), {
|
|
1080
|
+
component: 'menus',
|
|
1081
|
+
serviceCall: 'menuRepo.findOne',
|
|
1082
|
+
details: `parent=${m.parentMenuItemUserKey}`,
|
|
1083
|
+
})
|
|
687
1084
|
: null;
|
|
688
1085
|
|
|
689
1086
|
// Check if a menu with this name already exists
|
|
690
|
-
const existing = await menuRepo.findOne({
|
|
1087
|
+
const existing = await this.timeOperation('menu-existing-find-one', () => menuRepo.findOne({
|
|
691
1088
|
where: { name: m.name },
|
|
692
|
-
|
|
1089
|
+
relations: {
|
|
1090
|
+
action: true,
|
|
1091
|
+
module: true,
|
|
1092
|
+
parentMenuItem: true,
|
|
1093
|
+
},
|
|
1094
|
+
}), {
|
|
1095
|
+
component: 'menus',
|
|
1096
|
+
serviceCall: 'menuRepo.findOne',
|
|
1097
|
+
details: `menu=${m.name}`,
|
|
693
1098
|
});
|
|
694
1099
|
|
|
695
1100
|
// Build the entity data (without id)
|
|
@@ -703,30 +1108,60 @@ export class ModuleMetadataSeederService {
|
|
|
703
1108
|
iconName: m.iconName,
|
|
704
1109
|
};
|
|
705
1110
|
|
|
1111
|
+
const hasChanges = existing
|
|
1112
|
+
? existing.displayName !== base.displayName
|
|
1113
|
+
|| existing.sequenceNumber !== base.sequenceNumber
|
|
1114
|
+
|| existing.iconName !== base.iconName
|
|
1115
|
+
|| existing.action?.id !== base.action?.id
|
|
1116
|
+
|| existing.module?.id !== base.module?.id
|
|
1117
|
+
|| existing.parentMenuItem?.id !== base.parentMenuItem?.id
|
|
1118
|
+
: true;
|
|
1119
|
+
|
|
1120
|
+
if (existing && !hasChanges) {
|
|
1121
|
+
this.logger.debug(`Skipping menu upsert for ${m.name}; no changes detected.`);
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
706
1125
|
// If existing, set its id so save() will perform an update, otherwise insert
|
|
707
1126
|
const entity = menuRepo.create(
|
|
708
1127
|
existing ? { id: existing.id, ...base } : base,
|
|
709
1128
|
);
|
|
710
1129
|
|
|
711
|
-
await menuRepo.save(entity)
|
|
1130
|
+
await this.timeOperation('menu-save', () => menuRepo.save(entity), {
|
|
1131
|
+
component: 'menus',
|
|
1132
|
+
serviceCall: 'menuRepo.save',
|
|
1133
|
+
details: `menu=${m.name}`,
|
|
1134
|
+
});
|
|
712
1135
|
}
|
|
713
1136
|
|
|
714
1137
|
// 2) Fetch ids for batching
|
|
715
|
-
const seeded = await menuRepo.find({
|
|
1138
|
+
const seeded = await this.timeOperation('menu-seeded-find', () => menuRepo.find({
|
|
716
1139
|
where: { name: In(menus.map((m: any) => m.name)) },
|
|
717
1140
|
select: ["id", "name"],
|
|
1141
|
+
}), {
|
|
1142
|
+
component: 'menus',
|
|
1143
|
+
serviceCall: 'menuRepo.find',
|
|
1144
|
+
details: `count=${menus.length}`,
|
|
718
1145
|
});
|
|
719
1146
|
const idByName = new Map(seeded.map(s => [s.name, s.id]));
|
|
720
1147
|
|
|
721
1148
|
// 3) Build desired join rows once
|
|
722
|
-
const admin = await roleRepo.findOne({ where: { name: ADMIN_ROLE_NAME }, select: ["id", "name"] })
|
|
1149
|
+
const admin = await this.timeOperation('menu-admin-role-find-one', () => roleRepo.findOne({ where: { name: ADMIN_ROLE_NAME }, select: ["id", "name"] }), {
|
|
1150
|
+
component: 'menus',
|
|
1151
|
+
serviceCall: 'roleRepo.findOne',
|
|
1152
|
+
details: `role=${ADMIN_ROLE_NAME}`,
|
|
1153
|
+
});
|
|
723
1154
|
const allRoleNames = new Set<string>();
|
|
724
1155
|
for (const m of menus) (m.roles ?? []).forEach((r: string) => allRoleNames.add(r));
|
|
725
1156
|
if (admin) allRoleNames.add(admin.name);
|
|
726
1157
|
|
|
727
|
-
const roles = await roleRepo.find({
|
|
1158
|
+
const roles = await this.timeOperation('menu-roles-find', () => roleRepo.find({
|
|
728
1159
|
where: { name: In([...allRoleNames]) },
|
|
729
1160
|
select: ["id", "name"],
|
|
1161
|
+
}), {
|
|
1162
|
+
component: 'menus',
|
|
1163
|
+
serviceCall: 'roleRepo.find',
|
|
1164
|
+
details: `roleCount=${allRoleNames.size}`,
|
|
730
1165
|
});
|
|
731
1166
|
const roleByName = new Map(roles.map(r => [r.name, r]));
|
|
732
1167
|
|
|
@@ -744,12 +1179,16 @@ export class ModuleMetadataSeederService {
|
|
|
744
1179
|
// 4a) delete existing for affected menus
|
|
745
1180
|
const menuIds = [...new Set(joinRows.map(r => r.menuId))];
|
|
746
1181
|
if (menuIds.length) {
|
|
747
|
-
await trx
|
|
1182
|
+
await this.timeOperation('menu-role-joins-delete', () => trx
|
|
748
1183
|
.createQueryBuilder()
|
|
749
1184
|
.delete()
|
|
750
1185
|
.from(MENU_ROLE_JOIN_TABLE_NAME) // string table name is fine
|
|
751
1186
|
.where(`${MENU_ROLE_JOIN_TABLE_NAME_MENU_COL} IN (:...ids)`, { ids: menuIds })
|
|
752
|
-
.execute()
|
|
1187
|
+
.execute(), {
|
|
1188
|
+
component: 'menus',
|
|
1189
|
+
serviceCall: 'trx.delete.menuRoleJoins',
|
|
1190
|
+
details: `menuCount=${menuIds.length}`,
|
|
1191
|
+
});
|
|
753
1192
|
}
|
|
754
1193
|
|
|
755
1194
|
// 4b) bulk insert all pairs
|
|
@@ -759,15 +1198,19 @@ export class ModuleMetadataSeederService {
|
|
|
759
1198
|
[MENU_ROLE_JOIN_TABLE_NAME_ROLE_COL]: r.roleId,
|
|
760
1199
|
}));
|
|
761
1200
|
|
|
762
|
-
await trx
|
|
1201
|
+
await this.timeOperation('menu-role-joins-insert', () => trx
|
|
763
1202
|
.createQueryBuilder()
|
|
764
1203
|
.insert()
|
|
765
1204
|
.into(MENU_ROLE_JOIN_TABLE_NAME)
|
|
766
1205
|
.values(values)
|
|
767
1206
|
// .orIgnore() // ❌ remove this – it triggers unsupported onUpdate path
|
|
768
|
-
.execute()
|
|
1207
|
+
.execute(), {
|
|
1208
|
+
component: 'menus',
|
|
1209
|
+
serviceCall: 'trx.insert.menuRoleJoins',
|
|
1210
|
+
details: `rowCount=${values.length}`,
|
|
1211
|
+
});
|
|
769
1212
|
}
|
|
770
|
-
});
|
|
1213
|
+
}), { component: 'menus', serviceCall: 'dataSource.transaction' });
|
|
771
1214
|
}
|
|
772
1215
|
|
|
773
1216
|
// Ok
|
|
@@ -776,20 +1219,81 @@ export class ModuleMetadataSeederService {
|
|
|
776
1219
|
return;
|
|
777
1220
|
}
|
|
778
1221
|
|
|
1222
|
+
const actionRepo = this.dataSource.getRepository(ActionMetadata);
|
|
1223
|
+
// Prime related-entity caches once before iterating so we do not pay repeated user-key lookups per row.
|
|
1224
|
+
await this.timeOperation('action-prime-module-cache', () => this.primeModuleLookupCache(
|
|
1225
|
+
actions.map((action: any) => action?.moduleUserKey),
|
|
1226
|
+
), {
|
|
1227
|
+
component: 'actions',
|
|
1228
|
+
serviceCall: 'primeModuleLookupCache',
|
|
1229
|
+
details: `count=${actions.length}`,
|
|
1230
|
+
});
|
|
1231
|
+
await this.timeOperation('action-prime-model-cache', () => this.primeModelLookupCache(
|
|
1232
|
+
actions.map((action: any) => action?.modelUserKey),
|
|
1233
|
+
), {
|
|
1234
|
+
component: 'actions',
|
|
1235
|
+
serviceCall: 'primeModelLookupCache',
|
|
1236
|
+
details: `count=${actions.length}`,
|
|
1237
|
+
});
|
|
1238
|
+
await this.timeOperation('action-prime-view-cache', () => this.primeViewLookupCache(
|
|
1239
|
+
actions.map((action: any) => action?.viewUserKey),
|
|
1240
|
+
), {
|
|
1241
|
+
component: 'actions',
|
|
1242
|
+
serviceCall: 'primeViewLookupCache',
|
|
1243
|
+
details: `count=${actions.length}`,
|
|
1244
|
+
});
|
|
1245
|
+
const existingActionsByName = await this.timeOperation('action-preload-existing', () => this.loadExistingActionsByName(
|
|
1246
|
+
actionRepo,
|
|
1247
|
+
actions.map((action: any) => action?.name).filter(Boolean),
|
|
1248
|
+
), {
|
|
1249
|
+
component: 'actions',
|
|
1250
|
+
serviceCall: 'loadExistingActionsByName',
|
|
1251
|
+
details: `count=${actions.length}`,
|
|
1252
|
+
});
|
|
1253
|
+
// Keep the verbose output summarized rather than emitting forty separate "skip" lines.
|
|
1254
|
+
let created = 0;
|
|
1255
|
+
let updated = 0;
|
|
1256
|
+
let skipped = 0;
|
|
1257
|
+
|
|
779
1258
|
for (let j = 0; j < actions.length; j++) {
|
|
780
1259
|
const actionData = actions[j];
|
|
781
|
-
actionData['module'] = await this.
|
|
1260
|
+
actionData['module'] = await this.getModuleByUserKeyCached(actionData.moduleUserKey, {
|
|
1261
|
+
moduleName: actionData.moduleUserKey,
|
|
1262
|
+
component: 'actions',
|
|
1263
|
+
details: `module=${actionData.moduleUserKey}`,
|
|
1264
|
+
});
|
|
782
1265
|
if (actionData.type === 'solid') {
|
|
783
|
-
actionData['model'] = await this.
|
|
784
|
-
|
|
1266
|
+
actionData['model'] = await this.getModelByUserKeyCached(actionData.modelUserKey, {
|
|
1267
|
+
moduleName: actionData.moduleUserKey,
|
|
1268
|
+
component: 'actions',
|
|
1269
|
+
details: `model=${actionData.modelUserKey}`,
|
|
1270
|
+
});
|
|
1271
|
+
actionData['view'] = await this.getViewByUserKeyCached(actionData.viewUserKey, {
|
|
1272
|
+
moduleName: actionData.moduleUserKey,
|
|
1273
|
+
component: 'actions',
|
|
1274
|
+
details: `view=${actionData.viewUserKey}`,
|
|
1275
|
+
});
|
|
785
1276
|
}
|
|
786
1277
|
else {
|
|
787
1278
|
if (actionData.modelUserKey) {
|
|
788
|
-
actionData['model'] = await this.
|
|
1279
|
+
actionData['model'] = await this.getModelByUserKeyCached(actionData.modelUserKey, {
|
|
1280
|
+
moduleName: actionData.moduleUserKey,
|
|
1281
|
+
component: 'actions',
|
|
1282
|
+
details: `model=${actionData.modelUserKey}`,
|
|
1283
|
+
});
|
|
789
1284
|
}
|
|
790
1285
|
}
|
|
791
|
-
await this.
|
|
1286
|
+
const result = await this.upsertActionMetadataWithPreloadedExisting(actionRepo, existingActionsByName, actionData);
|
|
1287
|
+
if (result.outcome === 'created') {
|
|
1288
|
+
created += 1;
|
|
1289
|
+
} else if (result.outcome === 'updated') {
|
|
1290
|
+
updated += 1;
|
|
1291
|
+
} else {
|
|
1292
|
+
skipped += 1;
|
|
1293
|
+
}
|
|
792
1294
|
}
|
|
1295
|
+
|
|
1296
|
+
this.logger.debug(`Action seed summary: created=${created}, updated=${updated}, skipped=${skipped}.`);
|
|
793
1297
|
}
|
|
794
1298
|
|
|
795
1299
|
// Ok
|
|
@@ -798,18 +1302,64 @@ export class ModuleMetadataSeederService {
|
|
|
798
1302
|
return;
|
|
799
1303
|
}
|
|
800
1304
|
|
|
1305
|
+
const viewRepo = this.dataSource.getRepository(ViewMetadata);
|
|
1306
|
+
// Same preload/diff strategy as actions: resolve relation references in bulk, load existing rows once,
|
|
1307
|
+
// then do the rest of the work in memory so unchanged views never fall through to save().
|
|
1308
|
+
await this.timeOperation('view-prime-module-cache', () => this.primeModuleLookupCache(
|
|
1309
|
+
views.map((view: any) => view?.moduleUserKey),
|
|
1310
|
+
), {
|
|
1311
|
+
component: 'views',
|
|
1312
|
+
serviceCall: 'primeModuleLookupCache',
|
|
1313
|
+
details: `count=${views.length}`,
|
|
1314
|
+
});
|
|
1315
|
+
await this.timeOperation('view-prime-model-cache', () => this.primeModelLookupCache(
|
|
1316
|
+
views.map((view: any) => view?.modelUserKey),
|
|
1317
|
+
), {
|
|
1318
|
+
component: 'views',
|
|
1319
|
+
serviceCall: 'primeModelLookupCache',
|
|
1320
|
+
details: `count=${views.length}`,
|
|
1321
|
+
});
|
|
1322
|
+
const existingViewsByName = await this.timeOperation('view-preload-existing', () => this.loadExistingViewsByName(
|
|
1323
|
+
viewRepo,
|
|
1324
|
+
views.map((view: any) => view?.name).filter(Boolean),
|
|
1325
|
+
), {
|
|
1326
|
+
component: 'views',
|
|
1327
|
+
serviceCall: 'loadExistingViewsByName',
|
|
1328
|
+
details: `count=${views.length}`,
|
|
1329
|
+
});
|
|
1330
|
+
let created = 0;
|
|
1331
|
+
let updated = 0;
|
|
1332
|
+
let skipped = 0;
|
|
1333
|
+
|
|
801
1334
|
for (let j = 0; j < views.length; j++) {
|
|
802
1335
|
const viewData = views[j];
|
|
803
1336
|
|
|
804
1337
|
// preety format the layout & context.
|
|
805
1338
|
viewData['layout'] = JSON.stringify(viewData['layout'], null, 2);
|
|
806
1339
|
|
|
807
|
-
viewData['module'] = await this.
|
|
808
|
-
|
|
1340
|
+
viewData['module'] = await this.getModuleByUserKeyCached(viewData.moduleUserKey, {
|
|
1341
|
+
moduleName: viewData.moduleUserKey,
|
|
1342
|
+
component: 'views',
|
|
1343
|
+
details: `module=${viewData.moduleUserKey}`,
|
|
1344
|
+
});
|
|
1345
|
+
viewData['model'] = await this.getModelByUserKeyCached(viewData.modelUserKey, {
|
|
1346
|
+
moduleName: viewData.moduleUserKey,
|
|
1347
|
+
component: 'views',
|
|
1348
|
+
details: `model=${viewData.modelUserKey}`,
|
|
1349
|
+
});
|
|
809
1350
|
|
|
810
|
-
|
|
811
|
-
|
|
1351
|
+
const result = await this.upsertViewMetadataWithPreloadedExisting(viewRepo, existingViewsByName, viewData);
|
|
1352
|
+
this.viewLookupCache.set(viewData.name, result.entity ?? null);
|
|
1353
|
+
if (result.outcome === 'created') {
|
|
1354
|
+
created += 1;
|
|
1355
|
+
} else if (result.outcome === 'updated') {
|
|
1356
|
+
updated += 1;
|
|
1357
|
+
} else {
|
|
1358
|
+
skipped += 1;
|
|
1359
|
+
}
|
|
812
1360
|
}
|
|
1361
|
+
|
|
1362
|
+
this.logger.debug(`View seed summary: created=${created}, updated=${updated}, skipped=${skipped}.`);
|
|
813
1363
|
}
|
|
814
1364
|
|
|
815
1365
|
// OK
|
|
@@ -820,15 +1370,42 @@ export class ModuleMetadataSeederService {
|
|
|
820
1370
|
|
|
821
1371
|
for (let l = 0; l < users.length; l++) {
|
|
822
1372
|
const user: SignUpDto = users[l];
|
|
823
|
-
|
|
1373
|
+
const isSystemAdminUser = user.username === 'sa';
|
|
1374
|
+
let exisitingUser = await this.timeOperation('user-find-by-username', () => this.userService.findOneByUsername(user.username), {
|
|
1375
|
+
component: 'users',
|
|
1376
|
+
serviceCall: 'userService.findOneByUsername',
|
|
1377
|
+
details: `username=${user.username}`,
|
|
1378
|
+
});
|
|
824
1379
|
if (!exisitingUser) {
|
|
825
1380
|
if (user.username === 'sa') {
|
|
826
1381
|
user.password = DEFAULT_SA_PASSWORD;
|
|
827
1382
|
}
|
|
1383
|
+
if (isSystemAdminUser) {
|
|
1384
|
+
(user as SignUpDto & { isAllowedToGenerateApiKeys?: boolean }).isAllowedToGenerateApiKeys = true;
|
|
1385
|
+
}
|
|
828
1386
|
|
|
829
|
-
exisitingUser = await this.authenticationService.signUp(user)
|
|
1387
|
+
exisitingUser = await this.timeOperation('user-sign-up', () => this.authenticationService.signUp(user), {
|
|
1388
|
+
component: 'users',
|
|
1389
|
+
serviceCall: 'authenticationService.signUp',
|
|
1390
|
+
details: `username=${user.username}`,
|
|
1391
|
+
});
|
|
1392
|
+
if (isSystemAdminUser) {
|
|
1393
|
+
await this.timeOperation('seed-sa-api-key', async () => {
|
|
1394
|
+
const generatedApiKey = await this.apiKeyService.generate(exisitingUser.id, {
|
|
1395
|
+
name: 'Default SA API Key',
|
|
1396
|
+
});
|
|
1397
|
+
await this.writeSolidxAdminApiKeyConfig(generatedApiKey);
|
|
1398
|
+
}, {
|
|
1399
|
+
component: 'users',
|
|
1400
|
+
serviceCall: 'apiKeyService.generate',
|
|
1401
|
+
details: `username=${user.username}`,
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
830
1404
|
this.logger.log(`Newly created user ${user.username}`);
|
|
831
1405
|
}
|
|
1406
|
+
if (isSystemAdminUser) {
|
|
1407
|
+
await this.ensureSystemAdminApiKey(exisitingUser);
|
|
1408
|
+
}
|
|
832
1409
|
//FIXME: Create the user roles assignment logic here.
|
|
833
1410
|
// now add Roles to user.
|
|
834
1411
|
// for (let m = 0; m < roles.length; m++) {
|
|
@@ -838,12 +1415,113 @@ export class ModuleMetadataSeederService {
|
|
|
838
1415
|
}
|
|
839
1416
|
}
|
|
840
1417
|
|
|
1418
|
+
private async ensureSystemAdminApiKey(existingUser: { id: number; username: string; isAllowedToGenerateApiKeys?: boolean }) {
|
|
1419
|
+
if (!existingUser?.id) {
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
if (!existingUser.isAllowedToGenerateApiKeys) {
|
|
1424
|
+
await this.timeOperation('enable-sa-api-key-generation', () => this.userApiKeyRepository.manager.update(User, { id: existingUser.id }, {
|
|
1425
|
+
isAllowedToGenerateApiKeys: true,
|
|
1426
|
+
}), {
|
|
1427
|
+
component: 'users',
|
|
1428
|
+
serviceCall: 'userRepository.update',
|
|
1429
|
+
details: `username=${existingUser.username}`,
|
|
1430
|
+
});
|
|
1431
|
+
existingUser.isAllowedToGenerateApiKeys = true;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
const existingDefaultKey = await this.timeOperation('find-sa-default-api-key', () => this.userApiKeyRepository.findOne({
|
|
1435
|
+
where: {
|
|
1436
|
+
user: { id: existingUser.id },
|
|
1437
|
+
name: 'Default SA API Key',
|
|
1438
|
+
},
|
|
1439
|
+
relations: {
|
|
1440
|
+
user: true,
|
|
1441
|
+
},
|
|
1442
|
+
}), {
|
|
1443
|
+
component: 'users',
|
|
1444
|
+
serviceCall: 'userApiKeyRepository.findOne',
|
|
1445
|
+
details: `username=${existingUser.username}`,
|
|
1446
|
+
});
|
|
1447
|
+
|
|
1448
|
+
if (existingDefaultKey) {
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
await this.timeOperation('seed-sa-api-key', async () => {
|
|
1453
|
+
const generatedApiKey = await this.apiKeyService.generate(existingUser.id, {
|
|
1454
|
+
name: 'Default SA API Key',
|
|
1455
|
+
});
|
|
1456
|
+
await this.writeSolidxAdminApiKeyConfig(generatedApiKey);
|
|
1457
|
+
}, {
|
|
1458
|
+
component: 'users',
|
|
1459
|
+
serviceCall: 'apiKeyService.generate',
|
|
1460
|
+
details: `username=${existingUser.username}`,
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
private async writeSolidxAdminApiKeyConfig(generatedApiKey: {
|
|
1465
|
+
apiKey: string;
|
|
1466
|
+
record: {
|
|
1467
|
+
id: number;
|
|
1468
|
+
name: string;
|
|
1469
|
+
maskedKey: string;
|
|
1470
|
+
isActive: boolean;
|
|
1471
|
+
expiresAt: Date | null;
|
|
1472
|
+
createdAt?: Date;
|
|
1473
|
+
updatedAt?: Date;
|
|
1474
|
+
};
|
|
1475
|
+
}) {
|
|
1476
|
+
const consumingProjectRoot = path.resolve(process.cwd(), '..');
|
|
1477
|
+
const consumingProjectName = path.basename(consumingProjectRoot);
|
|
1478
|
+
const solidxDir = path.join(os.homedir(), '.solidx', consumingProjectName);
|
|
1479
|
+
const configPath = path.join(solidxDir, 'mcp.json');
|
|
1480
|
+
await fs.promises.mkdir(solidxDir, { recursive: true });
|
|
1481
|
+
|
|
1482
|
+
let existingConfig: Record<string, any> = {};
|
|
1483
|
+
|
|
1484
|
+
if (fs.existsSync(configPath)) {
|
|
1485
|
+
const rawConfig = await fs.promises.readFile(configPath, 'utf8');
|
|
1486
|
+
if (rawConfig.trim()) {
|
|
1487
|
+
const parsedConfig = JSON.parse(rawConfig);
|
|
1488
|
+
if (parsedConfig && typeof parsedConfig === 'object' && !Array.isArray(parsedConfig)) {
|
|
1489
|
+
existingConfig = parsedConfig;
|
|
1490
|
+
} else {
|
|
1491
|
+
this.logger.warn(`Expected ${configPath} to contain a JSON object. Overwriting with a new object.`);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
const nextConfig = {
|
|
1497
|
+
...existingConfig,
|
|
1498
|
+
solidxAdminApiKey: {
|
|
1499
|
+
id: generatedApiKey.record.id,
|
|
1500
|
+
name: generatedApiKey.record.name,
|
|
1501
|
+
apiKey: generatedApiKey.apiKey,
|
|
1502
|
+
maskedKey: generatedApiKey.record.maskedKey,
|
|
1503
|
+
isActive: generatedApiKey.record.isActive,
|
|
1504
|
+
expiresAt: generatedApiKey.record.expiresAt,
|
|
1505
|
+
createdAt: generatedApiKey.record.createdAt,
|
|
1506
|
+
updatedAt: generatedApiKey.record.updatedAt,
|
|
1507
|
+
},
|
|
1508
|
+
};
|
|
1509
|
+
|
|
1510
|
+
await fs.promises.writeFile(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, 'utf8');
|
|
1511
|
+
}
|
|
1512
|
+
|
|
841
1513
|
// OK
|
|
842
1514
|
private async seedModuleModelFields(moduleMetadata: CreateModuleMetadataDto): Promise<{ pruned: number; upserted: number }> {
|
|
843
|
-
this.
|
|
1515
|
+
const fieldMetadataRepo = this.dataSource.getRepository(FieldMetadata);
|
|
844
1516
|
|
|
845
1517
|
// First we create the module.
|
|
846
|
-
const module = await this.moduleMetadataService.upsert(moduleMetadata)
|
|
1518
|
+
const module = await this.timeOperation('module-upsert', () => this.moduleMetadataService.upsert(moduleMetadata), {
|
|
1519
|
+
moduleName: moduleMetadata.name,
|
|
1520
|
+
component: 'module-model-fields',
|
|
1521
|
+
serviceCall: 'moduleMetadataService.upsert',
|
|
1522
|
+
details: `module=${moduleMetadata.name}`,
|
|
1523
|
+
});
|
|
1524
|
+
this.moduleLookupCache.set(moduleMetadata.name, module ?? null);
|
|
847
1525
|
let pruned = 0;
|
|
848
1526
|
let upserted = 1;
|
|
849
1527
|
|
|
@@ -859,19 +1537,47 @@ export class ModuleMetadataSeederService {
|
|
|
859
1537
|
|
|
860
1538
|
// Load and set the parent model if it exists.
|
|
861
1539
|
if (modelMetadata.isChild && modelMetadata.parentModelUserKey) {
|
|
862
|
-
const parentModel = await this.
|
|
1540
|
+
const parentModel = await this.getModelByUserKeyCached(modelMetadata.parentModelUserKey, {
|
|
1541
|
+
moduleName: moduleMetadata.name,
|
|
1542
|
+
component: 'module-model-fields',
|
|
1543
|
+
details: `parentModel=${modelMetadata.parentModelUserKey}`,
|
|
1544
|
+
});
|
|
863
1545
|
modelMetaDataWithoutFields['parentModel'] = parentModel;
|
|
864
1546
|
}
|
|
865
1547
|
|
|
866
|
-
await this.modelMetadataService.upsert(modelMetaDataWithoutFields)
|
|
867
|
-
|
|
1548
|
+
const upsertedModel = await this.timeOperation('model-upsert', () => this.modelMetadataService.upsert(modelMetaDataWithoutFields), {
|
|
1549
|
+
moduleName: moduleMetadata.name,
|
|
1550
|
+
component: 'module-model-fields',
|
|
1551
|
+
serviceCall: 'modelMetadataService.upsert',
|
|
1552
|
+
details: `model=${modelMetadata.singularName}`,
|
|
1553
|
+
});
|
|
1554
|
+
this.modelLookupCache.set(modelMetadata.singularName, upsertedModel ?? null);
|
|
1555
|
+
const model = await this.timeOperation('model-find-by-singular-name', () => this.modelMetadataService.findOneBySingularName(modelMetadata.singularName), {
|
|
1556
|
+
moduleName: moduleMetadata.name,
|
|
1557
|
+
component: 'module-model-fields',
|
|
1558
|
+
serviceCall: 'modelMetadataService.findOneBySingularName',
|
|
1559
|
+
details: `model=${modelMetadata.singularName}`,
|
|
1560
|
+
});
|
|
868
1561
|
|
|
869
1562
|
// iterate over all fields and upsert.
|
|
870
1563
|
if (this.enablePruning) {
|
|
871
|
-
pruned += await this.pruneFieldsForModel(model, fieldsMetadata)
|
|
1564
|
+
pruned += await this.timeOperation('prune-fields-for-model', () => this.pruneFieldsForModel(model, fieldsMetadata), {
|
|
1565
|
+
moduleName: moduleMetadata.name,
|
|
1566
|
+
component: 'module-model-fields',
|
|
1567
|
+
serviceCall: 'pruneFieldsForModel',
|
|
1568
|
+
details: `model=${modelMetadata.singularName}`,
|
|
1569
|
+
});
|
|
872
1570
|
}
|
|
873
1571
|
let userKeyField = null;
|
|
874
1572
|
const userKeyFieldName = modelMetadata.userKeyFieldUserKey;
|
|
1573
|
+
// Preload all existing fields for the model once. This is why field upserts are now effectively cheap:
|
|
1574
|
+
// we no longer do row-by-row existence checks for every field in steady-state runs.
|
|
1575
|
+
const existingFieldsByName = await this.timeOperation('field-preload-for-model', () => this.loadExistingFieldsByName(fieldMetadataRepo, model.id), {
|
|
1576
|
+
moduleName: moduleMetadata.name,
|
|
1577
|
+
component: 'module-model-fields',
|
|
1578
|
+
serviceCall: 'loadExistingFieldsByName',
|
|
1579
|
+
details: `model=${modelMetadata.singularName}`,
|
|
1580
|
+
});
|
|
875
1581
|
upserted += fieldsMetadata?.length ?? 0;
|
|
876
1582
|
for (let k = 0; k < fieldsMetadata.length; k++) {
|
|
877
1583
|
const fieldMetadata = fieldsMetadata[k];
|
|
@@ -879,11 +1585,20 @@ export class ModuleMetadataSeederService {
|
|
|
879
1585
|
// Link model & mediaStorageProvider.
|
|
880
1586
|
fieldMetadata['model'] = model;
|
|
881
1587
|
if (fieldMetadata.mediaStorageProviderUserKey) {
|
|
882
|
-
fieldMetadata['mediaStorageProvider'] = await this.
|
|
1588
|
+
fieldMetadata['mediaStorageProvider'] = await this.getMediaStorageProviderByUserKeyCached(fieldMetadata.mediaStorageProviderUserKey, {
|
|
1589
|
+
moduleName: moduleMetadata.name,
|
|
1590
|
+
component: 'module-model-fields',
|
|
1591
|
+
details: `provider=${fieldMetadata.mediaStorageProviderUserKey}`,
|
|
1592
|
+
});
|
|
883
1593
|
}
|
|
884
1594
|
// console.log(fieldMetadata.displayName);
|
|
885
1595
|
|
|
886
|
-
const affectedField = await this.
|
|
1596
|
+
const affectedField = await this.timeOperation('field-upsert', () => this.upsertFieldMetadataWithPreloadedExisting(fieldMetadataRepo, existingFieldsByName, fieldMetadata), {
|
|
1597
|
+
moduleName: moduleMetadata.name,
|
|
1598
|
+
component: 'module-model-fields',
|
|
1599
|
+
serviceCall: 'upsertFieldMetadataWithPreloadedExisting',
|
|
1600
|
+
details: `field=${fieldMetadata.name} model=${modelMetadata.singularName}`,
|
|
1601
|
+
});
|
|
887
1602
|
if (fieldMetadata.name === userKeyFieldName || fieldMetadata.isUserKey) {
|
|
888
1603
|
const { model, ...fieldData } = affectedField;
|
|
889
1604
|
userKeyField = fieldData;
|
|
@@ -893,13 +1608,21 @@ export class ModuleMetadataSeederService {
|
|
|
893
1608
|
// Now that we have created fields & model update the model to stamp the userKeyField.
|
|
894
1609
|
if (userKeyField) {
|
|
895
1610
|
modelMetaDataWithoutFields['userKeyField'] = userKeyField;
|
|
896
|
-
await this.modelMetadataService.upsert(modelMetaDataWithoutFields)
|
|
1611
|
+
await this.timeOperation('model-user-key-field-upsert', () => this.modelMetadataService.upsert(modelMetaDataWithoutFields), {
|
|
1612
|
+
moduleName: moduleMetadata.name,
|
|
1613
|
+
component: 'module-model-fields',
|
|
1614
|
+
serviceCall: 'modelMetadataService.upsert',
|
|
1615
|
+
details: `model=${modelMetadata.singularName} userKeyField=${userKeyField?.name ?? 'unknown'}`,
|
|
1616
|
+
});
|
|
897
1617
|
}
|
|
898
1618
|
}
|
|
899
1619
|
if (this.enablePruning) {
|
|
900
|
-
pruned += await this.pruneModels(modelsMetadata, moduleMetadata.name)
|
|
1620
|
+
pruned += await this.timeOperation('prune-models', () => this.pruneModels(modelsMetadata, moduleMetadata.name), {
|
|
1621
|
+
moduleName: moduleMetadata.name,
|
|
1622
|
+
component: 'module-model-fields',
|
|
1623
|
+
serviceCall: 'pruneModels',
|
|
1624
|
+
});
|
|
901
1625
|
}
|
|
902
|
-
this.logger.debug(`[End] Processing module metadata`);
|
|
903
1626
|
return { pruned, upserted };
|
|
904
1627
|
}
|
|
905
1628
|
|
|
@@ -907,13 +1630,343 @@ export class ModuleMetadataSeederService {
|
|
|
907
1630
|
return Array.isArray(value) ? value : [];
|
|
908
1631
|
}
|
|
909
1632
|
|
|
1633
|
+
private async loadExistingFieldsByName(fieldMetadataRepo: Repository<FieldMetadata>, modelId: number): Promise<Map<string, FieldMetadata>> {
|
|
1634
|
+
const existingFields = await fieldMetadataRepo.find({
|
|
1635
|
+
where: {
|
|
1636
|
+
model: { id: modelId },
|
|
1637
|
+
},
|
|
1638
|
+
relations: {
|
|
1639
|
+
model: true,
|
|
1640
|
+
mediaStorageProvider: true,
|
|
1641
|
+
},
|
|
1642
|
+
});
|
|
1643
|
+
|
|
1644
|
+
return new Map(existingFields.map((field) => [field.name, field]));
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
private async primeModuleLookupCache(moduleUserKeys: Array<string | null | undefined>): Promise<void> {
|
|
1648
|
+
const missingModuleUserKeys = [...new Set(moduleUserKeys.filter((moduleUserKey): moduleUserKey is string =>
|
|
1649
|
+
Boolean(moduleUserKey) && !this.moduleLookupCache.has(moduleUserKey),
|
|
1650
|
+
))];
|
|
1651
|
+
|
|
1652
|
+
if (missingModuleUserKeys.length === 0) {
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// Only bulk-load keys that are still missing from the process-local cache.
|
|
1657
|
+
const moduleRepo = this.dataSource.getRepository(ModuleMetadata);
|
|
1658
|
+
const existingModules = await moduleRepo.find({
|
|
1659
|
+
where: {
|
|
1660
|
+
name: In(missingModuleUserKeys),
|
|
1661
|
+
},
|
|
1662
|
+
});
|
|
1663
|
+
|
|
1664
|
+
const moduleByName = new Map(existingModules.map((module) => [module.name, module]));
|
|
1665
|
+
for (const moduleUserKey of missingModuleUserKeys) {
|
|
1666
|
+
this.moduleLookupCache.set(moduleUserKey, moduleByName.get(moduleUserKey) ?? null);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
private async primeModelLookupCache(modelUserKeys: Array<string | null | undefined>): Promise<void> {
|
|
1671
|
+
const missingModelUserKeys = [...new Set(modelUserKeys.filter((modelUserKey): modelUserKey is string =>
|
|
1672
|
+
Boolean(modelUserKey) && !this.modelLookupCache.has(modelUserKey),
|
|
1673
|
+
))];
|
|
1674
|
+
|
|
1675
|
+
if (missingModelUserKeys.length === 0) {
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
const modelRepo = this.dataSource.getRepository(ModelMetadata);
|
|
1680
|
+
const existingModels = await modelRepo.find({
|
|
1681
|
+
where: {
|
|
1682
|
+
singularName: In(missingModelUserKeys),
|
|
1683
|
+
},
|
|
1684
|
+
relations: {
|
|
1685
|
+
module: true,
|
|
1686
|
+
userKeyField: true,
|
|
1687
|
+
},
|
|
1688
|
+
});
|
|
1689
|
+
|
|
1690
|
+
const modelBySingularName = new Map(existingModels.map((model) => [model.singularName, model]));
|
|
1691
|
+
for (const modelUserKey of missingModelUserKeys) {
|
|
1692
|
+
this.modelLookupCache.set(modelUserKey, modelBySingularName.get(modelUserKey) ?? null);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
private async primeViewLookupCache(viewUserKeys: Array<string | null | undefined>): Promise<void> {
|
|
1697
|
+
const missingViewUserKeys = [...new Set(viewUserKeys.filter((viewUserKey): viewUserKey is string =>
|
|
1698
|
+
Boolean(viewUserKey) && !this.viewLookupCache.has(viewUserKey),
|
|
1699
|
+
))];
|
|
1700
|
+
|
|
1701
|
+
if (missingViewUserKeys.length === 0) {
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
const viewRepo = this.dataSource.getRepository(ViewMetadata);
|
|
1706
|
+
const existingViews = await viewRepo.find({
|
|
1707
|
+
where: {
|
|
1708
|
+
name: In(missingViewUserKeys),
|
|
1709
|
+
},
|
|
1710
|
+
relations: {
|
|
1711
|
+
module: true,
|
|
1712
|
+
model: true,
|
|
1713
|
+
},
|
|
1714
|
+
});
|
|
1715
|
+
|
|
1716
|
+
const viewByName = new Map(existingViews.map((view) => [view.name, view]));
|
|
1717
|
+
for (const viewUserKey of missingViewUserKeys) {
|
|
1718
|
+
this.viewLookupCache.set(viewUserKey, viewByName.get(viewUserKey) ?? null);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
private async loadExistingViewsByName(viewRepo: Repository<ViewMetadata>, viewNames: string[]): Promise<Map<string, ViewMetadata>> {
|
|
1723
|
+
const uniqueViewNames = [...new Set(viewNames.filter(Boolean))];
|
|
1724
|
+
if (uniqueViewNames.length === 0) {
|
|
1725
|
+
return new Map();
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
// Load relation ids needed for no-op detection. If module/model are not present here, unchanged rows look
|
|
1729
|
+
// dirty and every view falls through to save().
|
|
1730
|
+
const existingViews = await viewRepo.find({
|
|
1731
|
+
where: {
|
|
1732
|
+
name: In(uniqueViewNames),
|
|
1733
|
+
},
|
|
1734
|
+
relations: {
|
|
1735
|
+
module: true,
|
|
1736
|
+
model: true,
|
|
1737
|
+
},
|
|
1738
|
+
});
|
|
1739
|
+
|
|
1740
|
+
return new Map(existingViews.map((view) => [view.name, view]));
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
private async loadExistingActionsByName(actionRepo: Repository<ActionMetadata>, actionNames: string[]): Promise<Map<string, ActionMetadata>> {
|
|
1744
|
+
const uniqueActionNames = [...new Set(actionNames.filter(Boolean))];
|
|
1745
|
+
if (uniqueActionNames.length === 0) {
|
|
1746
|
+
return new Map();
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
// Same reason as views: the diff logic compares relation ids, so those relations must be loaded here.
|
|
1750
|
+
const existingActions = await actionRepo.find({
|
|
1751
|
+
where: {
|
|
1752
|
+
name: In(uniqueActionNames),
|
|
1753
|
+
},
|
|
1754
|
+
relations: {
|
|
1755
|
+
module: true,
|
|
1756
|
+
model: true,
|
|
1757
|
+
view: true,
|
|
1758
|
+
},
|
|
1759
|
+
});
|
|
1760
|
+
|
|
1761
|
+
return new Map(existingActions.map((action) => [action.name, action]));
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
private normalizeJsonFieldValue(value: any): any {
|
|
1765
|
+
// Normalize persisted JSON strings and in-memory objects/arrays into the same canonical shape before compare.
|
|
1766
|
+
if (typeof value === 'string') {
|
|
1767
|
+
const trimmedValue = value.trim();
|
|
1768
|
+
if (
|
|
1769
|
+
(trimmedValue.startsWith('{') && trimmedValue.endsWith('}'))
|
|
1770
|
+
|| (trimmedValue.startsWith('[') && trimmedValue.endsWith(']'))
|
|
1771
|
+
) {
|
|
1772
|
+
try {
|
|
1773
|
+
return this.normalizeJsonFieldValue(JSON.parse(trimmedValue));
|
|
1774
|
+
} catch {
|
|
1775
|
+
return value;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
return value;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
if (Array.isArray(value)) {
|
|
1783
|
+
return value.map((item) => this.normalizeJsonFieldValue(item));
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
if (value && typeof value === 'object') {
|
|
1787
|
+
return Object.keys(value)
|
|
1788
|
+
.sort()
|
|
1789
|
+
.reduce((result, key) => {
|
|
1790
|
+
result[key] = this.normalizeJsonFieldValue(value[key]);
|
|
1791
|
+
return result;
|
|
1792
|
+
}, {} as Record<string, any>);
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
return value;
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
private getCanonicalJsonFieldString(value: any): string {
|
|
1799
|
+
return JSON.stringify(this.normalizeJsonFieldValue(value) ?? null);
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
private hasViewMetadataChanges(existingView: ViewMetadata, updateDto: any): boolean {
|
|
1803
|
+
const relationFields = new Set(['module', 'model']);
|
|
1804
|
+
const jsonFields = new Set(['layout', 'context']);
|
|
1805
|
+
|
|
1806
|
+
// Compare only the persisted fields we explicitly care about. Generic object-wide diffing became fragile once
|
|
1807
|
+
// seeding DTOs started carrying transient helper properties that should not trigger writes.
|
|
1808
|
+
return this.viewComparableFields.some((key) => {
|
|
1809
|
+
const value = updateDto[key];
|
|
1810
|
+
if (typeof value === 'undefined') {
|
|
1811
|
+
return false;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
if (relationFields.has(key)) {
|
|
1815
|
+
const relationValue = value as any;
|
|
1816
|
+
return (existingView as any)[key]?.id !== relationValue?.id;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
if (jsonFields.has(key)) {
|
|
1820
|
+
return this.getCanonicalJsonFieldString((existingView as any)[key]) !== this.getCanonicalJsonFieldString(value);
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
return (existingView as any)[key] !== value;
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
private hasActionMetadataChanges(existingAction: ActionMetadata, updateDto: any): boolean {
|
|
1828
|
+
const relationFields = new Set(['module', 'model', 'view']);
|
|
1829
|
+
const jsonFields = new Set(['domain', 'context']);
|
|
1830
|
+
|
|
1831
|
+
return this.actionComparableFields.some((key) => {
|
|
1832
|
+
const value = updateDto[key];
|
|
1833
|
+
if (typeof value === 'undefined') {
|
|
1834
|
+
return false;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
if (relationFields.has(key)) {
|
|
1838
|
+
const relationValue = value as any;
|
|
1839
|
+
return (existingAction as any)[key]?.id !== relationValue?.id;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
if (jsonFields.has(key)) {
|
|
1843
|
+
return this.getCanonicalJsonFieldString((existingAction as any)[key]) !== this.getCanonicalJsonFieldString(value);
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
return (existingAction as any)[key] !== value;
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
private async upsertViewMetadataWithPreloadedExisting(
|
|
1851
|
+
viewRepo: Repository<ViewMetadata>,
|
|
1852
|
+
existingViewsByName: Map<string, ViewMetadata>,
|
|
1853
|
+
updateDto: any,
|
|
1854
|
+
): Promise<{ entity: ViewMetadata; outcome: 'created' | 'updated' | 'skipped' }> {
|
|
1855
|
+
const existingView = existingViewsByName.get(updateDto.name);
|
|
1856
|
+
|
|
1857
|
+
if (existingView) {
|
|
1858
|
+
// In the steady-state case we want to return fast here and avoid save() entirely.
|
|
1859
|
+
if (!this.hasViewMetadataChanges(existingView, updateDto)) {
|
|
1860
|
+
return { entity: existingView, outcome: 'skipped' };
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
const updatedView = { ...existingView, ...updateDto };
|
|
1864
|
+
const savedView = await this.timeOperation('view-save', () => viewRepo.save(updatedView as ViewMetadata), {
|
|
1865
|
+
component: 'views',
|
|
1866
|
+
serviceCall: 'viewRepo.save',
|
|
1867
|
+
details: `view=${updateDto.name}`,
|
|
1868
|
+
}) as ViewMetadata;
|
|
1869
|
+
existingViewsByName.set(updateDto.name, savedView);
|
|
1870
|
+
return { entity: savedView, outcome: 'updated' };
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
const view = viewRepo.create(updateDto as Partial<ViewMetadata>);
|
|
1874
|
+
const savedView = await this.timeOperation('view-save', () => viewRepo.save(view as ViewMetadata), {
|
|
1875
|
+
component: 'views',
|
|
1876
|
+
serviceCall: 'viewRepo.save',
|
|
1877
|
+
details: `view=${updateDto.name}`,
|
|
1878
|
+
}) as ViewMetadata;
|
|
1879
|
+
existingViewsByName.set(updateDto.name, savedView);
|
|
1880
|
+
return { entity: savedView, outcome: 'created' };
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
private async upsertActionMetadataWithPreloadedExisting(
|
|
1884
|
+
actionRepo: Repository<ActionMetadata>,
|
|
1885
|
+
existingActionsByName: Map<string, ActionMetadata>,
|
|
1886
|
+
updateDto: any,
|
|
1887
|
+
): Promise<{ entity: ActionMetadata; outcome: 'created' | 'updated' | 'skipped' }> {
|
|
1888
|
+
const existingAction = existingActionsByName.get(updateDto.name);
|
|
1889
|
+
|
|
1890
|
+
if (existingAction) {
|
|
1891
|
+
if (!this.hasActionMetadataChanges(existingAction, updateDto)) {
|
|
1892
|
+
return { entity: existingAction, outcome: 'skipped' };
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
const updatedAction = { ...existingAction, ...updateDto };
|
|
1896
|
+
const savedAction = await this.timeOperation('action-save', () => actionRepo.save(updatedAction as ActionMetadata), {
|
|
1897
|
+
component: 'actions',
|
|
1898
|
+
serviceCall: 'actionRepo.save',
|
|
1899
|
+
details: `action=${updateDto.name}`,
|
|
1900
|
+
}) as ActionMetadata;
|
|
1901
|
+
existingActionsByName.set(updateDto.name, savedAction);
|
|
1902
|
+
return { entity: savedAction, outcome: 'updated' };
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
const action = actionRepo.create(updateDto as Partial<ActionMetadata>);
|
|
1906
|
+
const savedAction = await this.timeOperation('action-save', () => actionRepo.save(action as ActionMetadata), {
|
|
1907
|
+
component: 'actions',
|
|
1908
|
+
serviceCall: 'actionRepo.save',
|
|
1909
|
+
details: `action=${updateDto.name}`,
|
|
1910
|
+
}) as ActionMetadata;
|
|
1911
|
+
existingActionsByName.set(updateDto.name, savedAction);
|
|
1912
|
+
return { entity: savedAction, outcome: 'created' };
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
private async upsertFieldMetadataWithPreloadedExisting(
|
|
1916
|
+
fieldMetadataRepo: Repository<FieldMetadata>,
|
|
1917
|
+
existingFieldsByName: Map<string, FieldMetadata>,
|
|
1918
|
+
updateDto: any,
|
|
1919
|
+
): Promise<FieldMetadata> {
|
|
1920
|
+
const existingFieldMetadata = existingFieldsByName.get(updateDto.name);
|
|
1921
|
+
|
|
1922
|
+
if (existingFieldMetadata) {
|
|
1923
|
+
// Field metadata still uses a broad DTO diff because the major performance problem here was query volume,
|
|
1924
|
+
// not the in-memory comparison itself.
|
|
1925
|
+
const hasChanges = Object.entries(updateDto).some(([key, value]) => {
|
|
1926
|
+
const relationValue = value as any;
|
|
1927
|
+
if (key === 'model') {
|
|
1928
|
+
return existingFieldMetadata.model?.id !== relationValue?.id;
|
|
1929
|
+
}
|
|
1930
|
+
if (key === 'mediaStorageProvider') {
|
|
1931
|
+
return existingFieldMetadata.mediaStorageProvider?.id !== relationValue?.id;
|
|
1932
|
+
}
|
|
1933
|
+
const currentValue = (existingFieldMetadata as any)[key];
|
|
1934
|
+
if (Array.isArray(currentValue) || Array.isArray(value)) {
|
|
1935
|
+
return JSON.stringify(currentValue ?? null) !== JSON.stringify(value ?? null);
|
|
1936
|
+
}
|
|
1937
|
+
if (value && typeof value === 'object') {
|
|
1938
|
+
return JSON.stringify(currentValue ?? null) !== JSON.stringify(value ?? null);
|
|
1939
|
+
}
|
|
1940
|
+
return currentValue !== value;
|
|
1941
|
+
});
|
|
1942
|
+
|
|
1943
|
+
if (!hasChanges) {
|
|
1944
|
+
return existingFieldMetadata;
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
const updatedFieldMetadata = { ...existingFieldMetadata, ...updateDto };
|
|
1948
|
+
const savedFieldMetadata = await fieldMetadataRepo.save(updatedFieldMetadata as FieldMetadata) as FieldMetadata;
|
|
1949
|
+
existingFieldsByName.set(updateDto.name, savedFieldMetadata);
|
|
1950
|
+
return savedFieldMetadata;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
const fieldMetadata = fieldMetadataRepo.create(updateDto as Partial<FieldMetadata>);
|
|
1954
|
+
const savedFieldMetadata = await fieldMetadataRepo.save(fieldMetadata as FieldMetadata) as FieldMetadata;
|
|
1955
|
+
existingFieldsByName.set(updateDto.name, savedFieldMetadata);
|
|
1956
|
+
return savedFieldMetadata;
|
|
1957
|
+
}
|
|
1958
|
+
|
|
910
1959
|
private async handleSeedSecurityRules(rulesDto: CreateSecurityRuleDto[]) {
|
|
911
1960
|
if (!rulesDto || rulesDto.length === 0) {
|
|
912
1961
|
this.logger.debug(`No security rules found to seed`);
|
|
913
1962
|
return;
|
|
914
1963
|
}
|
|
915
1964
|
for (const dto of rulesDto) {
|
|
916
|
-
await this.securityRuleRepo.upsertWithDto({ ...dto, securityRuleConfig: JSON.stringify(dto.securityRuleConfig) })
|
|
1965
|
+
await this.timeOperation('security-rule-upsert', () => this.securityRuleRepo.upsertWithDto({ ...dto, securityRuleConfig: JSON.stringify(dto.securityRuleConfig) }), {
|
|
1966
|
+
component: 'security-rules',
|
|
1967
|
+
serviceCall: 'securityRuleRepo.upsertWithDto',
|
|
1968
|
+
details: `rule=${dto.name}`,
|
|
1969
|
+
});
|
|
917
1970
|
}
|
|
918
1971
|
}
|
|
919
1972
|
|
|
@@ -925,8 +1978,16 @@ export class ModuleMetadataSeederService {
|
|
|
925
1978
|
}
|
|
926
1979
|
for (let j = 0; j < listOfValuesDto.length; j++) {
|
|
927
1980
|
const listOfValueDto = listOfValuesDto[j];
|
|
928
|
-
listOfValueDto['module'] = await this.
|
|
929
|
-
|
|
1981
|
+
listOfValueDto['module'] = await this.getModuleByUserKeyCached(listOfValueDto.moduleUserKey, {
|
|
1982
|
+
moduleName: listOfValueDto.moduleUserKey,
|
|
1983
|
+
component: 'list-of-values',
|
|
1984
|
+
details: `module=${listOfValueDto.moduleUserKey}`,
|
|
1985
|
+
});
|
|
1986
|
+
await this.timeOperation('lov-upsert', () => this.listOfValuesService.upsert(listOfValuesDto[j]), {
|
|
1987
|
+
component: 'list-of-values',
|
|
1988
|
+
serviceCall: 'listOfValuesService.upsert',
|
|
1989
|
+
details: `type=${listOfValueDto.type} value=${listOfValueDto.value}`,
|
|
1990
|
+
});
|
|
930
1991
|
}
|
|
931
1992
|
}
|
|
932
1993
|
|
|
@@ -936,7 +1997,11 @@ export class ModuleMetadataSeederService {
|
|
|
936
1997
|
return;
|
|
937
1998
|
}
|
|
938
1999
|
for (const dto of createScheduledJobDto) {
|
|
939
|
-
await this.scheduledJobRepository.upsertWithDto(dto)
|
|
2000
|
+
await this.timeOperation('scheduled-job-upsert', () => this.scheduledJobRepository.upsertWithDto(dto), {
|
|
2001
|
+
component: 'scheduled-jobs',
|
|
2002
|
+
serviceCall: 'scheduledJobRepository.upsertWithDto',
|
|
2003
|
+
details: `schedule=${dto.scheduleName}`,
|
|
2004
|
+
});
|
|
940
2005
|
}
|
|
941
2006
|
}
|
|
942
2007
|
|
|
@@ -947,10 +2012,56 @@ export class ModuleMetadataSeederService {
|
|
|
947
2012
|
}
|
|
948
2013
|
for (const dto of createSavedFilterDto) {
|
|
949
2014
|
this.validateSavedFilterQueryJsonWrapper(dto);
|
|
950
|
-
await this.savedFiltersRepo.upsertWithDto({ ...dto, filterQueryJson: JSON.stringify(dto.filterQueryJson), isSeeded: true })
|
|
2015
|
+
await this.timeOperation('saved-filter-upsert', () => this.savedFiltersRepo.upsertWithDto({ ...dto, filterQueryJson: JSON.stringify(dto.filterQueryJson), isSeeded: true }), {
|
|
2016
|
+
component: 'saved-filters',
|
|
2017
|
+
serviceCall: 'savedFiltersRepo.upsertWithDto',
|
|
2018
|
+
details: `filter=${dto.name}`,
|
|
2019
|
+
});
|
|
951
2020
|
}
|
|
952
2021
|
}
|
|
953
2022
|
|
|
2023
|
+
private async handleSeedLocales(localesDto: CreateLocaleDto[]) {
|
|
2024
|
+
if (!localesDto || localesDto.length === 0) {
|
|
2025
|
+
this.logger.debug(`No locales found to seed`);
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
for (const dto of localesDto) {
|
|
2030
|
+
if (dto.isDefault) {
|
|
2031
|
+
const existingDefaultLocales = await this.localeRepo.find({
|
|
2032
|
+
where: {
|
|
2033
|
+
isDefault: true,
|
|
2034
|
+
} as any,
|
|
2035
|
+
});
|
|
2036
|
+
|
|
2037
|
+
for (const locale of existingDefaultLocales) {
|
|
2038
|
+
if (locale.locale !== dto.locale) {
|
|
2039
|
+
await this.localeRepo.save({
|
|
2040
|
+
...locale,
|
|
2041
|
+
isDefault: false,
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
const existingLocale = await this.localeRepo.findOne({
|
|
2048
|
+
where: {
|
|
2049
|
+
locale: dto.locale,
|
|
2050
|
+
} as any,
|
|
2051
|
+
});
|
|
2052
|
+
|
|
2053
|
+
await this.localeRepo.save(
|
|
2054
|
+
this.localeRepo.create({
|
|
2055
|
+
...existingLocale,
|
|
2056
|
+
...dto,
|
|
2057
|
+
})
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
const locales = await this.localeRepo.find();
|
|
2062
|
+
this.solidRegistry.registerlocales(locales);
|
|
2063
|
+
}
|
|
2064
|
+
|
|
954
2065
|
private validateSavedFilterQueryJsonWrapper(dto: CreateSavedFiltersDto): void {
|
|
955
2066
|
const filterName = dto?.name ?? '<unnamed>';
|
|
956
2067
|
const filterQueryJson = dto?.filterQueryJson;
|
|
@@ -976,7 +2087,11 @@ export class ModuleMetadataSeederService {
|
|
|
976
2087
|
return;
|
|
977
2088
|
}
|
|
978
2089
|
for (const dto of modelSequencesDto) {
|
|
979
|
-
await this.modelSequenceRepo.upsertWithDto(dto)
|
|
2090
|
+
await this.timeOperation('model-sequence-upsert', () => this.modelSequenceRepo.upsertWithDto(dto), {
|
|
2091
|
+
component: 'model-sequences',
|
|
2092
|
+
serviceCall: 'modelSequenceRepo.upsertWithDto',
|
|
2093
|
+
details: `sequence=${dto.sequenceName}`,
|
|
2094
|
+
});
|
|
980
2095
|
}
|
|
981
2096
|
}
|
|
982
2097
|
|
|
@@ -987,7 +2102,11 @@ export class ModuleMetadataSeederService {
|
|
|
987
2102
|
}
|
|
988
2103
|
const sequences = modelSequencesDto ?? [];
|
|
989
2104
|
|
|
990
|
-
const module = await this.
|
|
2105
|
+
const module = await this.getModuleByUserKeyCached(moduleName, {
|
|
2106
|
+
moduleName,
|
|
2107
|
+
component: 'model-sequences',
|
|
2108
|
+
details: `module=${moduleName}`,
|
|
2109
|
+
});
|
|
991
2110
|
if (!module) {
|
|
992
2111
|
this.logger.warn(`Skipping model sequence prune: module not found for ${moduleName}.`);
|
|
993
2112
|
return 0;
|
|
@@ -1026,7 +2145,11 @@ export class ModuleMetadataSeederService {
|
|
|
1026
2145
|
}
|
|
1027
2146
|
const savedFilters = savedFiltersDto ?? [];
|
|
1028
2147
|
|
|
1029
|
-
const module = await this.
|
|
2148
|
+
const module = await this.getModuleByUserKeyCached(moduleName, {
|
|
2149
|
+
moduleName,
|
|
2150
|
+
component: 'saved-filters',
|
|
2151
|
+
details: `module=${moduleName}`,
|
|
2152
|
+
});
|
|
1030
2153
|
if (!module) {
|
|
1031
2154
|
this.logger.warn(`Skipping saved filters prune: module not found for ${moduleName}.`);
|
|
1032
2155
|
return 0;
|
|
@@ -1066,7 +2189,11 @@ export class ModuleMetadataSeederService {
|
|
|
1066
2189
|
}
|
|
1067
2190
|
const scheduledJobs = scheduledJobsDto ?? [];
|
|
1068
2191
|
|
|
1069
|
-
const module = await this.
|
|
2192
|
+
const module = await this.getModuleByUserKeyCached(moduleName, {
|
|
2193
|
+
moduleName,
|
|
2194
|
+
component: 'scheduled-jobs',
|
|
2195
|
+
details: `module=${moduleName}`,
|
|
2196
|
+
});
|
|
1070
2197
|
if (!module) {
|
|
1071
2198
|
this.logger.warn(`Skipping scheduled jobs prune: module not found for ${moduleName}.`);
|
|
1072
2199
|
return 0;
|
|
@@ -1105,7 +2232,11 @@ export class ModuleMetadataSeederService {
|
|
|
1105
2232
|
}
|
|
1106
2233
|
const securityRules = securityRulesDto ?? [];
|
|
1107
2234
|
|
|
1108
|
-
const module = await this.
|
|
2235
|
+
const module = await this.getModuleByUserKeyCached(moduleName, {
|
|
2236
|
+
moduleName,
|
|
2237
|
+
component: 'security-rules',
|
|
2238
|
+
details: `module=${moduleName}`,
|
|
2239
|
+
});
|
|
1109
2240
|
if (!module) {
|
|
1110
2241
|
this.logger.warn(`Skipping security rules prune: module not found for ${moduleName}.`);
|
|
1111
2242
|
return 0;
|
|
@@ -1145,7 +2276,11 @@ export class ModuleMetadataSeederService {
|
|
|
1145
2276
|
}
|
|
1146
2277
|
const listOfValues = listOfValuesDto ?? [];
|
|
1147
2278
|
|
|
1148
|
-
const module = await this.
|
|
2279
|
+
const module = await this.getModuleByUserKeyCached(moduleName, {
|
|
2280
|
+
moduleName,
|
|
2281
|
+
component: 'list-of-values',
|
|
2282
|
+
details: `module=${moduleName}`,
|
|
2283
|
+
});
|
|
1149
2284
|
if (!module) {
|
|
1150
2285
|
this.logger.warn(`Skipping list of values prune: module not found for ${moduleName}.`);
|
|
1151
2286
|
return 0;
|
|
@@ -1193,7 +2328,11 @@ export class ModuleMetadataSeederService {
|
|
|
1193
2328
|
}
|
|
1194
2329
|
const menus = menusDto ?? [];
|
|
1195
2330
|
|
|
1196
|
-
const module = await this.
|
|
2331
|
+
const module = await this.getModuleByUserKeyCached(moduleName, {
|
|
2332
|
+
moduleName,
|
|
2333
|
+
component: 'menus',
|
|
2334
|
+
details: `module=${moduleName}`,
|
|
2335
|
+
});
|
|
1197
2336
|
if (!module) {
|
|
1198
2337
|
this.logger.warn(`Skipping menus prune: module not found for ${moduleName}.`);
|
|
1199
2338
|
return 0;
|
|
@@ -1240,7 +2379,11 @@ export class ModuleMetadataSeederService {
|
|
|
1240
2379
|
}
|
|
1241
2380
|
const views = viewsDto ?? [];
|
|
1242
2381
|
|
|
1243
|
-
const module = await this.
|
|
2382
|
+
const module = await this.getModuleByUserKeyCached(moduleName, {
|
|
2383
|
+
moduleName,
|
|
2384
|
+
component: 'views',
|
|
2385
|
+
details: `module=${moduleName}`,
|
|
2386
|
+
});
|
|
1244
2387
|
if (!module) {
|
|
1245
2388
|
this.logger.warn(`Skipping views prune: module not found for ${moduleName}.`);
|
|
1246
2389
|
return 0;
|
|
@@ -1300,7 +2443,11 @@ export class ModuleMetadataSeederService {
|
|
|
1300
2443
|
}
|
|
1301
2444
|
const actions = actionsDto ?? [];
|
|
1302
2445
|
|
|
1303
|
-
const module = await this.
|
|
2446
|
+
const module = await this.getModuleByUserKeyCached(moduleName, {
|
|
2447
|
+
moduleName,
|
|
2448
|
+
component: 'actions',
|
|
2449
|
+
details: `module=${moduleName}`,
|
|
2450
|
+
});
|
|
1304
2451
|
if (!module) {
|
|
1305
2452
|
this.logger.warn(`Skipping actions prune: module not found for ${moduleName}.`);
|
|
1306
2453
|
return 0;
|
|
@@ -1398,7 +2545,11 @@ export class ModuleMetadataSeederService {
|
|
|
1398
2545
|
}
|
|
1399
2546
|
const models = modelsMetadata ?? [];
|
|
1400
2547
|
|
|
1401
|
-
const module = await this.
|
|
2548
|
+
const module = await this.getModuleByUserKeyCached(moduleName, {
|
|
2549
|
+
moduleName,
|
|
2550
|
+
component: 'module-model-fields',
|
|
2551
|
+
details: `module=${moduleName}`,
|
|
2552
|
+
});
|
|
1402
2553
|
if (!module) {
|
|
1403
2554
|
this.logger.warn(`Skipping models prune: module not found for ${moduleName}.`);
|
|
1404
2555
|
return 0;
|