@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.
- package/cjs/config/form-builder-config.service.js +79 -0
- package/cjs/config/form-builder.constants.js +22 -0
- package/cjs/config/index.js +19 -0
- package/cjs/controllers/form-result.controller.js +131 -0
- package/cjs/controllers/form.controller.js +177 -0
- package/cjs/controllers/index.js +19 -0
- package/cjs/dtos/form-result.dto.js +309 -0
- package/cjs/dtos/form.dto.js +396 -0
- package/cjs/dtos/index.js +19 -0
- package/cjs/entities/form-base.entity.js +113 -0
- package/cjs/entities/form-result.entity.js +106 -0
- package/cjs/entities/form-with-company.entity.js +55 -0
- package/cjs/entities/form.entity.js +25 -0
- package/cjs/entities/index.js +40 -0
- package/cjs/enums/form-access-type.enum.js +19 -0
- package/cjs/enums/index.js +18 -0
- package/cjs/index.js +26 -0
- package/cjs/interfaces/form-builder-module.interface.js +4 -0
- package/cjs/interfaces/form-result.interface.js +9 -0
- package/cjs/interfaces/form.interface.js +4 -0
- package/cjs/interfaces/index.js +20 -0
- package/cjs/modules/form-builder.module.js +162 -0
- package/cjs/modules/index.js +18 -0
- package/cjs/services/form-builder-datasource.provider.js +213 -0
- package/cjs/services/form-result.service.js +252 -0
- package/cjs/services/form.service.js +335 -0
- package/cjs/services/index.js +20 -0
- package/config/form-builder-config.service.d.ts +10 -0
- package/config/form-builder.constants.d.ts +2 -0
- package/config/index.d.ts +2 -0
- package/controllers/form-result.controller.d.ts +24 -0
- package/controllers/form.controller.d.ts +23 -0
- package/controllers/index.d.ts +2 -0
- package/dtos/form-result.dto.d.ts +48 -0
- package/dtos/form.dto.d.ts +63 -0
- package/dtos/index.d.ts +2 -0
- package/entities/form-base.entity.d.ts +13 -0
- package/entities/form-result.entity.d.ts +11 -0
- package/entities/form-with-company.entity.d.ts +4 -0
- package/entities/form.entity.d.ts +3 -0
- package/entities/index.d.ts +5 -0
- package/enums/form-access-type.enum.d.ts +5 -0
- package/enums/index.d.ts +1 -0
- package/fesm/config/form-builder-config.service.js +69 -0
- package/fesm/config/form-builder.constants.js +6 -0
- package/fesm/config/index.js +2 -0
- package/fesm/controllers/form-result.controller.js +121 -0
- package/fesm/controllers/form.controller.js +167 -0
- package/fesm/controllers/index.js +2 -0
- package/fesm/dtos/form-result.dto.js +281 -0
- package/fesm/dtos/form.dto.js +370 -0
- package/fesm/dtos/index.js +2 -0
- package/fesm/entities/form-base.entity.js +106 -0
- package/fesm/entities/form-result.entity.js +96 -0
- package/fesm/entities/form-with-company.entity.js +45 -0
- package/fesm/entities/form.entity.js +15 -0
- package/fesm/entities/index.js +29 -0
- package/fesm/enums/form-access-type.enum.js +9 -0
- package/fesm/enums/index.js +1 -0
- package/fesm/index.js +16 -0
- package/fesm/interfaces/form-builder-module.interface.js +3 -0
- package/fesm/interfaces/form-result.interface.js +6 -0
- package/fesm/interfaces/form.interface.js +3 -0
- package/fesm/interfaces/index.js +3 -0
- package/fesm/modules/form-builder.module.js +152 -0
- package/fesm/modules/index.js +1 -0
- package/fesm/services/form-builder-datasource.provider.js +162 -0
- package/fesm/services/form-result.service.js +242 -0
- package/fesm/services/form.service.js +325 -0
- package/fesm/services/index.js +3 -0
- package/index.d.ts +8 -0
- package/interfaces/form-builder-module.interface.d.ts +23 -0
- package/interfaces/form-result.interface.d.ts +18 -0
- package/interfaces/form.interface.d.ts +29 -0
- package/interfaces/index.d.ts +3 -0
- package/modules/form-builder.module.d.ts +9 -0
- package/modules/index.d.ts +1 -0
- package/package.json +84 -0
- package/services/form-builder-datasource.provider.d.ts +25 -0
- package/services/form-result.service.d.ts +41 -0
- package/services/form.service.d.ts +45 -0
- 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,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);
|