@flusys/nestjs-form-builder 7.1.0 → 8.0.0-beta.1
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/config/index.d.ts +3 -3
- package/controllers/form-result.controller.d.ts +3 -3
- package/controllers/form.controller.d.ts +3 -3
- package/controllers/index.d.ts +2 -2
- package/docs/form-builder-swagger.config.d.ts +2 -2
- package/docs/index.d.ts +1 -1
- package/dtos/form.dto.d.ts +1 -1
- package/dtos/index.d.ts +2 -2
- package/entities/form-with-company.entity.d.ts +1 -1
- package/entities/form.entity.d.ts +1 -1
- package/entities/index.d.ts +7 -7
- package/enums/index.d.ts +1 -1
- package/fesm/262.js +1841 -0
- package/fesm/98.js +807 -0
- package/fesm/config/index.js +98 -3
- package/fesm/controllers/index.js +14 -2
- package/fesm/docs/index.js +171 -1
- package/fesm/dtos/index.js +571 -2
- package/fesm/entities/index.js +342 -10
- package/fesm/enums/index.js +38 -1
- package/fesm/index.js +482 -10
- package/fesm/interfaces/index.js +17 -3
- package/fesm/modules/index.js +178 -1
- package/fesm/runtime.js +85 -0
- package/fesm/services/index.js +35 -4
- package/index.d.ts +10 -10
- package/interfaces/form-builder-module.interface.d.ts +3 -3
- package/interfaces/form-result.interface.d.ts +1 -1
- package/interfaces/form.interface.d.ts +2 -2
- package/interfaces/index.d.ts +3 -3
- package/modules/form-builder.module.d.ts +2 -2
- package/modules/index.d.ts +1 -1
- package/package.json +26 -36
- package/services/form-builder-config.service.d.ts +4 -4
- package/services/form-builder-datasource.provider.d.ts +3 -3
- package/services/form-result.service.d.ts +6 -6
- package/services/form.service.d.ts +7 -7
- package/services/index.d.ts +4 -4
- package/utils/index.d.ts +2 -2
- package/utils/permission.utils.d.ts +1 -1
- package/cjs/config/event-actions.js +0 -33
- package/cjs/config/form-builder.constants.js +0 -13
- package/cjs/config/index.js +0 -20
- package/cjs/config/message-keys.js +0 -38
- package/cjs/controllers/form-result.controller.js +0 -281
- package/cjs/controllers/form-result.controller.spec.js +0 -257
- package/cjs/controllers/form.controller.js +0 -231
- package/cjs/controllers/form.controller.spec.js +0 -212
- package/cjs/controllers/index.js +0 -19
- package/cjs/docs/form-builder-swagger.config.js +0 -150
- package/cjs/docs/form-builder-swagger.config.spec.js +0 -75
- package/cjs/docs/index.js +0 -11
- package/cjs/dtos/form-result.dto.js +0 -248
- package/cjs/dtos/form.dto.js +0 -270
- package/cjs/dtos/index.js +0 -19
- package/cjs/entities/form-result.entity.js +0 -100
- package/cjs/entities/form-with-company.entity.js +0 -65
- package/cjs/entities/form.entity.js +0 -127
- package/cjs/entities/index.js +0 -52
- package/cjs/entities/index.spec.js +0 -18
- package/cjs/enums/form-access-type.enum.js +0 -19
- package/cjs/enums/index.js +0 -18
- package/cjs/index.js +0 -28
- package/cjs/interfaces/form-builder-module.interface.js +0 -4
- package/cjs/interfaces/form-result.interface.js +0 -4
- package/cjs/interfaces/form.interface.js +0 -4
- package/cjs/interfaces/index.js +0 -20
- package/cjs/modules/form-builder.module.js +0 -136
- package/cjs/modules/form-builder.module.spec.js +0 -133
- package/cjs/modules/index.js +0 -18
- package/cjs/services/form-builder-config.service.js +0 -69
- package/cjs/services/form-builder-config.service.spec.js +0 -121
- package/cjs/services/form-builder-datasource.provider.js +0 -125
- package/cjs/services/form-builder-datasource.provider.spec.js +0 -147
- package/cjs/services/form-result.service.js +0 -366
- package/cjs/services/form-result.service.spec.js +0 -823
- package/cjs/services/form.service.js +0 -341
- package/cjs/services/form.service.spec.js +0 -746
- package/cjs/services/index.js +0 -21
- package/cjs/utils/computed-field.utils.js +0 -199
- package/cjs/utils/computed-field.utils.spec.js +0 -1205
- package/cjs/utils/index.js +0 -19
- package/cjs/utils/permission.utils.js +0 -44
- package/cjs/utils/permission.utils.spec.js +0 -118
- package/fesm/config/event-actions.js +0 -12
- package/fesm/config/form-builder.constants.js +0 -3
- package/fesm/config/message-keys.js +0 -20
- package/fesm/controllers/form-result.controller.js +0 -271
- package/fesm/controllers/form-result.controller.spec.js +0 -253
- package/fesm/controllers/form.controller.js +0 -221
- package/fesm/controllers/form.controller.spec.js +0 -208
- package/fesm/docs/form-builder-swagger.config.js +0 -146
- package/fesm/docs/form-builder-swagger.config.spec.js +0 -71
- package/fesm/dtos/form-result.dto.js +0 -215
- package/fesm/dtos/form.dto.js +0 -243
- package/fesm/entities/form-result.entity.js +0 -90
- package/fesm/entities/form-with-company.entity.js +0 -55
- package/fesm/entities/form.entity.js +0 -117
- package/fesm/entities/index.spec.js +0 -14
- package/fesm/enums/form-access-type.enum.js +0 -9
- package/fesm/interfaces/form-builder-module.interface.js +0 -3
- package/fesm/interfaces/form-result.interface.js +0 -6
- package/fesm/interfaces/form.interface.js +0 -3
- package/fesm/modules/form-builder.module.js +0 -126
- package/fesm/modules/form-builder.module.spec.js +0 -129
- package/fesm/services/form-builder-config.service.js +0 -59
- package/fesm/services/form-builder-config.service.spec.js +0 -117
- package/fesm/services/form-builder-datasource.provider.js +0 -74
- package/fesm/services/form-builder-datasource.provider.spec.js +0 -143
- package/fesm/services/form-result.service.js +0 -356
- package/fesm/services/form-result.service.spec.js +0 -778
- package/fesm/services/form.service.js +0 -331
- package/fesm/services/form.service.spec.js +0 -701
- package/fesm/utils/computed-field.utils.js +0 -195
- package/fesm/utils/computed-field.utils.spec.js +0 -1201
- package/fesm/utils/index.js +0 -2
- package/fesm/utils/permission.utils.js +0 -42
- 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);
|