@flusys/nestjs-form-builder 7.1.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/config/index.d.ts +3 -3
  2. package/controllers/form-result.controller.d.ts +3 -3
  3. package/controllers/form.controller.d.ts +3 -3
  4. package/controllers/index.d.ts +2 -2
  5. package/docs/form-builder-swagger.config.d.ts +2 -2
  6. package/docs/index.d.ts +1 -1
  7. package/dtos/form.dto.d.ts +1 -1
  8. package/dtos/index.d.ts +2 -2
  9. package/entities/form-with-company.entity.d.ts +1 -1
  10. package/entities/form.entity.d.ts +1 -1
  11. package/entities/index.d.ts +7 -7
  12. package/enums/index.d.ts +1 -1
  13. package/fesm/262.js +1841 -0
  14. package/fesm/98.js +807 -0
  15. package/fesm/config/index.js +98 -3
  16. package/fesm/controllers/index.js +14 -2
  17. package/fesm/docs/index.js +171 -1
  18. package/fesm/dtos/index.js +571 -2
  19. package/fesm/entities/index.js +342 -10
  20. package/fesm/enums/index.js +38 -1
  21. package/fesm/index.js +482 -10
  22. package/fesm/interfaces/index.js +17 -3
  23. package/fesm/modules/index.js +178 -1
  24. package/fesm/runtime.js +85 -0
  25. package/fesm/services/index.js +35 -4
  26. package/index.d.ts +10 -10
  27. package/interfaces/form-builder-module.interface.d.ts +3 -3
  28. package/interfaces/form-result.interface.d.ts +1 -1
  29. package/interfaces/form.interface.d.ts +2 -2
  30. package/interfaces/index.d.ts +3 -3
  31. package/modules/form-builder.module.d.ts +2 -2
  32. package/modules/index.d.ts +1 -1
  33. package/package.json +26 -36
  34. package/services/form-builder-config.service.d.ts +4 -4
  35. package/services/form-builder-datasource.provider.d.ts +3 -3
  36. package/services/form-result.service.d.ts +6 -6
  37. package/services/form.service.d.ts +7 -7
  38. package/services/index.d.ts +4 -4
  39. package/utils/index.d.ts +2 -2
  40. package/utils/permission.utils.d.ts +1 -1
  41. package/cjs/config/event-actions.js +0 -33
  42. package/cjs/config/form-builder.constants.js +0 -13
  43. package/cjs/config/index.js +0 -20
  44. package/cjs/config/message-keys.js +0 -38
  45. package/cjs/controllers/form-result.controller.js +0 -281
  46. package/cjs/controllers/form-result.controller.spec.js +0 -257
  47. package/cjs/controllers/form.controller.js +0 -231
  48. package/cjs/controllers/form.controller.spec.js +0 -212
  49. package/cjs/controllers/index.js +0 -19
  50. package/cjs/docs/form-builder-swagger.config.js +0 -150
  51. package/cjs/docs/form-builder-swagger.config.spec.js +0 -75
  52. package/cjs/docs/index.js +0 -11
  53. package/cjs/dtos/form-result.dto.js +0 -248
  54. package/cjs/dtos/form.dto.js +0 -270
  55. package/cjs/dtos/index.js +0 -19
  56. package/cjs/entities/form-result.entity.js +0 -100
  57. package/cjs/entities/form-with-company.entity.js +0 -65
  58. package/cjs/entities/form.entity.js +0 -127
  59. package/cjs/entities/index.js +0 -52
  60. package/cjs/entities/index.spec.js +0 -18
  61. package/cjs/enums/form-access-type.enum.js +0 -19
  62. package/cjs/enums/index.js +0 -18
  63. package/cjs/index.js +0 -28
  64. package/cjs/interfaces/form-builder-module.interface.js +0 -4
  65. package/cjs/interfaces/form-result.interface.js +0 -4
  66. package/cjs/interfaces/form.interface.js +0 -4
  67. package/cjs/interfaces/index.js +0 -20
  68. package/cjs/modules/form-builder.module.js +0 -136
  69. package/cjs/modules/form-builder.module.spec.js +0 -133
  70. package/cjs/modules/index.js +0 -18
  71. package/cjs/services/form-builder-config.service.js +0 -69
  72. package/cjs/services/form-builder-config.service.spec.js +0 -121
  73. package/cjs/services/form-builder-datasource.provider.js +0 -125
  74. package/cjs/services/form-builder-datasource.provider.spec.js +0 -147
  75. package/cjs/services/form-result.service.js +0 -366
  76. package/cjs/services/form-result.service.spec.js +0 -823
  77. package/cjs/services/form.service.js +0 -341
  78. package/cjs/services/form.service.spec.js +0 -746
  79. package/cjs/services/index.js +0 -21
  80. package/cjs/utils/computed-field.utils.js +0 -199
  81. package/cjs/utils/computed-field.utils.spec.js +0 -1205
  82. package/cjs/utils/index.js +0 -19
  83. package/cjs/utils/permission.utils.js +0 -44
  84. package/cjs/utils/permission.utils.spec.js +0 -118
  85. package/fesm/config/event-actions.js +0 -12
  86. package/fesm/config/form-builder.constants.js +0 -3
  87. package/fesm/config/message-keys.js +0 -20
  88. package/fesm/controllers/form-result.controller.js +0 -271
  89. package/fesm/controllers/form-result.controller.spec.js +0 -253
  90. package/fesm/controllers/form.controller.js +0 -221
  91. package/fesm/controllers/form.controller.spec.js +0 -208
  92. package/fesm/docs/form-builder-swagger.config.js +0 -146
  93. package/fesm/docs/form-builder-swagger.config.spec.js +0 -71
  94. package/fesm/dtos/form-result.dto.js +0 -215
  95. package/fesm/dtos/form.dto.js +0 -243
  96. package/fesm/entities/form-result.entity.js +0 -90
  97. package/fesm/entities/form-with-company.entity.js +0 -55
  98. package/fesm/entities/form.entity.js +0 -117
  99. package/fesm/entities/index.spec.js +0 -14
  100. package/fesm/enums/form-access-type.enum.js +0 -9
  101. package/fesm/interfaces/form-builder-module.interface.js +0 -3
  102. package/fesm/interfaces/form-result.interface.js +0 -6
  103. package/fesm/interfaces/form.interface.js +0 -3
  104. package/fesm/modules/form-builder.module.js +0 -126
  105. package/fesm/modules/form-builder.module.spec.js +0 -129
  106. package/fesm/services/form-builder-config.service.js +0 -59
  107. package/fesm/services/form-builder-config.service.spec.js +0 -117
  108. package/fesm/services/form-builder-datasource.provider.js +0 -74
  109. package/fesm/services/form-builder-datasource.provider.spec.js +0 -143
  110. package/fesm/services/form-result.service.js +0 -356
  111. package/fesm/services/form-result.service.spec.js +0 -778
  112. package/fesm/services/form.service.js +0 -331
  113. package/fesm/services/form.service.spec.js +0 -701
  114. package/fesm/utils/computed-field.utils.js +0 -195
  115. package/fesm/utils/computed-field.utils.spec.js +0 -1201
  116. package/fesm/utils/index.js +0 -2
  117. package/fesm/utils/permission.utils.js +0 -42
  118. package/fesm/utils/permission.utils.spec.js +0 -114
