@flusys/nestjs-form-builder 0.1.0-beta.3

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 (82) hide show
  1. package/cjs/config/form-builder-config.service.js +79 -0
  2. package/cjs/config/form-builder.constants.js +22 -0
  3. package/cjs/config/index.js +19 -0
  4. package/cjs/controllers/form-result.controller.js +131 -0
  5. package/cjs/controllers/form.controller.js +177 -0
  6. package/cjs/controllers/index.js +19 -0
  7. package/cjs/dtos/form-result.dto.js +309 -0
  8. package/cjs/dtos/form.dto.js +396 -0
  9. package/cjs/dtos/index.js +19 -0
  10. package/cjs/entities/form-base.entity.js +113 -0
  11. package/cjs/entities/form-result.entity.js +106 -0
  12. package/cjs/entities/form-with-company.entity.js +55 -0
  13. package/cjs/entities/form.entity.js +25 -0
  14. package/cjs/entities/index.js +40 -0
  15. package/cjs/enums/form-access-type.enum.js +19 -0
  16. package/cjs/enums/index.js +18 -0
  17. package/cjs/index.js +26 -0
  18. package/cjs/interfaces/form-builder-module.interface.js +4 -0
  19. package/cjs/interfaces/form-result.interface.js +9 -0
  20. package/cjs/interfaces/form.interface.js +4 -0
  21. package/cjs/interfaces/index.js +20 -0
  22. package/cjs/modules/form-builder.module.js +162 -0
  23. package/cjs/modules/index.js +18 -0
  24. package/cjs/services/form-builder-datasource.provider.js +213 -0
  25. package/cjs/services/form-result.service.js +252 -0
  26. package/cjs/services/form.service.js +335 -0
  27. package/cjs/services/index.js +20 -0
  28. package/config/form-builder-config.service.d.ts +10 -0
  29. package/config/form-builder.constants.d.ts +2 -0
  30. package/config/index.d.ts +2 -0
  31. package/controllers/form-result.controller.d.ts +24 -0
  32. package/controllers/form.controller.d.ts +23 -0
  33. package/controllers/index.d.ts +2 -0
  34. package/dtos/form-result.dto.d.ts +48 -0
  35. package/dtos/form.dto.d.ts +63 -0
  36. package/dtos/index.d.ts +2 -0
  37. package/entities/form-base.entity.d.ts +13 -0
  38. package/entities/form-result.entity.d.ts +11 -0
  39. package/entities/form-with-company.entity.d.ts +4 -0
  40. package/entities/form.entity.d.ts +3 -0
  41. package/entities/index.d.ts +5 -0
  42. package/enums/form-access-type.enum.d.ts +5 -0
  43. package/enums/index.d.ts +1 -0
  44. package/fesm/config/form-builder-config.service.js +69 -0
  45. package/fesm/config/form-builder.constants.js +6 -0
  46. package/fesm/config/index.js +2 -0
  47. package/fesm/controllers/form-result.controller.js +121 -0
  48. package/fesm/controllers/form.controller.js +167 -0
  49. package/fesm/controllers/index.js +2 -0
  50. package/fesm/dtos/form-result.dto.js +281 -0
  51. package/fesm/dtos/form.dto.js +370 -0
  52. package/fesm/dtos/index.js +2 -0
  53. package/fesm/entities/form-base.entity.js +106 -0
  54. package/fesm/entities/form-result.entity.js +96 -0
  55. package/fesm/entities/form-with-company.entity.js +45 -0
  56. package/fesm/entities/form.entity.js +15 -0
  57. package/fesm/entities/index.js +29 -0
  58. package/fesm/enums/form-access-type.enum.js +9 -0
  59. package/fesm/enums/index.js +1 -0
  60. package/fesm/index.js +16 -0
  61. package/fesm/interfaces/form-builder-module.interface.js +3 -0
  62. package/fesm/interfaces/form-result.interface.js +6 -0
  63. package/fesm/interfaces/form.interface.js +3 -0
  64. package/fesm/interfaces/index.js +3 -0
  65. package/fesm/modules/form-builder.module.js +152 -0
  66. package/fesm/modules/index.js +1 -0
  67. package/fesm/services/form-builder-datasource.provider.js +162 -0
  68. package/fesm/services/form-result.service.js +242 -0
  69. package/fesm/services/form.service.js +325 -0
  70. package/fesm/services/index.js +3 -0
  71. package/index.d.ts +8 -0
  72. package/interfaces/form-builder-module.interface.d.ts +23 -0
  73. package/interfaces/form-result.interface.d.ts +18 -0
  74. package/interfaces/form.interface.d.ts +29 -0
  75. package/interfaces/index.d.ts +3 -0
  76. package/modules/form-builder.module.d.ts +9 -0
  77. package/modules/index.d.ts +1 -0
  78. package/package.json +84 -0
  79. package/services/form-builder-datasource.provider.d.ts +25 -0
  80. package/services/form-result.service.d.ts +41 -0
  81. package/services/form.service.d.ts +45 -0
  82. package/services/index.d.ts +3 -0
