@solidxai/core 0.1.6-beta.9 → 0.1.7
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 +15 -0
- package/CHANGELOG.md +71 -0
- package/dist/controllers/dashboard-layout.controller.d.ts +47 -0
- package/dist/controllers/dashboard-layout.controller.d.ts.map +1 -0
- package/dist/controllers/dashboard-layout.controller.js +204 -0
- package/dist/controllers/dashboard-layout.controller.js.map +1 -0
- package/dist/dtos/create-dashboard-layout.dto.d.ts +8 -0
- package/dist/dtos/create-dashboard-layout.dto.d.ts.map +1 -0
- package/dist/dtos/create-dashboard-layout.dto.js +53 -0
- package/dist/dtos/create-dashboard-layout.dto.js.map +1 -0
- package/dist/dtos/create-dashboard-variable.dto.d.ts +1 -0
- package/dist/dtos/create-dashboard-variable.dto.d.ts.map +1 -1
- package/dist/dtos/create-dashboard-variable.dto.js +7 -1
- package/dist/dtos/create-dashboard-variable.dto.js.map +1 -1
- package/dist/dtos/update-dashboard-layout.dto.d.ts +8 -0
- package/dist/dtos/update-dashboard-layout.dto.d.ts.map +1 -0
- package/dist/dtos/update-dashboard-layout.dto.js +53 -0
- package/dist/dtos/update-dashboard-layout.dto.js.map +1 -0
- package/dist/dtos/update-dashboard-variable.dto.d.ts +1 -0
- package/dist/dtos/update-dashboard-variable.dto.d.ts.map +1 -1
- package/dist/dtos/update-dashboard-variable.dto.js +7 -1
- package/dist/dtos/update-dashboard-variable.dto.js.map +1 -1
- package/dist/entities/action-metadata.entity.d.ts.map +1 -1
- package/dist/entities/action-metadata.entity.js.map +1 -1
- package/dist/entities/ai-interaction.entity.d.ts.map +1 -1
- package/dist/entities/ai-interaction.entity.js +5 -4
- package/dist/entities/ai-interaction.entity.js.map +1 -1
- package/dist/entities/chatter-message-details.entity.d.ts.map +1 -1
- package/dist/entities/chatter-message-details.entity.js +4 -3
- package/dist/entities/chatter-message-details.entity.js.map +1 -1
- package/dist/entities/chatter-message.entity.d.ts.map +1 -1
- package/dist/entities/chatter-message.entity.js +4 -3
- package/dist/entities/chatter-message.entity.js.map +1 -1
- package/dist/entities/dashboard-layout.entity.d.ts +9 -0
- package/dist/entities/dashboard-layout.entity.d.ts.map +1 -0
- package/dist/entities/dashboard-layout.entity.js +41 -0
- package/dist/entities/dashboard-layout.entity.js.map +1 -0
- package/dist/entities/dashboard-question-sql-dataset-config.entity.d.ts.map +1 -1
- package/dist/entities/dashboard-question-sql-dataset-config.entity.js +5 -4
- package/dist/entities/dashboard-question-sql-dataset-config.entity.js.map +1 -1
- package/dist/entities/dashboard-question.entity.d.ts.map +1 -1
- package/dist/entities/dashboard-question.entity.js +5 -4
- package/dist/entities/dashboard-question.entity.js.map +1 -1
- package/dist/entities/dashboard-variable.entity.d.ts +1 -0
- package/dist/entities/dashboard-variable.entity.d.ts.map +1 -1
- package/dist/entities/dashboard-variable.entity.js +10 -4
- package/dist/entities/dashboard-variable.entity.js.map +1 -1
- package/dist/entities/dashboard.entity.d.ts +2 -0
- package/dist/entities/dashboard.entity.d.ts.map +1 -1
- package/dist/entities/dashboard.entity.js +9 -3
- package/dist/entities/dashboard.entity.js.map +1 -1
- package/dist/entities/email-attachment.entity.d.ts.map +1 -1
- package/dist/entities/email-attachment.entity.js +2 -1
- package/dist/entities/email-attachment.entity.js.map +1 -1
- package/dist/entities/email-template.entity.js +1 -1
- package/dist/entities/email-template.entity.js.map +1 -1
- package/dist/entities/export-transaction.entity.d.ts.map +1 -1
- package/dist/entities/export-transaction.entity.js +2 -1
- package/dist/entities/export-transaction.entity.js.map +1 -1
- package/dist/entities/field-metadata.entity.js +2 -2
- package/dist/entities/field-metadata.entity.js.map +1 -1
- package/dist/entities/import-transaction-error-log.entity.d.ts.map +1 -1
- package/dist/entities/import-transaction-error-log.entity.js +3 -2
- package/dist/entities/import-transaction-error-log.entity.js.map +1 -1
- package/dist/entities/import-transaction.entity.d.ts.map +1 -1
- package/dist/entities/import-transaction.entity.js +2 -1
- package/dist/entities/import-transaction.entity.js.map +1 -1
- package/dist/entities/mq-message-queue.entity.d.ts.map +1 -1
- package/dist/entities/mq-message-queue.entity.js.map +1 -1
- package/dist/entities/mq-message.entity.d.ts.map +1 -1
- package/dist/entities/mq-message.entity.js +5 -3
- package/dist/entities/mq-message.entity.js.map +1 -1
- package/dist/entities/saved-filters.entity.d.ts.map +1 -1
- package/dist/entities/saved-filters.entity.js +3 -2
- package/dist/entities/saved-filters.entity.js.map +1 -1
- package/dist/entities/security-rule.entity.d.ts.map +1 -1
- package/dist/entities/security-rule.entity.js +2 -1
- package/dist/entities/security-rule.entity.js.map +1 -1
- package/dist/entities/sms-template.entity.js +1 -1
- package/dist/entities/sms-template.entity.js.map +1 -1
- package/dist/entities/user-view-metadata.entity.d.ts.map +1 -1
- package/dist/entities/user-view-metadata.entity.js +2 -1
- package/dist/entities/user-view-metadata.entity.js.map +1 -1
- package/dist/entities/user.entity.d.ts.map +1 -1
- package/dist/entities/user.entity.js +2 -0
- package/dist/entities/user.entity.js.map +1 -1
- package/dist/entities/view-metadata.entity.d.ts.map +1 -1
- package/dist/entities/view-metadata.entity.js.map +1 -1
- package/dist/helpers/bootstrap.helper.d.ts +14 -0
- package/dist/helpers/bootstrap.helper.d.ts.map +1 -0
- package/dist/helpers/bootstrap.helper.js +132 -0
- package/dist/helpers/bootstrap.helper.js.map +1 -0
- package/dist/helpers/cache.helper.d.ts +2 -0
- package/dist/helpers/cache.helper.d.ts.map +1 -0
- package/dist/helpers/cache.helper.js +8 -0
- package/dist/helpers/cache.helper.js.map +1 -0
- package/dist/helpers/cors.helper.d.ts.map +1 -1
- package/dist/helpers/cors.helper.js +13 -4
- package/dist/helpers/cors.helper.js.map +1 -1
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts +1 -0
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js +8 -9
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js.map +1 -1
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.d.ts +2 -2
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.js +8 -5
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.js.map +1 -1
- package/dist/helpers/solid-registry.d.ts +3 -0
- package/dist/helpers/solid-registry.d.ts.map +1 -1
- package/dist/helpers/solid-registry.js +7 -0
- package/dist/helpers/solid-registry.js.map +1 -1
- package/dist/helpers/typeorm-db-helper.d.ts.map +1 -1
- package/dist/helpers/typeorm-db-helper.js +21 -0
- package/dist/helpers/typeorm-db-helper.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +2 -0
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/jobs/chatter-queue-options.js +1 -1
- package/dist/jobs/chatter-queue-options.js.map +1 -1
- package/dist/jobs/chatter-queue-publisher.service.d.ts +9 -9
- package/dist/jobs/chatter-queue-publisher.service.d.ts.map +1 -1
- package/dist/jobs/chatter-queue-publisher.service.js +5 -5
- package/dist/jobs/chatter-queue-publisher.service.js.map +1 -1
- package/dist/jobs/chatter-queue-subscriber.service.d.ts +4 -4
- package/dist/jobs/chatter-queue-subscriber.service.d.ts.map +1 -1
- package/dist/jobs/chatter-queue-subscriber.service.js +11 -11
- package/dist/jobs/chatter-queue-subscriber.service.js.map +1 -1
- package/dist/jobs/computed-field-evaluation-queue-options.d.ts +2 -0
- package/dist/jobs/computed-field-evaluation-queue-options.d.ts.map +1 -1
- package/dist/jobs/computed-field-evaluation-queue-options.js +2 -0
- package/dist/jobs/computed-field-evaluation-queue-options.js.map +1 -1
- package/dist/jobs/database/chatter-queue-options-database.d.ts +8 -0
- package/dist/jobs/database/chatter-queue-options-database.d.ts.map +1 -0
- package/dist/jobs/database/chatter-queue-options-database.js +10 -0
- package/dist/jobs/database/chatter-queue-options-database.js.map +1 -0
- package/dist/jobs/database/chatter-queue-publisher-database.service.d.ts +12 -0
- package/dist/jobs/database/chatter-queue-publisher-database.service.d.ts.map +1 -0
- package/dist/jobs/database/chatter-queue-publisher-database.service.js +39 -0
- package/dist/jobs/database/chatter-queue-publisher-database.service.js.map +1 -0
- package/dist/jobs/database/chatter-queue-subscriber-database.service.d.ts +19 -0
- package/dist/jobs/database/chatter-queue-subscriber-database.service.d.ts.map +1 -0
- package/dist/jobs/database/chatter-queue-subscriber-database.service.js +62 -0
- package/dist/jobs/database/chatter-queue-subscriber-database.service.js.map +1 -0
- package/dist/repository/dashboard-layout.repository.d.ts +12 -0
- package/dist/repository/dashboard-layout.repository.d.ts.map +1 -0
- package/dist/repository/dashboard-layout.repository.js +34 -0
- package/dist/repository/dashboard-layout.repository.js.map +1 -0
- package/dist/repository/model-metadata.repository.d.ts +6 -1
- package/dist/repository/model-metadata.repository.d.ts.map +1 -1
- package/dist/repository/model-metadata.repository.js +41 -2
- package/dist/repository/model-metadata.repository.js.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +4 -4
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +372 -32
- package/dist/services/chatter-message.service.d.ts +4 -4
- package/dist/services/chatter-message.service.d.ts.map +1 -1
- package/dist/services/chatter-message.service.js +33 -9
- package/dist/services/chatter-message.service.js.map +1 -1
- package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.d.ts +7 -3
- package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.d.ts.map +1 -1
- package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.js +61 -22
- package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.js.map +1 -1
- package/dist/services/crud.service.js +1 -1
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/dashboard-layout.service.d.ts +20 -0
- package/dist/services/dashboard-layout.service.d.ts.map +1 -0
- package/dist/services/dashboard-layout.service.js +120 -0
- package/dist/services/dashboard-layout.service.js.map +1 -0
- package/dist/services/dashboard.service.d.ts +2 -0
- package/dist/services/dashboard.service.d.ts.map +1 -1
- package/dist/services/dashboard.service.js +4 -0
- package/dist/services/dashboard.service.js.map +1 -1
- package/dist/services/model-metadata.service.d.ts +3 -1
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +21 -2
- package/dist/services/model-metadata.service.js.map +1 -1
- package/dist/services/permission-metadata.service.d.ts +5 -1
- package/dist/services/permission-metadata.service.d.ts.map +1 -1
- package/dist/services/permission-metadata.service.js +66 -20
- package/dist/services/permission-metadata.service.js.map +1 -1
- package/dist/services/queues/database-subscriber.service.d.ts.map +1 -1
- package/dist/services/queues/database-subscriber.service.js +6 -1
- package/dist/services/queues/database-subscriber.service.js.map +1 -1
- package/dist/services/queues/publisher-factory.service.js +0 -1
- package/dist/services/queues/publisher-factory.service.js.map +1 -1
- package/dist/services/queues/rabbitmq-publisher.service.d.ts +1 -0
- package/dist/services/queues/rabbitmq-publisher.service.d.ts.map +1 -1
- package/dist/services/queues/rabbitmq-publisher.service.js +6 -1
- package/dist/services/queues/rabbitmq-publisher.service.js.map +1 -1
- package/dist/services/queues/rabbitmq-subscriber.service.d.ts +5 -1
- package/dist/services/queues/rabbitmq-subscriber.service.d.ts.map +1 -1
- package/dist/services/queues/rabbitmq-subscriber.service.js +84 -9
- package/dist/services/queues/rabbitmq-subscriber.service.js.map +1 -1
- package/dist/services/request-context.service.d.ts +2 -1
- package/dist/services/request-context.service.d.ts.map +1 -1
- package/dist/services/request-context.service.js.map +1 -1
- package/dist/services/scheduled-jobs/scheduler.service.d.ts.map +1 -1
- package/dist/services/scheduled-jobs/scheduler.service.js +20 -2
- package/dist/services/scheduled-jobs/scheduler.service.js.map +1 -1
- package/dist/services/solid-introspect.service.d.ts +6 -1
- package/dist/services/solid-introspect.service.d.ts.map +1 -1
- package/dist/services/solid-introspect.service.js +27 -2
- package/dist/services/solid-introspect.service.js.map +1 -1
- package/dist/services/solid-ts-morph.service.d.ts +9 -0
- package/dist/services/solid-ts-morph.service.d.ts.map +1 -1
- package/dist/services/solid-ts-morph.service.js +76 -0
- package/dist/services/solid-ts-morph.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +16 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist/subscribers/audit.subscriber.d.ts +10 -7
- package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
- package/dist/subscribers/audit.subscriber.js +58 -85
- package/dist/subscribers/audit.subscriber.js.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.js +3 -1
- package/dist/subscribers/computed-entity-field.subscriber.js.map +1 -1
- package/dist/subscribers/created-by-updated-by.subscriber.d.ts +0 -1
- package/dist/subscribers/created-by-updated-by.subscriber.d.ts.map +1 -1
- package/dist/subscribers/created-by-updated-by.subscriber.js +3 -13
- package/dist/subscribers/created-by-updated-by.subscriber.js.map +1 -1
- package/dist/winston.logger.d.ts.map +1 -1
- package/dist/winston.logger.js +2 -1
- package/dist/winston.logger.js.map +1 -1
- package/package.json +3 -1
- package/sql/default/mariadb/proc_CleanupModelMetadata.sql +153 -0
- package/sql/default/mariadb/proc_CleanupModuleMetadata.sql +56 -0
- package/sql/default/mysql/proc_CleanupModelMetadata.sql +153 -0
- package/sql/default/mysql/proc_CleanupModuleMetadata.sql +56 -0
- package/src/controllers/dashboard-layout.controller.ts +106 -0
- package/src/dtos/create-dashboard-layout.dto.ts +31 -0
- package/src/dtos/create-dashboard-variable.dto.ts +4 -0
- package/src/dtos/update-dashboard-layout.dto.ts +30 -0
- package/src/dtos/update-dashboard-variable.dto.ts +5 -1
- package/src/entities/action-metadata.entity.ts +3 -2
- package/src/entities/ai-interaction.entity.ts +5 -4
- package/src/entities/chatter-message-details.entity.ts +4 -3
- package/src/entities/chatter-message.entity.ts +4 -3
- package/src/entities/dashboard-layout.entity.ts +18 -0
- package/src/entities/dashboard-question-sql-dataset-config.entity.ts +5 -4
- package/src/entities/dashboard-question.entity.ts +5 -4
- package/src/entities/dashboard-variable.entity.ts +9 -4
- package/src/entities/dashboard.entity.ts +7 -2
- package/src/entities/email-attachment.entity.ts +2 -1
- package/src/entities/email-template.entity.ts +1 -1
- package/src/entities/export-transaction.entity.ts +2 -1
- package/src/entities/field-metadata.entity.ts +2 -2
- package/src/entities/import-transaction-error-log.entity.ts +3 -2
- package/src/entities/import-transaction.entity.ts +2 -1
- package/src/entities/mq-message-queue.entity.ts +8 -8
- package/src/entities/mq-message.entity.ts +5 -3
- package/src/entities/saved-filters.entity.ts +3 -2
- package/src/entities/security-rule.entity.ts +2 -1
- package/src/entities/sms-template.entity.ts +1 -1
- package/src/entities/user-view-metadata.entity.ts +2 -1
- package/src/entities/user.entity.ts +37 -2
- package/src/entities/view-metadata.entity.ts +3 -0
- package/src/helpers/bootstrap.helper.ts +222 -0
- package/src/helpers/cache.helper.ts +5 -0
- package/src/helpers/cors.helper.ts +26 -6
- package/src/helpers/field-crud-managers/MediaFieldCrudManager.ts +9 -9
- package/src/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.ts +9 -6
- package/src/helpers/solid-registry.ts +10 -5
- package/src/helpers/typeorm-db-helper.ts +26 -0
- package/src/index.ts +3 -0
- package/src/interfaces.ts +3 -0
- package/src/jobs/chatter-queue-options.ts +1 -1
- package/src/jobs/chatter-queue-publisher.service.ts +11 -11
- package/src/jobs/chatter-queue-subscriber.service.ts +13 -8
- package/src/jobs/computed-field-evaluation-queue-options.ts +2 -0
- package/src/jobs/database/chatter-queue-options-database.ts +9 -0
- package/src/jobs/database/chatter-queue-publisher-database.service.ts +24 -0
- package/src/jobs/database/chatter-queue-subscriber-database.service.ts +53 -0
- package/src/repository/dashboard-layout.repository.ts +17 -0
- package/src/repository/model-metadata.repository.ts +45 -2
- package/src/seeders/module-metadata-seeder.service.ts +5 -5
- package/src/seeders/seed-data/solid-core-metadata.json +373 -33
- package/src/services/1.js +6 -0
- package/src/services/chatter-message.service.ts +41 -9
- package/src/services/computed-fields/entity/sequence-num-computed-field-provider.ts +79 -40
- package/src/services/crud.service.ts +1 -1
- package/src/services/dashboard-layout.service.ts +111 -0
- package/src/services/dashboard.service.ts +7 -0
- package/src/services/model-metadata.service.ts +22 -43
- package/src/services/permission-metadata.service.ts +73 -20
- package/src/services/queues/database-subscriber.service.ts +7 -1
- package/src/services/queues/publisher-factory.service.ts +1 -1
- package/src/services/queues/rabbitmq-publisher.service.ts +8 -2
- package/src/services/queues/rabbitmq-subscriber.service.ts +127 -10
- package/src/services/request-context.service.ts +2 -1
- package/src/services/scheduled-jobs/scheduler.service.ts +22 -4
- package/src/services/solid-introspect.service.ts +28 -0
- package/src/services/solid-ts-morph.service.ts +98 -0
- package/src/solid-core.module.ts +21 -2
- package/src/subscribers/audit.subscriber.ts +63 -271
- package/src/subscribers/computed-entity-field.subscriber.ts +3 -3
- package/src/subscribers/created-by-updated-by.subscriber.ts +22 -16
- package/src/winston.logger.ts +2 -1
- package/dist-tests/api/authenticate.spec.js +0 -119
- package/dist-tests/api/authenticate.spec.js.map +0 -1
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +0 -97
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +0 -1
- package/dist-tests/api/ping.spec.js +0 -21
- package/dist-tests/api/ping.spec.js.map +0 -1
- package/dist-tests/helpers/auth.js +0 -41
- package/dist-tests/helpers/auth.js.map +0 -1
- package/dist-tests/helpers/env.js +0 -11
- package/dist-tests/helpers/env.js.map +0 -1
|
@@ -1,32 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ModelMetadataHelperService } from 'src/helpers/model-metadata-helper.service';
|
|
1
|
+
import { Injectable, Logger, Scope } from '@nestjs/common';
|
|
3
2
|
import { lowerFirst } from 'src/helpers/string.helper';
|
|
4
|
-
import {
|
|
3
|
+
import { SolidRegistry } from 'src/helpers/solid-registry';
|
|
5
4
|
import { DataSource, EntityMetadata, EntitySubscriberInterface, InsertEvent, RemoveEvent, UpdateEvent } from 'typeorm';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
type DeferredCall =
|
|
10
|
-
| { kind: 'insert'; args: Parameters<ChatterMessageService['postAuditMessageOnInsert']> }
|
|
11
|
-
| { kind: 'update'; args: Parameters<ChatterMessageService['postAuditMessageOnUpdate']> }
|
|
12
|
-
| { kind: 'delete'; args: Parameters<ChatterMessageService['postAuditMessageOnDelete']> };
|
|
5
|
+
import { AuditQueuePayload } from 'src/jobs/chatter-queue-publisher.service';
|
|
6
|
+
import { RequestContextService } from 'src/services/request-context.service';
|
|
7
|
+
import { PublisherFactory } from 'src/services/queues/publisher-factory.service';
|
|
13
8
|
|
|
14
9
|
@Injectable({scope: Scope.TRANSIENT})
|
|
15
|
-
// @EventSubscriber()
|
|
16
10
|
export class AuditSubscriber implements EntitySubscriberInterface {
|
|
11
|
+
private readonly logger = new Logger(AuditSubscriber.name);
|
|
17
12
|
private dataSource: DataSource;
|
|
18
13
|
constructor(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
private readonly
|
|
22
|
-
|
|
23
|
-
// private readonly modelMetadataRepo: Repository<ModelMetadata>,
|
|
24
|
-
@Inject(forwardRef(() => ModelMetadataRepository))
|
|
25
|
-
private readonly modelMetadataRepo: ModelMetadataRepository,
|
|
26
|
-
private readonly modelMetadataHelperService: ModelMetadataHelperService,
|
|
27
|
-
) {
|
|
28
|
-
// this.dataSource.subscribers.push(this);
|
|
29
|
-
}
|
|
14
|
+
private readonly publisherFactory: PublisherFactory<AuditQueuePayload>,
|
|
15
|
+
private readonly solidRegistry: SolidRegistry,
|
|
16
|
+
private readonly requestContextService: RequestContextService,
|
|
17
|
+
) { }
|
|
30
18
|
|
|
31
19
|
bindToDataSource(dataSource: DataSource) {
|
|
32
20
|
this.dataSource = dataSource;
|
|
@@ -34,90 +22,61 @@ export class AuditSubscriber implements EntitySubscriberInterface {
|
|
|
34
22
|
}
|
|
35
23
|
|
|
36
24
|
// Per-transaction buffer (auto-GC when queryRunner is gone)
|
|
37
|
-
private perTxn = new WeakMap<any,
|
|
25
|
+
private perTxn = new WeakMap<any, AuditQueuePayload[]>();
|
|
38
26
|
|
|
39
|
-
private enqueue(event: { queryRunner: any },
|
|
27
|
+
private enqueue(event: { queryRunner: any }, payload: AuditQueuePayload) {
|
|
40
28
|
const qr = event.queryRunner;
|
|
41
29
|
const arr = this.perTxn.get(qr) ?? [];
|
|
42
|
-
arr.push(
|
|
30
|
+
arr.push(payload);
|
|
43
31
|
this.perTxn.set(qr, arr);
|
|
44
32
|
}
|
|
45
33
|
|
|
46
|
-
private
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
singularName: lowerFirst(metadata.name)
|
|
50
|
-
},
|
|
51
|
-
relations: {
|
|
52
|
-
fields: true,
|
|
53
|
-
module: true
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
if (!model || !model.enableAuditTracking) {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const modelFields = await this.modelMetadataHelperService.loadFieldHierarchy(model.singularName)
|
|
62
|
-
|
|
63
|
-
const auditFields = modelFields.filter(field =>
|
|
64
|
-
field.enableAuditTracking &&
|
|
65
|
-
!['mediaSingle', 'mediaMultiple', 'richText', 'json'].includes(field.type) &&
|
|
66
|
-
!(field.type === 'relation' && field.relationType === 'one-to-many')
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
if (auditFields.length === 0) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// if (!entity) {
|
|
74
|
-
// console.warn(`[AuditSubscriber] Skipping audit for ${metadata.name} – entity is undefined or null`);
|
|
75
|
-
// return false;
|
|
76
|
-
// }
|
|
34
|
+
private shouldTrackAudit(metadata: EntityMetadata): boolean {
|
|
35
|
+
return this.solidRegistry.isAuditableModel(lowerFirst(metadata.name));
|
|
36
|
+
}
|
|
77
37
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return fieldValue !== undefined && fieldValue !== null;
|
|
81
|
-
});
|
|
38
|
+
private activeUserId(): number | null {
|
|
39
|
+
return this.requestContextService.getActiveUser()?.sub ?? null;
|
|
82
40
|
}
|
|
83
41
|
|
|
84
42
|
async afterInsert(event: InsertEvent<any>) {
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
43
|
+
if (!this.shouldTrackAudit(event.metadata)) return;
|
|
44
|
+
this.enqueue(event, {
|
|
45
|
+
eventType: 'insert',
|
|
46
|
+
modelName: event.metadata.name,
|
|
47
|
+
entityId: event.entity?.id ?? null,
|
|
48
|
+
occurredAt: new Date().toISOString(),
|
|
49
|
+
after: event.entity ?? null,
|
|
50
|
+
userId: this.activeUserId(),
|
|
51
|
+
});
|
|
92
52
|
}
|
|
93
53
|
|
|
94
54
|
async afterUpdate(event: UpdateEvent<any>) {
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
55
|
+
if (!this.shouldTrackAudit(event.metadata)) return;
|
|
56
|
+
this.enqueue(event, {
|
|
57
|
+
eventType: 'update',
|
|
58
|
+
modelName: event.metadata.name,
|
|
59
|
+
entityId: event.entity?.id ?? null,
|
|
60
|
+
occurredAt: new Date().toISOString(),
|
|
61
|
+
after: event.entity ?? null,
|
|
62
|
+
// databaseEntity is only populated when the entity was fetched first (save() path).
|
|
63
|
+
// QueryBuilder update() leaves this undefined; postAuditMessageOnUpdate guards for it.
|
|
64
|
+
before: event.databaseEntity ?? null,
|
|
65
|
+
updatedColumnNames: (event.updatedColumns ?? []).map(c => c.propertyName),
|
|
66
|
+
userId: this.activeUserId(),
|
|
67
|
+
});
|
|
107
68
|
}
|
|
108
69
|
|
|
109
70
|
async afterRemove(event: RemoveEvent<any>) {
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
});
|
|
120
|
-
}
|
|
71
|
+
if (!this.shouldTrackAudit(event.metadata)) return;
|
|
72
|
+
this.enqueue(event, {
|
|
73
|
+
eventType: 'delete',
|
|
74
|
+
modelName: event.metadata.name,
|
|
75
|
+
entityId: event.databaseEntity?.id ?? null,
|
|
76
|
+
occurredAt: new Date().toISOString(),
|
|
77
|
+
before: event.databaseEntity,
|
|
78
|
+
userId: this.activeUserId(),
|
|
79
|
+
});
|
|
121
80
|
}
|
|
122
81
|
|
|
123
82
|
// --------- transaction lifecycle ----------
|
|
@@ -125,191 +84,24 @@ export class AuditSubscriber implements EntitySubscriberInterface {
|
|
|
125
84
|
const batch = this.perTxn.get(event.queryRunner) ?? [];
|
|
126
85
|
this.perTxn.delete(event.queryRunner);
|
|
127
86
|
|
|
128
|
-
// Now
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
87
|
+
// Now outside the DB transaction — safe to publish to the queue.
|
|
88
|
+
// allSettled: publish in parallel; a single failure does not block the rest.
|
|
89
|
+
const results = await Promise.allSettled(
|
|
90
|
+
batch.map(payload => this.publisherFactory.publish({ payload }, 'ChatterQueuePublisher'))
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
results.forEach((result, i) => {
|
|
94
|
+
if (result.status === 'rejected') {
|
|
95
|
+
this.logger.error(
|
|
96
|
+
`Failed to publish audit event for ${batch[i].modelName}#${batch[i].entityId}`,
|
|
97
|
+
result.reason,
|
|
98
|
+
);
|
|
139
99
|
}
|
|
140
|
-
}
|
|
100
|
+
});
|
|
141
101
|
}
|
|
142
102
|
|
|
143
103
|
afterTransactionRollback(event: { queryRunner: any }) {
|
|
144
|
-
// Drop buffered
|
|
104
|
+
// Drop buffered payloads; the write never happened.
|
|
145
105
|
this.perTxn.delete(event.queryRunner);
|
|
146
106
|
}
|
|
147
107
|
}
|
|
148
|
-
|
|
149
|
-
// import { DataSource, EntityMetadata, EntitySubscriberInterface, EventSubscriber, InsertEvent, RemoveEvent, UpdateEvent } from 'typeorm';
|
|
150
|
-
// import { Injectable } from '@nestjs/common';
|
|
151
|
-
// import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
|
152
|
-
// import { Repository } from 'typeorm';
|
|
153
|
-
// import { ModelMetadata } from '../entities/model-metadata.entity';
|
|
154
|
-
// import { lowerFirst } from 'src/helpers/string.helper';
|
|
155
|
-
// import { ModelMetadataHelperService } from 'src/helpers/model-metadata-helper.service';
|
|
156
|
-
// import { ChatterMessagePayload } from 'src/jobs/chatter-queue-publisher.service';
|
|
157
|
-
// import { RequestContextService } from 'src/services/request-context.service';
|
|
158
|
-
// import { PublisherFactory } from 'src/services/queues/publisher-factory.service';
|
|
159
|
-
|
|
160
|
-
// @EventSubscriber()
|
|
161
|
-
// @Injectable()
|
|
162
|
-
// export class AuditSubscriber implements EntitySubscriberInterface {
|
|
163
|
-
// private perTxn = new WeakMap<any, ChatterMessagePayload[]>();
|
|
164
|
-
|
|
165
|
-
// constructor(
|
|
166
|
-
// @InjectDataSource() private readonly dataSource: DataSource,
|
|
167
|
-
// @InjectRepository(ModelMetadata) private readonly modelMetadataRepo: Repository<ModelMetadata>,
|
|
168
|
-
// private readonly modelMetadataHelperService: ModelMetadataHelperService,
|
|
169
|
-
// private readonly requestContext: RequestContextService,
|
|
170
|
-
// private readonly publisherFactory: PublisherFactory<any>
|
|
171
|
-
// ) {
|
|
172
|
-
// this.dataSource.subscribers.push(this);
|
|
173
|
-
// }
|
|
174
|
-
|
|
175
|
-
// // --- small cache to avoid metadata queries on every row ---
|
|
176
|
-
// private modelCache = new Map<string, { enable: boolean; fields: Array<{ name: string; enableAuditTracking: boolean; type: string; relationType?: string }>; ts: number }>();
|
|
177
|
-
// private cacheTTLms = 60_000;
|
|
178
|
-
|
|
179
|
-
// private async shouldTrackAudit(entity: any, metadata: EntityMetadata): Promise<{ enable: boolean; auditFields?: string[] }> {
|
|
180
|
-
// const key = metadata.name;
|
|
181
|
-
// const now = Date.now();
|
|
182
|
-
// const cached = this.modelCache.get(key);
|
|
183
|
-
// if (cached && (now - cached.ts) < this.cacheTTLms) {
|
|
184
|
-
// if (!cached.enable) return { enable: false };
|
|
185
|
-
// const fields = cached.fields.filter(f =>
|
|
186
|
-
// f.enableAuditTracking &&
|
|
187
|
-
// !['mediaSingle', 'mediaMultiple', 'computed', 'richText', 'json'].includes(f.type) &&
|
|
188
|
-
// !(f.type === 'relation' && f.relationType === 'one-to-many')
|
|
189
|
-
// );
|
|
190
|
-
// const present = fields.map(f => f.name).filter(n => entity?.[n] !== undefined);
|
|
191
|
-
// return { enable: present.length > 0, auditFields: present };
|
|
192
|
-
// }
|
|
193
|
-
|
|
194
|
-
// const model = await this.modelMetadataRepo.findOne({
|
|
195
|
-
// where: { singularName: lowerFirst(metadata.name) },
|
|
196
|
-
// relations: { fields: true, module: true },
|
|
197
|
-
// });
|
|
198
|
-
// const enable = !!model?.enableAuditTracking;
|
|
199
|
-
// const fields = model?.fields ?? [];
|
|
200
|
-
// this.modelCache.set(key, { enable, fields, ts: now });
|
|
201
|
-
|
|
202
|
-
// if (!enable) return { enable: false };
|
|
203
|
-
// const filtered = fields.filter(f =>
|
|
204
|
-
// f.enableAuditTracking &&
|
|
205
|
-
// !['mediaSingle', 'mediaMultiple', 'computed', 'richText', 'json'].includes(f.type) &&
|
|
206
|
-
// !(f.type === 'relation' && f.relationType === 'one-to-many')
|
|
207
|
-
// );
|
|
208
|
-
// const present = filtered.map(f => f.name).filter(n => entity?.[n] !== undefined);
|
|
209
|
-
// return { enable: present.length > 0, auditFields: present };
|
|
210
|
-
// }
|
|
211
|
-
|
|
212
|
-
// private push(event: { queryRunner: any }, msg: ChatterMessagePayload) {
|
|
213
|
-
// const arr = this.perTxn.get(event.queryRunner) ?? [];
|
|
214
|
-
// arr.push(msg);
|
|
215
|
-
// this.perTxn.set(event.queryRunner, arr);
|
|
216
|
-
// }
|
|
217
|
-
|
|
218
|
-
// async afterInsert(event: InsertEvent<any>) {
|
|
219
|
-
// if (!event.entity) return;
|
|
220
|
-
// const enable = await this.shouldTrackAudit(event.entity, event.metadata);
|
|
221
|
-
// if (!enable) return;
|
|
222
|
-
|
|
223
|
-
// const payload: ChatterMessagePayload = {
|
|
224
|
-
// eventType: 'insert',
|
|
225
|
-
// model: event.metadata.name,
|
|
226
|
-
// entityId: String(event.entity.id ?? event.entity.uuid ?? ''),
|
|
227
|
-
// occurredAt: new Date().toISOString(),
|
|
228
|
-
// after: this.safeCopy(event.entity),
|
|
229
|
-
// userId: this.getUserId(),
|
|
230
|
-
// };
|
|
231
|
-
// this.push(event, payload);
|
|
232
|
-
// }
|
|
233
|
-
|
|
234
|
-
// async afterUpdate(event: UpdateEvent<any>) {
|
|
235
|
-
// // Updated entity may be null if you used raw query; fall back to databaseEntity
|
|
236
|
-
// const current = event.entity ?? {};
|
|
237
|
-
// const before = event.databaseEntity ?? {};
|
|
238
|
-
// const { enable, auditFields } = await this.shouldTrackAudit(current, event.metadata);
|
|
239
|
-
// if (!enable) return;
|
|
240
|
-
|
|
241
|
-
// const changedCols = (event.updatedColumns || []).map(c => c.propertyName);
|
|
242
|
-
// const payload: ChatterMessagePayload = {
|
|
243
|
-
// eventType: 'update',
|
|
244
|
-
// model: event.metadata.name,
|
|
245
|
-
// entityId: String((current as any).id ?? (before as any).id ?? ''),
|
|
246
|
-
// occurredAt: new Date().toISOString(),
|
|
247
|
-
// before: this.pick(before, auditFields || changedCols),
|
|
248
|
-
// after: this.pick(current, auditFields || changedCols),
|
|
249
|
-
// diff: changedCols,
|
|
250
|
-
// userId: this.getUserId(),
|
|
251
|
-
// };
|
|
252
|
-
// this.push(event, payload);
|
|
253
|
-
// }
|
|
254
|
-
|
|
255
|
-
// async afterRemove(event: RemoveEvent<any>) {
|
|
256
|
-
// const base = event.entity ?? event.databaseEntity;
|
|
257
|
-
// if (!base) return;
|
|
258
|
-
|
|
259
|
-
// const { enable } = await this.shouldTrackAudit(base, event.metadata);
|
|
260
|
-
// if (!enable) return;
|
|
261
|
-
|
|
262
|
-
// const payload: ChatterMessagePayload = {
|
|
263
|
-
// eventType: 'delete',
|
|
264
|
-
// model: event.metadata.name,
|
|
265
|
-
// entityId: String((base as any).id ?? ''),
|
|
266
|
-
// occurredAt: new Date().toISOString(),
|
|
267
|
-
// before: this.safeCopy(base),
|
|
268
|
-
// userId: this.getUserId(),
|
|
269
|
-
// };
|
|
270
|
-
// this.push(event, payload);
|
|
271
|
-
// }
|
|
272
|
-
|
|
273
|
-
// // Publish AFTER the transaction commits -> no idle-in-transaction
|
|
274
|
-
// async afterTransactionCommit(event: { queryRunner: any }) {
|
|
275
|
-
// const batch = this.perTxn.get(event.queryRunner) ?? [];
|
|
276
|
-
// this.perTxn.delete(event.queryRunner);
|
|
277
|
-
// for (const msg of batch) {
|
|
278
|
-
// try {
|
|
279
|
-
// await this.publisherFactory.publish({ payload: msg, parentEntity: msg.model, parentEntityId: msg.entityId }, 'ChatterQueuePublisher');
|
|
280
|
-
// } catch (err) {
|
|
281
|
-
// // log + optionally send to a DLQ or retry queue
|
|
282
|
-
// // do NOT throw; commit already happened
|
|
283
|
-
// // your RabbitMqPublisher likely tracks failures in MqMessage tables anyway
|
|
284
|
-
// }
|
|
285
|
-
// }
|
|
286
|
-
// }
|
|
287
|
-
|
|
288
|
-
// afterTransactionRollback(event: { queryRunner: any }) {
|
|
289
|
-
// this.perTxn.delete(event.queryRunner);
|
|
290
|
-
// }
|
|
291
|
-
|
|
292
|
-
// // --- small helpers to keep payloads JSON-safe and small ---
|
|
293
|
-
// private safeCopy(obj: any) {
|
|
294
|
-
// try {
|
|
295
|
-
// return JSON.parse(JSON.stringify(obj));
|
|
296
|
-
// } catch {
|
|
297
|
-
// return {}; // strip circular refs
|
|
298
|
-
// }
|
|
299
|
-
// }
|
|
300
|
-
|
|
301
|
-
// private pick(obj: any, keys: string[]) {
|
|
302
|
-
// const out: any = {};
|
|
303
|
-
// for (const k of keys) out[k] = obj?.[k];
|
|
304
|
-
// return this.safeCopy(out);
|
|
305
|
-
// }
|
|
306
|
-
|
|
307
|
-
// private getUserId(): string | null {
|
|
308
|
-
|
|
309
|
-
// const activeUser = this.requestContext.getActiveUser();
|
|
310
|
-
// if (activeUser?.sub)
|
|
311
|
-
// return String(activeUser.sub);
|
|
312
|
-
// }
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
// }
|
|
@@ -78,9 +78,9 @@ export class ComputedEntityFieldSubscriber implements EntitySubscriberInterface
|
|
|
78
78
|
modelName
|
|
79
79
|
);
|
|
80
80
|
//TODO: We can add a feature i.e dependsOn, where we can check if the computed field depends on other computed fields and evaluate them first
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
for (const computedField of computedFieldsTobeEvaluated) {
|
|
82
|
+
await this.evaluateComputedField(this.attachContext(computedField, eventContext), entity, currentOperation);
|
|
83
|
+
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
private handleComputedFieldEvaluationJob(entity: any, currentOperation: ComputedFieldTriggerOperation, modelName: string, eventContext?: TypeOrmEventContext) {
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { Injectable, Scope } from "@nestjs/common";
|
|
2
2
|
import { InjectDataSource } from "@nestjs/typeorm";
|
|
3
|
-
import { User } from "src/entities/user.entity";
|
|
4
|
-
import { ActiveUserData } from "src/interfaces/active-user-data.interface";
|
|
5
3
|
import { RequestContextService } from "src/services/request-context.service";
|
|
6
|
-
import { DataSource, EntitySubscriberInterface,
|
|
4
|
+
import { DataSource, EntitySubscriberInterface, InsertEvent, UpdateEvent } from "typeorm";
|
|
7
5
|
|
|
8
|
-
@Injectable({scope: Scope.TRANSIENT})
|
|
6
|
+
@Injectable({ scope: Scope.TRANSIENT })
|
|
9
7
|
// @EventSubscriber()
|
|
10
8
|
export class CreatedByUpdatedBySubscriber implements EntitySubscriberInterface {
|
|
11
9
|
private dataSource: DataSource;
|
|
@@ -30,7 +28,7 @@ export class CreatedByUpdatedBySubscriber implements EntitySubscriberInterface {
|
|
|
30
28
|
await this.stampUserField(event, false);
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
private async stampUserField(event: InsertEvent<any> | UpdateEvent<any>, isInsert: boolean){
|
|
31
|
+
private async stampUserField(event: InsertEvent<any> | UpdateEvent<any>, isInsert: boolean) {
|
|
34
32
|
if (!event.entity) {
|
|
35
33
|
return;
|
|
36
34
|
}
|
|
@@ -40,21 +38,29 @@ export class CreatedByUpdatedBySubscriber implements EntitySubscriberInterface {
|
|
|
40
38
|
return;
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
const loadedUser = await this.loadUser(activeUserOrUndefined as unknown as ActiveUserData);
|
|
41
|
+
// const loadedUser = await this.loadUser(activeUserOrUndefined as unknown as ActiveUserData);
|
|
42
|
+
// if (isInsert) {
|
|
43
|
+
// event.entity.createdBy = loadedUser?.id;
|
|
44
|
+
// event.entity.updatedBy = loadedUser?.id; // For insert, we set both createdBy and updatedBy to the same user
|
|
45
|
+
// }
|
|
46
|
+
// else {
|
|
47
|
+
// event.entity.updatedBy = loadedUser?.id;
|
|
48
|
+
// }
|
|
49
|
+
|
|
44
50
|
if (isInsert) {
|
|
45
|
-
event.entity.createdBy =
|
|
46
|
-
event.entity.updatedBy =
|
|
51
|
+
event.entity.createdBy = activeUserOrUndefined?.sub;
|
|
52
|
+
event.entity.updatedBy = activeUserOrUndefined?.sub; // For insert, we set both createdBy and updatedBy to the same user
|
|
47
53
|
}
|
|
48
54
|
else {
|
|
49
|
-
event.entity.updatedBy =
|
|
55
|
+
event.entity.updatedBy = activeUserOrUndefined?.sub;
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
|
|
53
|
-
private async loadUser(activeUser: ActiveUserData): Promise<User> {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
59
|
+
// private async loadUser(activeUser: ActiveUserData): Promise<User> {
|
|
60
|
+
// const userRepo = this.defaultDataSource.getRepository(User); // Assuming 'User' is the entity name for users in your application
|
|
61
|
+
// const loadedUser = await userRepo.findOne({
|
|
62
|
+
// where: { id: activeUser.sub }, // Assuming 'sub' is the user ID in the JWT token
|
|
63
|
+
// });
|
|
64
|
+
// return loadedUser;;
|
|
65
|
+
// }
|
|
60
66
|
}
|
package/src/winston.logger.ts
CHANGED
|
@@ -4,9 +4,10 @@ import { Logger } from 'winston';
|
|
|
4
4
|
import { Inject } from '@nestjs/common';
|
|
5
5
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
|
6
6
|
import * as winston from 'winston';
|
|
7
|
+
import { Environment } from './decorators/disallow-in-production.decorator';
|
|
7
8
|
|
|
8
9
|
export const WinstonLoggerConfig = {
|
|
9
|
-
level:
|
|
10
|
+
level: process.env.LOG_LEVEL || (process.env.ENV === Environment.Production ? 'warn' : 'debug'),
|
|
10
11
|
format: winston.format.combine(
|
|
11
12
|
winston.format.timestamp(),
|
|
12
13
|
winston.format.errors({ stack: true }),
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const test_1 = require("@playwright/test");
|
|
4
|
-
const env_1 = require("../helpers/env");
|
|
5
|
-
const baseURL = process.env.API_BASE_URL ?? "http://localhost:3000";
|
|
6
|
-
const TEST_USER_EMAIL = (0, env_1.getRequiredEnv)("TEST_USER_EMAIL");
|
|
7
|
-
const TEST_USER_PASSWORD = (0, env_1.getRequiredEnv)("TEST_USER_PASSWORD");
|
|
8
|
-
function base64UrlDecode(input) {
|
|
9
|
-
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
10
|
-
const padded = normalized.length % 4 === 0
|
|
11
|
-
? normalized
|
|
12
|
-
: normalized.padEnd(normalized.length + (4 - (normalized.length % 4)), "=");
|
|
13
|
-
return Buffer.from(padded, "base64").toString("utf-8");
|
|
14
|
-
}
|
|
15
|
-
function validateJwt(token) {
|
|
16
|
-
const parts = token.split(".");
|
|
17
|
-
if (parts.length !== 3) {
|
|
18
|
-
throw new Error("JWT must have three dot-separated parts.");
|
|
19
|
-
}
|
|
20
|
-
const headerJson = JSON.parse(base64UrlDecode(parts[0]));
|
|
21
|
-
const payloadJson = JSON.parse(base64UrlDecode(parts[1]));
|
|
22
|
-
if (!headerJson || typeof headerJson !== "object") {
|
|
23
|
-
throw new Error("JWT header must be a JSON object.");
|
|
24
|
-
}
|
|
25
|
-
if (!payloadJson || typeof payloadJson !== "object") {
|
|
26
|
-
throw new Error("JWT payload must be a JSON object.");
|
|
27
|
-
}
|
|
28
|
-
if (typeof payloadJson.exp !== "number") {
|
|
29
|
-
throw new Error("JWT payload.exp must be a number.");
|
|
30
|
-
}
|
|
31
|
-
return payloadJson;
|
|
32
|
-
}
|
|
33
|
-
(0, test_1.test)("API: authenticate succeeds with valid credentials", async () => {
|
|
34
|
-
const api = await test_1.request.newContext({
|
|
35
|
-
baseURL,
|
|
36
|
-
extraHTTPHeaders: {
|
|
37
|
-
accept: "*/*",
|
|
38
|
-
"content-type": "application/json",
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
try {
|
|
42
|
-
const res = await api.post("/api/iam/authenticate", {
|
|
43
|
-
data: {
|
|
44
|
-
email: TEST_USER_EMAIL,
|
|
45
|
-
username: "",
|
|
46
|
-
password: TEST_USER_PASSWORD,
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
(0, test_1.expect)(res.status()).toBe(200);
|
|
50
|
-
const json = await res.json();
|
|
51
|
-
(0, test_1.expect)(json.statusCode).toBe(200);
|
|
52
|
-
(0, test_1.expect)(Array.isArray(json.message)).toBe(true);
|
|
53
|
-
(0, test_1.expect)(json.message.length).toBe(0);
|
|
54
|
-
(0, test_1.expect)(json.error).toBe("");
|
|
55
|
-
const user = json.data?.user;
|
|
56
|
-
(0, test_1.expect)(user, "Expected data.user to be an object.").toBeTruthy();
|
|
57
|
-
(0, test_1.expect)(typeof user).toBe("object");
|
|
58
|
-
const email = user?.email;
|
|
59
|
-
(0, test_1.expect)(typeof email).toBe("string");
|
|
60
|
-
if (email === TEST_USER_EMAIL) {
|
|
61
|
-
(0, test_1.expect)(email).toBe(TEST_USER_EMAIL);
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
(0, test_1.expect)(email.length).toBeGreaterThan(0);
|
|
65
|
-
}
|
|
66
|
-
(0, test_1.expect)(typeof user?.mobile).toBe("string");
|
|
67
|
-
(0, test_1.expect)(typeof user?.username).toBe("string");
|
|
68
|
-
(0, test_1.expect)(typeof user?.forcePasswordChange).toBe("boolean");
|
|
69
|
-
(0, test_1.expect)(typeof user?.id).toBe("number");
|
|
70
|
-
const roles = user?.roles;
|
|
71
|
-
(0, test_1.expect)(Array.isArray(roles)).toBe(true);
|
|
72
|
-
if (Array.isArray(roles)) {
|
|
73
|
-
(0, test_1.expect)(roles.every((role) => typeof role === "string")).toBe(true);
|
|
74
|
-
(0, test_1.expect)(roles).toContain("Admin");
|
|
75
|
-
}
|
|
76
|
-
const accessToken = json.data?.accessToken;
|
|
77
|
-
const refreshToken = json.data?.refreshToken;
|
|
78
|
-
(0, test_1.expect)(typeof accessToken).toBe("string");
|
|
79
|
-
(0, test_1.expect)(typeof refreshToken).toBe("string");
|
|
80
|
-
const accessPayload = validateJwt(accessToken);
|
|
81
|
-
const refreshPayload = validateJwt(refreshToken);
|
|
82
|
-
(0, test_1.expect)(typeof accessPayload.exp).toBe("number");
|
|
83
|
-
(0, test_1.expect)(typeof refreshPayload.exp).toBe("number");
|
|
84
|
-
}
|
|
85
|
-
finally {
|
|
86
|
-
await api.dispose();
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
(0, test_1.test)("API: authenticate fails with wrong password", async () => {
|
|
90
|
-
const api = await test_1.request.newContext({
|
|
91
|
-
baseURL,
|
|
92
|
-
extraHTTPHeaders: {
|
|
93
|
-
accept: "*/*",
|
|
94
|
-
"content-type": "application/json",
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
try {
|
|
98
|
-
const res = await api.post("/api/iam/authenticate", {
|
|
99
|
-
data: {
|
|
100
|
-
email: TEST_USER_EMAIL,
|
|
101
|
-
username: "",
|
|
102
|
-
password: `${TEST_USER_PASSWORD}__wrong`,
|
|
103
|
-
},
|
|
104
|
-
});
|
|
105
|
-
(0, test_1.expect)(res.status()).toBe(401);
|
|
106
|
-
const json = await res.json();
|
|
107
|
-
(0, test_1.expect)(json.statusCode).toBe(401);
|
|
108
|
-
(0, test_1.expect)(json.statusCodeMessage).toBe("Unauthorized");
|
|
109
|
-
(0, test_1.expect)(json.message).toBe("Invalid credentials");
|
|
110
|
-
(0, test_1.expect)(json.error).toBe("Invalid credentials");
|
|
111
|
-
(0, test_1.expect)(json.data?.statusCode).toBe(401);
|
|
112
|
-
(0, test_1.expect)(json.data?.error).toBe("Unauthorized");
|
|
113
|
-
(0, test_1.expect)(json.data?.message).toBe("Invalid credentials");
|
|
114
|
-
}
|
|
115
|
-
finally {
|
|
116
|
-
await api.dispose();
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
//# sourceMappingURL=authenticate.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"authenticate.spec.js","sourceRoot":"","sources":["../../tests/api/authenticate.spec.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, request, test } from \"@playwright/test\";\nimport { getRequiredEnv } from \"../helpers/env\";\n\ntype JwtPayload = {\n exp?: number;\n [key: string]: unknown;\n};\n\nconst baseURL = process.env.API_BASE_URL ?? \"http://localhost:3000\";\nconst TEST_USER_EMAIL = getRequiredEnv(\"TEST_USER_EMAIL\");\nconst TEST_USER_PASSWORD = getRequiredEnv(\"TEST_USER_PASSWORD\");\n\nfunction base64UrlDecode(input: string): string {\n const normalized = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded =\n normalized.length % 4 === 0\n ? normalized\n : normalized.padEnd(\n normalized.length + (4 - (normalized.length % 4)),\n \"=\"\n );\n return Buffer.from(padded, \"base64\").toString(\"utf-8\");\n}\n\nfunction validateJwt(token: string): JwtPayload {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"JWT must have three dot-separated parts.\");\n }\n\n const headerJson = JSON.parse(base64UrlDecode(parts[0]));\n const payloadJson = JSON.parse(base64UrlDecode(parts[1]));\n\n if (!headerJson || typeof headerJson !== \"object\") {\n throw new Error(\"JWT header must be a JSON object.\");\n }\n\n if (!payloadJson || typeof payloadJson !== \"object\") {\n throw new Error(\"JWT payload must be a JSON object.\");\n }\n\n if (typeof payloadJson.exp !== \"number\") {\n throw new Error(\"JWT payload.exp must be a number.\");\n }\n\n return payloadJson as JwtPayload;\n}\n\ntest(\"API: authenticate succeeds with valid credentials\", async () => {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: {\n accept: \"*/*\",\n \"content-type\": \"application/json\",\n },\n });\n\n try {\n const res = await api.post(\"/api/iam/authenticate\", {\n data: {\n email: TEST_USER_EMAIL,\n username: \"\",\n password: TEST_USER_PASSWORD,\n },\n });\n\n expect(res.status()).toBe(200);\n const json = await res.json();\n\n expect(json.statusCode).toBe(200);\n expect(Array.isArray(json.message)).toBe(true);\n expect(json.message.length).toBe(0);\n expect(json.error).toBe(\"\");\n\n const user = json.data?.user as Record<string, unknown> | undefined;\n expect(user, \"Expected data.user to be an object.\").toBeTruthy();\n expect(typeof user).toBe(\"object\");\n\n const email = user?.email;\n expect(typeof email).toBe(\"string\");\n if (email === TEST_USER_EMAIL) {\n expect(email).toBe(TEST_USER_EMAIL);\n } else {\n // If your API returns a fixed system email, replace this with an exact match.\n expect((email as string).length).toBeGreaterThan(0);\n }\n\n expect(typeof user?.mobile).toBe(\"string\");\n expect(typeof user?.username).toBe(\"string\");\n expect(typeof user?.forcePasswordChange).toBe(\"boolean\");\n expect(typeof user?.id).toBe(\"number\");\n\n const roles = user?.roles;\n expect(Array.isArray(roles)).toBe(true);\n if (Array.isArray(roles)) {\n expect(roles.every((role) => typeof role === \"string\")).toBe(true);\n expect(roles).toContain(\"Admin\");\n }\n\n const accessToken = json.data?.accessToken;\n const refreshToken = json.data?.refreshToken;\n expect(typeof accessToken).toBe(\"string\");\n expect(typeof refreshToken).toBe(\"string\");\n\n const accessPayload = validateJwt(accessToken as string);\n const refreshPayload = validateJwt(refreshToken as string);\n\n expect(typeof accessPayload.exp).toBe(\"number\");\n expect(typeof refreshPayload.exp).toBe(\"number\");\n } finally {\n await api.dispose();\n }\n});\n\ntest(\"API: authenticate fails with wrong password\", async () => {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: {\n accept: \"*/*\",\n \"content-type\": \"application/json\",\n },\n });\n\n try {\n const res = await api.post(\"/api/iam/authenticate\", {\n data: {\n email: TEST_USER_EMAIL,\n username: \"\",\n password: `${TEST_USER_PASSWORD}__wrong`,\n },\n });\n\n expect(res.status()).toBe(401);\n const json = await res.json();\n\n expect(json.statusCode).toBe(401);\n expect(json.statusCodeMessage).toBe(\"Unauthorized\");\n expect(json.message).toBe(\"Invalid credentials\");\n expect(json.error).toBe(\"Invalid credentials\");\n expect(json.data?.statusCode).toBe(401);\n expect(json.data?.error).toBe(\"Unauthorized\");\n expect(json.data?.message).toBe(\"Invalid credentials\");\n } finally {\n await api.dispose();\n }\n});\n"]}
|