@@ -1,117 +0,0 @@
1
- import { FormBuilderConfigService } from './form-builder-config.service';
2
- function buildOptions(overrides = {}) {
3
- return {
4
- bootstrapAppConfig: undefined,
5
- config: undefined,
6
- ...overrides
7
- };
8
- }
9
- describe('FormBuilderConfigService', ()=>{
10
- describe('isCompanyFeatureEnabled', ()=>{
11
- it('returns false when neither tenant nor bootstrap config enable the feature', ()=>{
12
- const service = new FormBuilderConfigService(buildOptions());
13
- expect(service.isCompanyFeatureEnabled()).toBe(false);
14
- });
15
- it('returns true when bootstrapAppConfig enables the feature', ()=>{
16
- const service = new FormBuilderConfigService(buildOptions({
17
- bootstrapAppConfig: {
18
- databaseMode: 'single',
19
- enableCompanyFeature: true
20
- }
21
- }));
22
- expect(service.isCompanyFeatureEnabled()).toBe(true);
23
- });
24
- it('prefers the tenant override over bootstrapAppConfig when a tenant is passed', ()=>{
25
- const service = new FormBuilderConfigService(buildOptions({
26
- bootstrapAppConfig: {
27
- databaseMode: 'multi-tenant',
28
- enableCompanyFeature: false
29
- }
30
- }));
31
- const result = service.isCompanyFeatureEnabled({
32
- enableCompanyFeature: true
33
- });
34
- expect(result).toBe(true);
35
- });
36
- it('falls back to bootstrapAppConfig when tenant does not specify enableCompanyFeature', ()=>{
37
- const service = new FormBuilderConfigService(buildOptions({
38
- bootstrapAppConfig: {
39
- databaseMode: 'multi-tenant',
40
- enableCompanyFeature: true
41
- }
42
- }));
43
- const result = service.isCompanyFeatureEnabled({
44
- id: 'tenant-1'
45
- });
46
- expect(result).toBe(true);
47
- });
48
- });
49
- describe('getDatabaseMode', ()=>{
50
- it('returns the configured database mode', ()=>{
51
- const service = new FormBuilderConfigService(buildOptions({
52
- bootstrapAppConfig: {
53
- databaseMode: 'multi-tenant'
54
- }
55
- }));
56
- expect(service.getDatabaseMode()).toBe('multi-tenant');
57
- });
58
- it('defaults to "single" when bootstrapAppConfig is missing', ()=>{
59
- const service = new FormBuilderConfigService(buildOptions({
60
- bootstrapAppConfig: undefined
61
- }));
62
- expect(service.getDatabaseMode()).toBe('single');
63
- });
64
- it('defaults to "single" when databaseMode is not set on bootstrapAppConfig', ()=>{
65
- const service = new FormBuilderConfigService(buildOptions({
66
- bootstrapAppConfig: {}
67
- }));
68
- expect(service.getDatabaseMode()).toBe('single');
69
- });
70
- });
71
- describe('isMultiTenant', ()=>{
72
- it('returns true when database mode is multi-tenant', ()=>{
73
- const service = new FormBuilderConfigService(buildOptions({
74
- bootstrapAppConfig: {
75
- databaseMode: 'multi-tenant'
76
- }
77
- }));
78
- expect(service.isMultiTenant()).toBe(true);
79
- });
80
- it('returns false when database mode is single', ()=>{
81
- const service = new FormBuilderConfigService(buildOptions({
82
- bootstrapAppConfig: {
83
- databaseMode: 'single'
84
- }
85
- }));
86
- expect(service.isMultiTenant()).toBe(false);
87
- });
88
- });
89
- describe('getOptions', ()=>{
90
- it('returns the exact options object it was constructed with', ()=>{
91
- const options = buildOptions({
92
- config: {
93
- tenants: []
94
- }
95
- });
96
- const service = new FormBuilderConfigService(options);
97
- expect(service.getOptions()).toBe(options);
98
- });
99
- });
100
- describe('getConfig', ()=>{
101
- it('returns the config sub-object', ()=>{
102
- const config = {
103
- tenants: []
104
- };
105
- const service = new FormBuilderConfigService(buildOptions({
106
- config
107
- }));
108
- expect(service.getConfig()).toBe(config);
109
- });
110
- it('returns undefined when config was never provided', ()=>{
111
- const service = new FormBuilderConfigService(buildOptions({
112
- config: undefined
113
- }));
114
- expect(service.getConfig()).toBeUndefined();
115
- });
116
- });
117
- });
@@ -1,74 +0,0 @@
1
- function _define_property(obj, key, value) {
2
- if (key in obj) {
3
- Object.defineProperty(obj, key, {
4
- value: value,
5
- enumerable: true,
6
- configurable: true,
7
- writable: true
8
- });
9
- } else {
10
- obj[key] = value;
11
- }
12
- return obj;
13
- }
14
- function _ts_decorate(decorators, target, key, desc) {
15
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
- else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
18
- return c > 3 && r && Object.defineProperty(target, key, r), r;
19
- }
20
- function _ts_metadata(k, v) {
21
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
- }
23
- function _ts_param(paramIndex, decorator) {
24
- return function(target, key) {
25
- decorator(target, key, paramIndex);
26
- };
27
- }
28
- import { MultiTenantDataSourceService } from '@flusys/nestjs-shared/modules';
29
- import { Inject, Injectable, Optional, Scope } from '@nestjs/common';
30
- import { REQUEST } from '@nestjs/core';
31
- import { Request } from 'express';
32
- import { FormBuilderConfigService } from './form-builder-config.service';
33
- export class FormBuilderDataSourceProvider extends MultiTenantDataSourceService {
34
- static buildParentOptions(options) {
35
- return {
36
- bootstrapAppConfig: options.bootstrapAppConfig,
37
- defaultDatabaseConfig: options.config?.defaultDatabaseConfig,
38
- tenantDefaultDatabaseConfig: options.config?.tenantDefaultDatabaseConfig,
39
- tenants: options.config?.tenants
40
- };
41
- }
42
- async getFormBuilderEntities(enableCompanyFeature) {
43
- const enable = enableCompanyFeature ?? this.configService.isCompanyFeatureEnabled();
44
- const { getFormBuilderEntitiesByConfig } = await import('../entities');
45
- return getFormBuilderEntitiesByConfig(enable);
46
- }
47
- async createDataSourceFromConfig(config) {
48
- const enableCompanyFeature = this.configService.isCompanyFeatureEnabled(this.getCurrentTenant() ?? undefined);
49
- const entities = await this.getFormBuilderEntities(enableCompanyFeature);
50
- return super.createDataSourceFromConfig(config, entities);
51
- }
52
- constructor(configService, request){
53
- super(FormBuilderDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), this.configService = configService;
54
- }
55
- }
56
- _define_property(FormBuilderDataSourceProvider, "tenantConnections", new Map());
57
- _define_property(FormBuilderDataSourceProvider, "singleDataSource", null);
58
- _define_property(FormBuilderDataSourceProvider, "tenantsRegistry", new Map());
59
- _define_property(FormBuilderDataSourceProvider, "initialized", false);
60
- _define_property(FormBuilderDataSourceProvider, "connectionLocks", new Map());
61
- _define_property(FormBuilderDataSourceProvider, "singleConnectionLock", null);
62
- FormBuilderDataSourceProvider = _ts_decorate([
63
- Injectable({
64
- scope: Scope.REQUEST
65
- }),
66
- _ts_param(0, Inject(FormBuilderConfigService)),
67
- _ts_param(1, Optional()),
68
- _ts_param(1, Inject(REQUEST)),
69
- _ts_metadata("design:type", Function),
70
- _ts_metadata("design:paramtypes", [
71
- typeof FormBuilderConfigService === "undefined" ? Object : FormBuilderConfigService,
72
- typeof Request === "undefined" ? Object : Request
73
- ])
74
- ], FormBuilderDataSourceProvider);
@@ -1,143 +0,0 @@
1
- import { FormBuilderDataSourceProvider } from './form-builder-datasource.provider';
2
- import { Form, FormResult, FormWithCompany } from '../entities';
3
- function buildConfigService(overrides = {}) {
4
- const options = {
5
- bootstrapAppConfig: {
6
- databaseMode: 'single'
7
- },
8
- config: {
9
- defaultDatabaseConfig: {
10
- type: 'mysql',
11
- host: 'localhost',
12
- port: 3306,
13
- username: 'root',
14
- password: 'password',
15
- database: 'flusys_test'
16
- },
17
- tenantDefaultDatabaseConfig: undefined,
18
- tenants: []
19
- }
20
- };
21
- return {
22
- getOptions: jest.fn().mockReturnValue(options),
23
- isCompanyFeatureEnabled: jest.fn().mockReturnValue(overrides.isCompanyFeatureEnabled ?? false)
24
- };
25
- }
26
- describe('FormBuilderDataSourceProvider', ()=>{
27
- afterEach(()=>{
28
- // Reset the isolated static caches this provider owns so tests don't leak state between runs.
29
- FormBuilderDataSourceProvider.tenantConnections = new Map();
30
- FormBuilderDataSourceProvider.singleDataSource = null;
31
- FormBuilderDataSourceProvider.tenantsRegistry = new Map();
32
- FormBuilderDataSourceProvider.initialized = false;
33
- FormBuilderDataSourceProvider.connectionLocks = new Map();
34
- FormBuilderDataSourceProvider.singleConnectionLock = null;
35
- });
36
- it('constructs and forwards bootstrap/database config to the base class', ()=>{
37
- const configService = buildConfigService();
38
- const provider = new FormBuilderDataSourceProvider(configService, undefined);
39
- expect(configService.getOptions).toHaveBeenCalled();
40
- expect(provider.getDatabaseMode()).toBe('single');
41
- expect(provider.isMultiTenant()).toBe(false);
42
- });
43
- it('reflects multi-tenant mode from bootstrapAppConfig', ()=>{
44
- const configService = buildConfigService();
45
- configService.getOptions.mockReturnValue({
46
- bootstrapAppConfig: {
47
- databaseMode: 'multi-tenant'
48
- },
49
- config: {}
50
- });
51
- const provider = new FormBuilderDataSourceProvider(configService, undefined);
52
- expect(provider.isMultiTenant()).toBe(true);
53
- });
54
- it('owns isolated static caches, separate from the base MultiTenantDataSourceService', ()=>{
55
- // Each DataSource Provider must override BOTH static cache properties and the
56
- // methods that read them — otherwise modules would share connections (see FLUSYS_NEST/CLAUDE.md).
57
- expect(FormBuilderDataSourceProvider.tenantConnections).not.toBe(Object.getPrototypeOf(FormBuilderDataSourceProvider).tenantConnections);
58
- });
59
- describe('getFormBuilderEntities', ()=>{
60
- it('returns core entities (no company feature) using the config service default', async ()=>{
61
- const configService = buildConfigService({
62
- isCompanyFeatureEnabled: false
63
- });
64
- const provider = new FormBuilderDataSourceProvider(configService, undefined);
65
- const entities = await provider.getFormBuilderEntities();
66
- expect(entities).toEqual([
67
- Form,
68
- FormResult
69
- ]);
70
- });
71
- it('returns company entities using the config service default', async ()=>{
72
- const configService = buildConfigService({
73
- isCompanyFeatureEnabled: true
74
- });
75
- const provider = new FormBuilderDataSourceProvider(configService, undefined);
76
- const entities = await provider.getFormBuilderEntities();
77
- expect(entities).toEqual([
78
- FormWithCompany,
79
- FormResult
80
- ]);
81
- });
82
- it('allows an explicit enableCompanyFeature override, bypassing config service', async ()=>{
83
- const configService = buildConfigService({
84
- isCompanyFeatureEnabled: false
85
- });
86
- const provider = new FormBuilderDataSourceProvider(configService, undefined);
87
- const entities = await provider.getFormBuilderEntities(true);
88
- expect(entities).toEqual([
89
- FormWithCompany,
90
- FormResult
91
- ]);
92
- expect(configService.isCompanyFeatureEnabled).not.toHaveBeenCalled();
93
- });
94
- });
95
- describe('createDataSourceFromConfig', ()=>{
96
- it('builds a DataSource using core entities when company feature disabled', async ()=>{
97
- const configService = buildConfigService({
98
- isCompanyFeatureEnabled: false
99
- });
100
- const provider = new FormBuilderDataSourceProvider(configService, undefined);
101
- const fakeDataSource = {
102
- isInitialized: true
103
- };
104
- const superSpy = jest.spyOn(Object.getPrototypeOf(Object.getPrototypeOf(provider)), 'createDataSourceFromConfig').mockResolvedValue(fakeDataSource);
105
- const config = {
106
- type: 'mysql',
107
- host: 'localhost',
108
- port: 3306,
109
- username: 'root',
110
- password: 'password',
111
- database: 'flusys_test'
112
- };
113
- const result = await provider.createDataSourceFromConfig(config);
114
- expect(superSpy).toHaveBeenCalledWith(config, [
115
- Form,
116
- FormResult
117
- ]);
118
- expect(result).toBe(fakeDataSource);
119
- superSpy.mockRestore();
120
- });
121
- it('builds a DataSource using company entities when company feature enabled', async ()=>{
122
- const configService = buildConfigService({
123
- isCompanyFeatureEnabled: true
124
- });
125
- const provider = new FormBuilderDataSourceProvider(configService, undefined);
126
- const fakeDataSource = {
127
- isInitialized: true
128
- };
129
- const superSpy = jest.spyOn(Object.getPrototypeOf(Object.getPrototypeOf(provider)), 'createDataSourceFromConfig').mockResolvedValue(fakeDataSource);
130
- const config = {
131
- type: 'mysql',
132
- database: 'flusys_test'
133
- };
134
- const result = await provider.createDataSourceFromConfig(config);
135
- expect(superSpy).toHaveBeenCalledWith(config, [
136
- FormWithCompany,
137
- FormResult
138
- ]);
139
- expect(result).toBe(fakeDataSource);
140
- superSpy.mockRestore();
141
- });
142
- });
143
- });
@@ -1,356 +0,0 @@
1
- function _define_property(obj, key, value) {
2
- if (key in obj) {
3
- Object.defineProperty(obj, key, {
4
- value: value,
5
- enumerable: true,
6
- configurable: true,
7
- writable: true
8
- });
9
- } else {
10
- obj[key] = value;
11
- }
12
- return obj;
13
- }
14
- function _ts_decorate(decorators, target, key, desc) {
15
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
- else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
18
- return c > 3 && r && Object.defineProperty(target, key, r), r;
19
- }
20
- function _ts_metadata(k, v) {
21
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
- }
23
- function _ts_param(paramIndex, decorator) {
24
- return function(target, key) {
25
- decorator(target, key, paramIndex);
26
- };
27
- }
28
- import { HybridCache, ApiService } from '@flusys/nestjs-shared/classes';
29
- import { LogAction } from '@flusys/nestjs-shared/decorators';
30
- import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
31
- import { UtilsService } from '@flusys/nestjs-shared/modules';
32
- import { BadRequestException, ForbiddenException, Inject, Injectable, NotFoundException, Scope, UnauthorizedException } from '@nestjs/common';
33
- import { IsNull } from 'typeorm';
34
- import { FORM_BUILDER_EVENT_ACTIONS, FORM_BUILDER_EVENT_ENTITIES, FORM_BUILDER_EVENT_MODULE, FORM_MESSAGES, FORM_RESULT_MESSAGES } from '../config';
35
- import { SubmitFormDto } from '../dtos/form-result.dto';
36
- import { Form, FormResult, FormWithCompany } from '../entities';
37
- import { FormAccessType } from '../enums/form-access-type.enum';
38
- import { calculateComputedFields } from '../utils/computed-field.utils';
39
- import { validateUserPermissions } from '../utils/permission.utils';
40
- import { FormBuilderConfigService } from './form-builder-config.service';
41
- import { FormBuilderDataSourceProvider } from './form-builder-datasource.provider';
42
- export class FormResultService extends ApiService {
43
- async ensureFormRepositoryInitialized() {
44
- if (!this.formRepository) {
45
- const enableCompanyFeature = this.formBuilderConfig.isCompanyFeatureEnabled();
46
- const formEntity = enableCompanyFeature ? FormWithCompany : Form;
47
- this.formRepository = await this.dataSourceProvider.getRepository(formEntity);
48
- }
49
- }
50
- async getActiveForm(formId) {
51
- await this.ensureFormRepositoryInitialized();
52
- const form = await this.formRepository.findOne({
53
- where: {
54
- id: formId,
55
- isActive: true,
56
- deletedAt: IsNull()
57
- }
58
- });
59
- if (!form) {
60
- throw new NotFoundException({
61
- message: 'Form not found or inactive',
62
- messageKey: FORM_MESSAGES.NOT_FOUND
63
- });
64
- }
65
- return form;
66
- }
67
- applyCompanyFilterToQuery(query, user) {
68
- if (this.formBuilderConfig.isCompanyFeatureEnabled() && user?.companyId) {
69
- query.innerJoin('form', 'f', 'f.id = form_result.formId').andWhere('f.company_id = :companyId', {
70
- companyId: user.companyId
71
- });
72
- }
73
- }
74
- async validateSubmissionAccess(form, user, isPublic) {
75
- if (isPublic) {
76
- if (form.accessType !== FormAccessType.PUBLIC) {
77
- throw new UnauthorizedException({
78
- message: 'This form is not available for public submission',
79
- messageKey: FORM_MESSAGES.NOT_PUBLIC
80
- });
81
- }
82
- return;
83
- }
84
- if (form.accessType === FormAccessType.PUBLIC) return;
85
- if (!user) {
86
- throw new UnauthorizedException({
87
- message: 'Authentication required to submit this form',
88
- messageKey: FORM_MESSAGES.AUTH_REQUIRED
89
- });
90
- }
91
- if (form.accessType === FormAccessType.ACTION_GROUP && form.actionGroups?.length) {
92
- const hasPermission = await validateUserPermissions(user, form.actionGroups, this.cacheManager, this.formBuilderConfig.isCompanyFeatureEnabled());
93
- if (!hasPermission) {
94
- throw new ForbiddenException({
95
- message: 'You do not have permission to submit this form',
96
- messageKey: FORM_MESSAGES.ACCESS_DENIED
97
- });
98
- }
99
- } else if (form.accessType !== FormAccessType.AUTHENTICATED) {
100
- throw new BadRequestException({
101
- message: 'Invalid form access type',
102
- messageKey: FORM_MESSAGES.INVALID_ACCESS_TYPE
103
- });
104
- }
105
- }
106
- buildUserDraftWhere(formId, userId) {
107
- return {
108
- formId,
109
- submittedById: userId,
110
- isDraft: true,
111
- deletedAt: IsNull()
112
- };
113
- }
114
- // Query Customization
115
- async getSelectQuery(query, _user, select) {
116
- if (!select || !select.length) {
117
- select = [
118
- 'id',
119
- 'formId',
120
- 'schemaVersionSnapshot',
121
- 'schemaVersion',
122
- 'data',
123
- 'submittedById',
124
- 'submittedAt',
125
- 'isDraft',
126
- 'createdAt',
127
- 'updatedAt'
128
- ];
129
- }
130
- const selectFields = select.map((field)=>`${this.entityName}.${field}`);
131
- query.select(selectFields);
132
- return {
133
- query,
134
- isRaw: false
135
- };
136
- }
137
- async getExtraManipulateQuery(query, filterDto, user) {
138
- const result = await super.getExtraManipulateQuery(query, filterDto, user);
139
- this.applyCompanyFilterToQuery(query, user);
140
- return result;
141
- }
142
- applyComputedFields(data, form, isDraft) {
143
- if (isDraft) {
144
- return data;
145
- }
146
- const schema = form.schema;
147
- const settings = schema?.settings;
148
- const computedFields = settings?.computedFields;
149
- if (!computedFields || computedFields.length === 0) {
150
- return data;
151
- }
152
- const computedValues = calculateComputedFields(data, computedFields);
153
- return {
154
- ...data,
155
- _computed: computedValues
156
- };
157
- }
158
- convertEntityToResponseDto(entity, _isRaw) {
159
- return {
160
- id: entity.id,
161
- formId: entity.formId,
162
- schemaVersionSnapshot: entity.schemaVersionSnapshot,
163
- schemaVersion: entity.schemaVersion,
164
- data: entity.data,
165
- submittedById: entity.submittedById,
166
- submittedAt: entity.submittedAt,
167
- isDraft: entity.isDraft,
168
- createdAt: entity.createdAt,
169
- updatedAt: entity.updatedAt,
170
- deletedAt: entity.deletedAt,
171
- createdById: entity.createdById,
172
- updatedById: entity.updatedById,
173
- deletedById: entity.deletedById
174
- };
175
- }
176
- // Form Submission
177
- async submitForm(dto, user, isPublic = false) {
178
- await this.ensureDataSourceRepository();
179
- const form = await this.getActiveForm(dto.formId);
180
- await this.validateSubmissionAccess(form, user, isPublic);
181
- const isDraft = dto.isDraft ?? false;
182
- const staleResults = [];
183
- // Handle existing draft for authenticated users
184
- if (user?.id) {
185
- const existingDraft = await this.repository.findOne({
186
- where: this.buildUserDraftWhere(dto.formId, user.id)
187
- });
188
- if (existingDraft) {
189
- if (isDraft) {
190
- Object.assign(existingDraft, {
191
- data: dto.data,
192
- schemaVersionSnapshot: form.schema,
193
- schemaVersion: form.schemaVersion,
194
- submittedAt: new Date()
195
- });
196
- const saved = await this.repository.save(existingDraft);
197
- await this.invalidateResultCache([
198
- saved
199
- ]);
200
- await this.publishSubmissionEvent(saved, dto.formId, true, user);
201
- return this.convertEntityToResponseDto(saved, false);
202
- }
203
- await this.repository.softRemove(existingDraft);
204
- staleResults.push(existingDraft);
205
- }
206
- }
207
- const saved = await this.repository.save({
208
- formId: dto.formId,
209
- schemaVersionSnapshot: form.schema,
210
- schemaVersion: form.schemaVersion,
211
- data: this.applyComputedFields(dto.data, form, isDraft),
212
- submittedById: user?.id ?? null,
213
- submittedAt: new Date(),
214
- isDraft
215
- });
216
- await this.invalidateResultCache([
217
- ...staleResults,
218
- saved
219
- ]);
220
- await this.publishSubmissionEvent(saved, dto.formId, isDraft, user);
221
- return this.convertEntityToResponseDto(saved, false);
222
- }
223
- async getMyDraft(formId, user) {
224
- await this.ensureDataSourceRepository();
225
- const draft = await this.repository.findOne({
226
- where: this.buildUserDraftWhere(formId, user.id),
227
- order: {
228
- updatedAt: 'DESC'
229
- }
230
- });
231
- return draft ? this.convertEntityToResponseDto(draft, false) : null;
232
- }
233
- async updateDraft(draftId, dto, user) {
234
- await this.ensureDataSourceRepository();
235
- const draft = await this.repository.findOne({
236
- where: {
237
- id: draftId,
238
- submittedById: user.id,
239
- deletedAt: IsNull()
240
- }
241
- });
242
- if (!draft) {
243
- throw new NotFoundException({
244
- message: 'Draft not found',
245
- messageKey: FORM_RESULT_MESSAGES.NOT_FOUND
246
- });
247
- }
248
- const form = await this.getActiveForm(dto.formId);
249
- const isDraft = dto.isDraft ?? false;
250
- Object.assign(draft, {
251
- data: this.applyComputedFields(dto.data, form, isDraft),
252
- schemaVersionSnapshot: form.schema,
253
- schemaVersion: form.schemaVersion,
254
- submittedAt: new Date(),
255
- isDraft
256
- });
257
- const saved = await this.repository.save(draft);
258
- await this.invalidateResultCache([
259
- saved
260
- ]);
261
- await this.publishSubmissionEvent(saved, dto.formId, isDraft, user);
262
- return this.convertEntityToResponseDto(saved, false);
263
- }
264
- async publishSubmissionEvent(result, formId, isDraft, user) {
265
- await this.publishDomainAction(isDraft ? FORM_BUILDER_EVENT_ACTIONS.DRAFT_SAVED : FORM_BUILDER_EVENT_ACTIONS.SUBMITTED, {
266
- ids: [
267
- result.id
268
- ],
269
- data: result,
270
- metadata: {
271
- formId,
272
- isDraft,
273
- schemaVersion: result.schemaVersion
274
- },
275
- user
276
- });
277
- }
278
- async invalidateResultCache(results) {
279
- await Promise.all([
280
- this.clearCacheForAll(),
281
- this.clearCacheForId(results)
282
- ]);
283
- }
284
- async getByFormId(formId, user, pagination) {
285
- await this.ensureDataSourceRepository();
286
- const query = this.repository.createQueryBuilder(this.entityName).where('form_result.formId = :formId', {
287
- formId
288
- }).andWhere('form_result.deletedAt IS NULL');
289
- this.applyCompanyFilterToQuery(query, user);
290
- const page = pagination?.page ?? 0;
291
- const pageSize = pagination?.pageSize ?? 10;
292
- query.skip(page * pageSize).take(pageSize).orderBy('form_result.submittedAt', 'DESC');
293
- const [entities, total] = await query.getManyAndCount();
294
- return {
295
- data: entities.map((e)=>this.convertEntityToResponseDto(e, false)),
296
- total
297
- };
298
- }
299
- async hasUserSubmitted(formId, user) {
300
- await this.ensureDataSourceRepository();
301
- const count = await this.repository.count({
302
- where: {
303
- formId,
304
- submittedById: user.id,
305
- isDraft: false,
306
- deletedAt: IsNull()
307
- }
308
- });
309
- return count > 0;
310
- }
311
- constructor(cacheManager, utilsService, formBuilderConfig, dataSourceProvider){
312
- super(FORM_BUILDER_EVENT_ENTITIES.FORM_RESULT, cacheManager, utilsService, FormResultService.name, true, FORM_BUILDER_EVENT_MODULE, dataSourceProvider, FormResult), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "formBuilderConfig", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "formRepository", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.formBuilderConfig = formBuilderConfig, this.dataSourceProvider = dataSourceProvider, this.formRepository = null;
313
- }
314
- }
315
- _ts_decorate([
316
- LogAction({
317
- action: 'formResult.submit',
318
- module: 'form-builder'
319
- }),
320
- _ts_metadata("design:type", Function),
321
- _ts_metadata("design:paramtypes", [
322
- typeof SubmitFormDto === "undefined" ? Object : SubmitFormDto,
323
- Object,
324
- void 0
325
- ]),
326
- _ts_metadata("design:returntype", Promise)
327
- ], FormResultService.prototype, "submitForm", null);
328
- _ts_decorate([
329
- LogAction({
330
- action: 'formResult.updateDraft',
331
- module: 'form-builder'
332
- }),
333
- _ts_metadata("design:type", Function),
334
- _ts_metadata("design:paramtypes", [
335
- String,
336
- typeof SubmitFormDto === "undefined" ? Object : SubmitFormDto,
337
- typeof ILoggedUserInfo === "undefined" ? Object : ILoggedUserInfo
338
- ]),
339
- _ts_metadata("design:returntype", Promise)
340
- ], FormResultService.prototype, "updateDraft", null);
341
- FormResultService = _ts_decorate([
342
- Injectable({
343
- scope: Scope.REQUEST
344
- }),
345
- _ts_param(0, Inject('CACHE_INSTANCE')),
346
- _ts_param(1, Inject(UtilsService)),
347
- _ts_param(2, Inject(FormBuilderConfigService)),
348
- _ts_param(3, Inject(FormBuilderDataSourceProvider)),
349
- _ts_metadata("design:type", Function),
350
- _ts_metadata("design:paramtypes", [
351
- typeof HybridCache === "undefined" ? Object : HybridCache,
352
- typeof UtilsService === "undefined" ? Object : UtilsService,
353
- typeof FormBuilderConfigService === "undefined" ? Object : FormBuilderConfigService,
354
- typeof FormBuilderDataSourceProvider === "undefined" ? Object : FormBuilderDataSourceProvider
355
- ])
356
- ], FormResultService);