package/fesm/index.js ADDED
@@ -0,0 +1,16 @@
1
+ // Config
2
+ export * from './config/index';
3
+ // Controllers
4
+ export * from './controllers/index';
5
+ // DTOs
6
+ export * from './dtos/index';
7
+ // Entities
8
+ export * from './entities/index';
9
+ // Enums
10
+ export * from './enums/index';
11
+ // Interfaces
12
+ export * from './interfaces/index';
13
+ // Modules
14
+ export * from './modules/index';
15
+ // Services
16
+ export * from './services/index';
@@ -0,0 +1,3 @@
1
+ /**
2
+ * Async options for Form Builder Module
3
+ */ export { };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Form Result Interface
3
+ * Represents a form submission in responses
4
+ *
5
+ * Note: No companyId field - company context is derived from the linked Form via formId
6
+ */ export { };
@@ -0,0 +1,3 @@
1
+ /**
2
+ * Public form interface - limited fields for public access
3
+ */ export { };
@@ -0,0 +1,3 @@
1
+ export * from './form.interface';
2
+ export * from './form-result.interface';
3
+ export * from './form-builder-module.interface';
@@ -0,0 +1,152 @@
1
+ function _ts_decorate(decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ 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;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ }
7
+ import { CacheModule, UtilsModule } from '@flusys/nestjs-shared/modules';
8
+ import { Module } from '@nestjs/common';
9
+ import { FormBuilderConfigService } from '../config/form-builder-config.service';
10
+ import { FORM_BUILDER_MODULE_OPTIONS } from '../config/form-builder.constants';
11
+ import { FormController, FormResultController } from '../controllers';
12
+ import { FormBuilderDataSourceProvider, FormService, FormResultService } from '../services';
13
+ export class FormBuilderModule {
14
+ /**
15
+ * Register FormBuilderModule synchronously
16
+ */ static forRoot(options) {
17
+ const controllers = this.getControllers(options);
18
+ const providers = this.getProviders(options);
19
+ return {
20
+ module: FormBuilderModule,
21
+ global: options.global ?? false,
22
+ imports: [
23
+ CacheModule,
24
+ UtilsModule
25
+ ],
26
+ controllers: options.includeController !== false ? controllers : [],
27
+ providers,
28
+ exports: [
29
+ FormBuilderConfigService,
30
+ FormBuilderDataSourceProvider,
31
+ FormService,
32
+ FormResultService
33
+ ]
34
+ };
35
+ }
36
+ /**
37
+ * Register FormBuilderModule asynchronously
38
+ */ static forRootAsync(options) {
39
+ const controllers = this.getControllers(options);
40
+ const asyncProviders = this.createAsyncProviders(options);
41
+ return {
42
+ module: FormBuilderModule,
43
+ global: options.global ?? false,
44
+ imports: [
45
+ ...options.imports || [],
46
+ CacheModule,
47
+ UtilsModule
48
+ ],
49
+ controllers: options.includeController !== false ? controllers : [],
50
+ providers: [
51
+ ...asyncProviders,
52
+ ...this.getProviders(options, false)
53
+ ],
54
+ exports: [
55
+ FormBuilderConfigService,
56
+ FormBuilderDataSourceProvider,
57
+ FormService,
58
+ FormResultService
59
+ ]
60
+ };
61
+ }
62
+ // ==================== Private Helper Methods ====================
63
+ /**
64
+ * Get controllers
65
+ */ static getControllers(options) {
66
+ return [
67
+ FormController,
68
+ FormResultController
69
+ ];
70
+ }
71
+ /**
72
+ * Get providers
73
+ * @param options Module options
74
+ * @param includeOptionsProvider Whether to include the FORM_BUILDER_MODULE_OPTIONS provider
75
+ */ static getProviders(options, includeOptionsProvider = true) {
76
+ const providers = [
77
+ FormBuilderConfigService,
78
+ FormBuilderDataSourceProvider,
79
+ FormService,
80
+ FormResultService
81
+ ];
82
+ // Only include options provider for sync registration
83
+ if (includeOptionsProvider) {
84
+ providers.unshift({
85
+ provide: FORM_BUILDER_MODULE_OPTIONS,
86
+ useValue: options
87
+ });
88
+ }
89
+ return providers;
90
+ }
91
+ /**
92
+ * Create async providers for forRootAsync
93
+ */ static createAsyncProviders(options) {
94
+ if (options.useFactory) {
95
+ return [
96
+ {
97
+ provide: FORM_BUILDER_MODULE_OPTIONS,
98
+ useFactory: async (...args)=>{
99
+ const config = await options.useFactory(...args);
100
+ return {
101
+ ...options,
102
+ config
103
+ };
104
+ },
105
+ inject: options.inject || []
106
+ }
107
+ ];
108
+ }
109
+ if (options.useClass) {
110
+ return [
111
+ {
112
+ provide: FORM_BUILDER_MODULE_OPTIONS,
113
+ useFactory: async (optionsFactory)=>{
114
+ const config = await optionsFactory.createFormBuilderOptions();
115
+ return {
116
+ ...options,
117
+ config
118
+ };
119
+ },
120
+ inject: [
121
+ options.useClass
122
+ ]
123
+ },
124
+ {
125
+ provide: options.useClass,
126
+ useClass: options.useClass
127
+ }
128
+ ];
129
+ }
130
+ if (options.useExisting) {
131
+ return [
132
+ {
133
+ provide: FORM_BUILDER_MODULE_OPTIONS,
134
+ useFactory: async (optionsFactory)=>{
135
+ const config = await optionsFactory.createFormBuilderOptions();
136
+ return {
137
+ ...options,
138
+ config
139
+ };
140
+ },
141
+ inject: [
142
+ options.useExisting
143
+ ]
144
+ }
145
+ ];
146
+ }
147
+ return [];
148
+ }
149
+ }
150
+ FormBuilderModule = _ts_decorate([
151
+ Module({})
152
+ ], FormBuilderModule);
@@ -0,0 +1 @@
1
+ export * from './form-builder.module';
@@ -0,0 +1,162 @@
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, Logger, Optional, Scope } from '@nestjs/common';
30
+ import { REQUEST } from '@nestjs/core';
31
+ import { Request } from 'express';
32
+ import { FormBuilderModuleOptions } from '../interfaces';
33
+ import { FORM_BUILDER_MODULE_OPTIONS } from '../config/form-builder.constants';
34
+ export class FormBuilderDataSourceProvider extends MultiTenantDataSourceService {
35
+ // ==================== Factory Methods ====================
36
+ /**
37
+ * Build parent options from FormBuilderModuleOptions
38
+ */ static buildParentOptions(options) {
39
+ return {
40
+ bootstrapAppConfig: options.bootstrapAppConfig,
41
+ defaultDatabaseConfig: options.config?.defaultDatabaseConfig
42
+ };
43
+ }
44
+ // ==================== Feature Flags ====================
45
+ /**
46
+ * Get global enable company feature flag
47
+ */ getEnableCompanyFeature() {
48
+ return this.formBuilderOptions.bootstrapAppConfig?.enableCompanyFeature ?? false;
49
+ }
50
+ /**
51
+ * Get enable company feature for specific tenant
52
+ * Falls back to global setting if not specified per-tenant
53
+ */ getEnableCompanyFeatureForTenant(tenant) {
54
+ return tenant?.enableCompanyFeature ?? this.getEnableCompanyFeature();
55
+ }
56
+ /**
57
+ * Get enable company feature for current request context
58
+ */ getEnableCompanyFeatureForCurrentTenant() {
59
+ return this.getEnableCompanyFeatureForTenant(this.getCurrentTenant() ?? undefined);
60
+ }
61
+ // ==================== Entity Management ====================
62
+ /**
63
+ * Get form builder entities based on company feature flag
64
+ * Note: FormResult is always the same - company context derived from Form via formId
65
+ */ async getFormBuilderEntities(enableCompanyFeature) {
66
+ const enable = enableCompanyFeature ?? this.getEnableCompanyFeature();
67
+ const { Form, FormResult, FormWithCompany } = await import('../entities');
68
+ if (enable) {
69
+ return [
70
+ FormWithCompany,
71
+ FormResult
72
+ ];
73
+ }
74
+ return [
75
+ Form,
76
+ FormResult
77
+ ];
78
+ }
79
+ // ==================== Overrides ====================
80
+ /**
81
+ * Override to dynamically set entities based on tenant config
82
+ */ async createDataSourceFromConfig(config) {
83
+ const currentTenant = this.getCurrentTenant();
84
+ const enableCompanyFeature = this.getEnableCompanyFeatureForTenant(currentTenant ?? undefined);
85
+ const entities = await this.getFormBuilderEntities(enableCompanyFeature);
86
+ return super.createDataSourceFromConfig(config, entities);
87
+ }
88
+ /**
89
+ * Override to use FormBuilder-specific static cache
90
+ */ async getSingleDataSource() {
91
+ // Return existing initialized connection from FormBuilder-specific cache
92
+ if (FormBuilderDataSourceProvider.singleDataSource?.isInitialized) {
93
+ return FormBuilderDataSourceProvider.singleDataSource;
94
+ }
95
+ // If another request is creating the connection, wait for it
96
+ if (FormBuilderDataSourceProvider.singleConnectionLock) {
97
+ return FormBuilderDataSourceProvider.singleConnectionLock;
98
+ }
99
+ const config = this.getDefaultDatabaseConfig();
100
+ if (!config) {
101
+ throw new Error('No database config available. Provide defaultDatabaseConfig in FormBuilderModule options.');
102
+ }
103
+ // Create connection with lock to prevent race conditions
104
+ const connectionPromise = this.createDataSourceFromConfig(config);
105
+ FormBuilderDataSourceProvider.singleConnectionLock = connectionPromise;
106
+ try {
107
+ const dataSource = await connectionPromise;
108
+ FormBuilderDataSourceProvider.singleDataSource = dataSource;
109
+ return dataSource;
110
+ } finally{
111
+ FormBuilderDataSourceProvider.singleConnectionLock = null;
112
+ }
113
+ }
114
+ /**
115
+ * Override to use FormBuilder-specific static cache for tenant connections
116
+ */ async getOrCreateTenantConnection(tenant) {
117
+ // Return existing initialized connection from FormBuilder-specific cache
118
+ const existing = FormBuilderDataSourceProvider.tenantConnections.get(tenant.id);
119
+ if (existing?.isInitialized) {
120
+ return existing;
121
+ }
122
+ // If another request is creating this tenant's connection, wait for it
123
+ const pendingConnection = FormBuilderDataSourceProvider.connectionLocks.get(tenant.id);
124
+ if (pendingConnection) {
125
+ return pendingConnection;
126
+ }
127
+ // Create connection with lock to prevent race conditions
128
+ const config = this.buildTenantDatabaseConfig(tenant);
129
+ const connectionPromise = this.createDataSourceFromConfig(config);
130
+ FormBuilderDataSourceProvider.connectionLocks.set(tenant.id, connectionPromise);
131
+ try {
132
+ const dataSource = await connectionPromise;
133
+ FormBuilderDataSourceProvider.tenantConnections.set(tenant.id, dataSource);
134
+ return dataSource;
135
+ } finally{
136
+ FormBuilderDataSourceProvider.connectionLocks.delete(tenant.id);
137
+ }
138
+ }
139
+ constructor(formBuilderOptions, request){
140
+ super(FormBuilderDataSourceProvider.buildParentOptions(formBuilderOptions), request), _define_property(this, "formBuilderOptions", void 0), _define_property(this, "logger", void 0), this.formBuilderOptions = formBuilderOptions, this.logger = new Logger(FormBuilderDataSourceProvider.name);
141
+ }
142
+ }
143
+ // Override parent's static properties to have FormBuilder-specific cache
144
+ _define_property(FormBuilderDataSourceProvider, "tenantConnections", new Map());
145
+ _define_property(FormBuilderDataSourceProvider, "singleDataSource", null);
146
+ _define_property(FormBuilderDataSourceProvider, "tenantsRegistry", new Map());
147
+ _define_property(FormBuilderDataSourceProvider, "initialized", false);
148
+ _define_property(FormBuilderDataSourceProvider, "connectionLocks", new Map());
149
+ _define_property(FormBuilderDataSourceProvider, "singleConnectionLock", null);
150
+ FormBuilderDataSourceProvider = _ts_decorate([
151
+ Injectable({
152
+ scope: Scope.REQUEST
153
+ }),
154
+ _ts_param(0, Inject(FORM_BUILDER_MODULE_OPTIONS)),
155
+ _ts_param(1, Optional()),
156
+ _ts_param(1, Inject(REQUEST)),
157
+ _ts_metadata("design:type", Function),
158
+ _ts_metadata("design:paramtypes", [
159
+ typeof FormBuilderModuleOptions === "undefined" ? Object : FormBuilderModuleOptions,
160
+ typeof Request === "undefined" ? Object : Request
161
+ ])
162
+ ], FormBuilderDataSourceProvider);
@@ -0,0 +1,242 @@
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 { RequestScopedApiService, HybridCache } from '@flusys/nestjs-shared/classes';
29
+ import { UtilsService } from '@flusys/nestjs-shared/modules';
30
+ import { BadRequestException, Inject, Injectable, Logger, NotFoundException, Scope, UnauthorizedException } from '@nestjs/common';
31
+ import { IsNull } from 'typeorm';
32
+ import { FormBuilderConfigService } from '../config/form-builder-config.service';
33
+ import { Form, FormResult, FormWithCompany } from '../entities';
34
+ import { FormAccessType } from '../enums/form-access-type.enum';
35
+ import { FormBuilderDataSourceProvider } from './form-builder-datasource.provider';
36
+ export class FormResultService extends RequestScopedApiService {
37
+ /**
38
+ * Resolve entity class for this service
39
+ * @returns FormResult (same entity regardless of company feature)
40
+ */ resolveEntity() {
41
+ return FormResult;
42
+ }
43
+ /**
44
+ * Get DataSource provider for this service
45
+ * @returns FormBuilderDataSourceProvider instance
46
+ */ getDataSourceProvider() {
47
+ return this.dataSourceProvider;
48
+ }
49
+ /**
50
+ * Initialize form repository for form lookups
51
+ */ async ensureFormRepositoryInitialized() {
52
+ if (!this.formRepository) {
53
+ const enableCompanyFeature = this.formBuilderConfig.isCompanyFeatureEnabled();
54
+ const formEntity = enableCompanyFeature ? FormWithCompany : Form;
55
+ this.formRepository = await this.dataSourceProvider.getRepository(formEntity);
56
+ }
57
+ }
58
+ // ==================== Entity Conversion ====================
59
+ async convertSingleDtoToEntity(dto, user) {
60
+ let result = {};
61
+ // NOTE: Using 'id' in dto check instead of instanceof - instanceof may not work after esbuild bundling
62
+ if ('id' in dto && dto.id && typeof dto.id === 'string') {
63
+ const dbData = await this.repository.findOne({
64
+ where: {
65
+ id: dto.id
66
+ }
67
+ });
68
+ if (!dbData) {
69
+ throw new NotFoundException('Form result not found');
70
+ }
71
+ result = dbData;
72
+ }
73
+ // Merge DTO into result
74
+ result = {
75
+ ...result,
76
+ ...dto
77
+ };
78
+ return result;
79
+ }
80
+ async getSelectQuery(query, _user, select) {
81
+ if (!select || !select.length) {
82
+ select = [
83
+ 'id',
84
+ 'formId',
85
+ 'schemaVersionSnapshot',
86
+ 'schemaVersion',
87
+ 'data',
88
+ 'submittedById',
89
+ 'submittedAt',
90
+ 'isDraft',
91
+ 'metadata',
92
+ 'createdAt',
93
+ 'updatedAt'
94
+ ];
95
+ }
96
+ const selectFields = select.map((field)=>`${this.entityName}.${field}`);
97
+ query.select(selectFields);
98
+ return {
99
+ query,
100
+ isRaw: false
101
+ };
102
+ }
103
+ /**
104
+ * Override: Extra query manipulation - Auto-filter by user's company via Form
105
+ */ async getExtraManipulateQuery(query, filterDto, user) {
106
+ const result = await super.getExtraManipulateQuery(query, filterDto, user);
107
+ // If company feature enabled and user has companyId, filter via Form's company
108
+ const enableCompanyFeature = this.formBuilderConfig.isCompanyFeatureEnabled();
109
+ if (enableCompanyFeature && user?.companyId) {
110
+ query.innerJoin('form', 'f', 'f.id = form_result.formId').andWhere('f.company_id = :companyId', {
111
+ companyId: user.companyId
112
+ });
113
+ }
114
+ return result;
115
+ }
116
+ /**
117
+ * Override: Convert entity to response DTO
118
+ */ convertEntityToResponseDto(entity, _isRaw) {
119
+ return {
120
+ id: entity.id,
121
+ formId: entity.formId,
122
+ schemaVersionSnapshot: entity.schemaVersionSnapshot,
123
+ schemaVersion: entity.schemaVersion,
124
+ data: entity.data,
125
+ submittedById: entity.submittedById,
126
+ submittedAt: entity.submittedAt,
127
+ isDraft: entity.isDraft,
128
+ metadata: entity.metadata,
129
+ createdAt: entity.createdAt,
130
+ updatedAt: entity.updatedAt,
131
+ deletedAt: entity.deletedAt,
132
+ createdById: entity.createdById,
133
+ updatedById: entity.updatedById,
134
+ deletedById: entity.deletedById
135
+ };
136
+ }
137
+ // ==================== Form Submission ====================
138
+ /**
139
+ * Submit a form (authenticated or public)
140
+ * Creates a result with schema snapshot
141
+ */ async submitForm(dto, user, isPublic = false) {
142
+ await this.ensureRepositoryInitialized();
143
+ await this.ensureFormRepositoryInitialized();
144
+ // Get the form
145
+ const form = await this.formRepository.findOne({
146
+ where: {
147
+ id: dto.formId,
148
+ isActive: true,
149
+ deletedAt: IsNull()
150
+ }
151
+ });
152
+ if (!form) {
153
+ throw new NotFoundException('Form not found or inactive');
154
+ }
155
+ // Validate access
156
+ if (isPublic) {
157
+ if (form.accessType !== FormAccessType.PUBLIC) {
158
+ throw new UnauthorizedException('This form is not available for public submission');
159
+ }
160
+ } else {
161
+ // For non-public submissions
162
+ switch(form.accessType){
163
+ case FormAccessType.PUBLIC:
164
+ break;
165
+ case FormAccessType.AUTHENTICATED:
166
+ if (!user) {
167
+ throw new UnauthorizedException('Authentication required to submit this form');
168
+ }
169
+ break;
170
+ case FormAccessType.ACTION_GROUP:
171
+ if (!user) {
172
+ throw new UnauthorizedException('Authentication required to submit this form');
173
+ }
174
+ break;
175
+ default:
176
+ throw new BadRequestException('Invalid form access type');
177
+ }
178
+ }
179
+ // Create the result with schema snapshot
180
+ const resultData = {
181
+ formId: dto.formId,
182
+ schemaVersionSnapshot: form.schema,
183
+ schemaVersion: form.schemaVersion,
184
+ data: dto.data,
185
+ submittedById: user?.id ?? null,
186
+ submittedAt: new Date(),
187
+ isDraft: dto.isDraft ?? false,
188
+ metadata: dto.metadata ?? null
189
+ };
190
+ const saved = await this.repository.save(resultData);
191
+ return this.convertEntityToResponseDto(saved, false);
192
+ }
193
+ /**
194
+ * Get results for a specific form
195
+ */ async getByFormId(formId, user, pagination) {
196
+ await this.ensureRepositoryInitialized();
197
+ const query = this.repository.createQueryBuilder(this.entityName);
198
+ query.where('form_result.formId = :formId', {
199
+ formId
200
+ });
201
+ query.andWhere('form_result.deletedAt IS NULL');
202
+ // Apply company filter via Form if enabled
203
+ const enableCompanyFeature = this.formBuilderConfig.isCompanyFeatureEnabled();
204
+ if (enableCompanyFeature && user?.companyId) {
205
+ query.innerJoin('form', 'f', 'f.id = form_result.formId').andWhere('f.company_id = :companyId', {
206
+ companyId: user.companyId
207
+ });
208
+ }
209
+ // Pagination
210
+ const page = pagination?.page ?? 0;
211
+ const pageSize = pagination?.pageSize ?? 10;
212
+ query.skip(page * pageSize).take(pageSize);
213
+ query.orderBy('form_result.submittedAt', 'DESC');
214
+ const [entities, total] = await query.getManyAndCount();
215
+ const data = entities.map((e)=>this.convertEntityToResponseDto(e, false));
216
+ return {
217
+ data,
218
+ total
219
+ };
220
+ }
221
+ // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
222
+ constructor(cacheManager, utilsService, formBuilderConfig, dataSourceProvider){
223
+ // Repository will be set asynchronously by RequestScopedApiService
224
+ super('form_result', null, cacheManager, utilsService, FormResultService.name, true), _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, "logger", void 0), _define_property(this, "formRepository", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.formBuilderConfig = formBuilderConfig, this.dataSourceProvider = dataSourceProvider, this.logger = new Logger(FormResultService.name), this.formRepository = null;
225
+ }
226
+ }
227
+ FormResultService = _ts_decorate([
228
+ Injectable({
229
+ scope: Scope.REQUEST
230
+ }),
231
+ _ts_param(0, Inject('CACHE_INSTANCE')),
232
+ _ts_param(1, Inject(UtilsService)),
233
+ _ts_param(2, Inject(FormBuilderConfigService)),
234
+ _ts_param(3, Inject(FormBuilderDataSourceProvider)),
235
+ _ts_metadata("design:type", Function),
236
+ _ts_metadata("design:paramtypes", [
237
+ typeof HybridCache === "undefined" ? Object : HybridCache,
238
+ typeof UtilsService === "undefined" ? Object : UtilsService,
239
+ typeof FormBuilderConfigService === "undefined" ? Object : FormBuilderConfigService,
240
+ typeof FormBuilderDataSourceProvider === "undefined" ? Object : FormBuilderDataSourceProvider
241
+ ])
242
+ ], FormResultService);