@flusys/nestjs-form-builder 4.1.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -386
- package/cjs/config/message-keys.js +7 -31
- package/cjs/controllers/form-result.controller.js +33 -16
- package/cjs/controllers/form.controller.js +40 -19
- package/cjs/docs/form-builder-swagger.config.js +3 -0
- package/cjs/dtos/form-result.dto.js +1 -17
- package/cjs/dtos/form.dto.js +1 -17
- package/cjs/entities/form-result.entity.js +1 -7
- package/cjs/entities/form.entity.js +2 -8
- package/cjs/services/form-result.service.js +3 -8
- package/cjs/services/form.service.js +0 -2
- package/config/message-keys.d.ts +5 -60
- package/controllers/form-result.controller.d.ts +4 -4
- package/controllers/form.controller.d.ts +16 -15
- package/dtos/form-result.dto.d.ts +0 -2
- package/dtos/form.dto.d.ts +0 -2
- package/entities/form-result.entity.d.ts +0 -1
- package/entities/form.entity.d.ts +0 -1
- package/fesm/config/message-keys.js +7 -29
- package/fesm/controllers/form-result.controller.js +33 -16
- package/fesm/controllers/form.controller.js +40 -19
- package/fesm/docs/form-builder-swagger.config.js +3 -0
- package/fesm/dtos/form-result.dto.js +1 -17
- package/fesm/dtos/form.dto.js +1 -17
- package/fesm/entities/form-result.entity.js +1 -7
- package/fesm/entities/form.entity.js +2 -8
- package/fesm/services/form-result.service.js +3 -8
- package/fesm/services/form.service.js +0 -2
- package/interfaces/form-result.interface.d.ts +0 -1
- package/interfaces/form.interface.d.ts +0 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,68 +1,9 @@
|
|
|
1
1
|
# @flusys/nestjs-form-builder
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Dynamic form management for NestJS — JSON schema storage, schema versioning, access control (PUBLIC / AUTHENTICATED / ACTION_GROUP), draft submissions, and a server-side computed fields engine.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@flusys/nestjs-form-builder)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[](https://nestjs.com/)
|
|
8
|
-
[](https://www.typescriptlang.org/)
|
|
9
|
-
[](https://nodejs.org/)
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Table of Contents
|
|
14
|
-
|
|
15
|
-
- [Overview](#overview)
|
|
16
|
-
- [Features](#features)
|
|
17
|
-
- [Compatibility](#compatibility)
|
|
18
|
-
- [Installation](#installation)
|
|
19
|
-
- [Quick Start](#quick-start)
|
|
20
|
-
- [Module Registration](#module-registration)
|
|
21
|
-
- [forRoot (Sync)](#forroot-sync)
|
|
22
|
-
- [forRootAsync (Factory)](#forrootasync-factory)
|
|
23
|
-
- [Configuration Reference](#configuration-reference)
|
|
24
|
-
- [Feature Toggles](#feature-toggles)
|
|
25
|
-
- [API Endpoints](#api-endpoints)
|
|
26
|
-
- [Entities](#entities)
|
|
27
|
-
- [Access Control](#access-control)
|
|
28
|
-
- [Schema Versioning](#schema-versioning)
|
|
29
|
-
- [Draft Support](#draft-support)
|
|
30
|
-
- [Computed Fields Engine](#computed-fields-engine)
|
|
31
|
-
- [Exported Services](#exported-services)
|
|
32
|
-
- [Programmatic Usage](#programmatic-usage)
|
|
33
|
-
- [Troubleshooting](#troubleshooting)
|
|
34
|
-
- [License](#license)
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## Overview
|
|
39
|
-
|
|
40
|
-
`@flusys/nestjs-form-builder` provides a complete dynamic form system where form structures are stored as JSON in the database. New forms can be created without any code changes. The module handles schema versioning (submissions snapshot the schema they were submitted against), per-form access control, draft submissions, and server-side computed field evaluation.
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Features
|
|
45
|
-
|
|
46
|
-
- **Database-driven forms** — JSON schema stored in PostgreSQL; no code changes for new forms
|
|
47
|
-
- **Schema versioning** — Schema version auto-increments on change; each submission captures the version it used
|
|
48
|
-
- **Access types** — `PUBLIC` (no auth), `AUTHENTICATED` (any logged-in user), `ACTION_GROUP` (IAM permission-based)
|
|
49
|
-
- **Draft submissions** — Users can save partial form data and finalize later
|
|
50
|
-
- **Computed fields engine** — Server-side arithmetic computed from submission data using rules and conditions
|
|
51
|
-
- **Slug-based lookup** — Forms accessible by a URL-friendly slug
|
|
52
|
-
- **Company scoping** — Optional `companyId` isolation via `FormWithCompany` entity
|
|
53
|
-
- **Multi-tenant** — Per-tenant DataSource isolation
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## Compatibility
|
|
58
|
-
|
|
59
|
-
| Package | Version |
|
|
60
|
-
|---------|---------|
|
|
61
|
-
| `@flusys/nestjs-core` | `^4.0.0` |
|
|
62
|
-
| `@flusys/nestjs-shared` | `^4.0.0` |
|
|
63
|
-
| `@nestjs/core` | `^11.0.0` |
|
|
64
|
-
| `typeorm` | `^0.3.0` |
|
|
65
|
-
| Node.js | `>= 18.x` |
|
|
66
7
|
|
|
67
8
|
---
|
|
68
9
|
|
|
@@ -74,377 +15,139 @@ npm install @flusys/nestjs-form-builder @flusys/nestjs-shared @flusys/nestjs-cor
|
|
|
74
15
|
|
|
75
16
|
---
|
|
76
17
|
|
|
77
|
-
##
|
|
18
|
+
## 1. Module Registration
|
|
19
|
+
|
|
20
|
+
### forRoot (sync)
|
|
78
21
|
|
|
79
|
-
|
|
22
|
+
#### Mode 1: Single Database
|
|
80
23
|
|
|
81
24
|
```typescript
|
|
82
|
-
import { Module } from '@nestjs/common';
|
|
83
25
|
import { FormBuilderModule } from '@flusys/nestjs-form-builder';
|
|
84
26
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
},
|
|
104
|
-
}),
|
|
105
|
-
],
|
|
106
|
-
})
|
|
107
|
-
export class AppModule {}
|
|
27
|
+
FormBuilderModule.forRoot({
|
|
28
|
+
global: true,
|
|
29
|
+
includeController: true,
|
|
30
|
+
bootstrapAppConfig: {
|
|
31
|
+
databaseMode: 'single',
|
|
32
|
+
enableCompanyFeature: false,
|
|
33
|
+
},
|
|
34
|
+
config: {
|
|
35
|
+
defaultDatabaseConfig: {
|
|
36
|
+
type: 'mysql',
|
|
37
|
+
host: process.env.DB_HOST,
|
|
38
|
+
port: Number(process.env.DB_PORT ?? 3306),
|
|
39
|
+
username: process.env.DB_USER,
|
|
40
|
+
password: process.env.DB_PASSWORD,
|
|
41
|
+
database: process.env.DB_NAME,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
108
45
|
```
|
|
109
46
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
## Module Registration
|
|
115
|
-
|
|
116
|
-
### forRoot (Sync)
|
|
47
|
+
#### Mode 2: Multi-Tenant
|
|
117
48
|
|
|
118
49
|
```typescript
|
|
119
50
|
FormBuilderModule.forRoot({
|
|
120
|
-
global
|
|
121
|
-
includeController
|
|
122
|
-
bootstrapAppConfig
|
|
123
|
-
databaseMode: '
|
|
124
|
-
enableCompanyFeature:
|
|
125
|
-
}
|
|
126
|
-
config
|
|
127
|
-
|
|
51
|
+
global: true,
|
|
52
|
+
includeController: true,
|
|
53
|
+
bootstrapAppConfig: {
|
|
54
|
+
databaseMode: 'multi-tenant',
|
|
55
|
+
enableCompanyFeature: true,
|
|
56
|
+
},
|
|
57
|
+
config: {
|
|
58
|
+
tenantDefaultDatabaseConfig: {
|
|
59
|
+
type: 'mysql',
|
|
60
|
+
host: process.env.TENANT_DB_HOST,
|
|
61
|
+
port: Number(process.env.TENANT_DB_PORT ?? 3306),
|
|
62
|
+
username: process.env.TENANT_DB_USER,
|
|
63
|
+
password: process.env.TENANT_DB_PASSWORD,
|
|
64
|
+
database: process.env.TENANT_DB_NAME,
|
|
65
|
+
},
|
|
66
|
+
tenants: [
|
|
67
|
+
{ id: 'tenant-a', database: 'tenant_a_db' },
|
|
68
|
+
{ id: 'tenant-b', database: 'tenant_b_db' },
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
});
|
|
128
72
|
```
|
|
129
73
|
|
|
130
|
-
### forRootAsync (
|
|
74
|
+
### forRootAsync (factory — recommended)
|
|
131
75
|
|
|
132
76
|
```typescript
|
|
133
|
-
import { ConfigService } from '@nestjs/config';
|
|
77
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
78
|
+
import { FormBuilderModule, ITenantDatabaseConfig } from '@flusys/nestjs-form-builder';
|
|
134
79
|
|
|
80
|
+
// Single database
|
|
135
81
|
FormBuilderModule.forRootAsync({
|
|
136
82
|
global: true,
|
|
137
|
-
includeController: true,
|
|
138
83
|
bootstrapAppConfig: {
|
|
139
84
|
databaseMode: 'single',
|
|
140
85
|
enableCompanyFeature: true,
|
|
141
86
|
},
|
|
142
87
|
imports: [ConfigModule],
|
|
143
|
-
useFactory: (
|
|
88
|
+
useFactory: (cfg: ConfigService) => ({
|
|
144
89
|
defaultDatabaseConfig: {
|
|
145
|
-
type: '
|
|
146
|
-
host:
|
|
147
|
-
port:
|
|
148
|
-
username:
|
|
149
|
-
password:
|
|
150
|
-
database:
|
|
90
|
+
type: 'mysql',
|
|
91
|
+
host: cfg.get('DB_HOST'),
|
|
92
|
+
port: cfg.get<number>('DB_PORT'),
|
|
93
|
+
username: cfg.get('DB_USER'),
|
|
94
|
+
password: cfg.get('DB_PASSWORD'),
|
|
95
|
+
database: cfg.get('DB_NAME'),
|
|
151
96
|
},
|
|
152
97
|
}),
|
|
153
98
|
inject: [ConfigService],
|
|
154
|
-
})
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
**Exported services** (available for injection after registration):
|
|
158
|
-
- `FormBuilderConfigService`
|
|
159
|
-
- `FormBuilderDataSourceProvider`
|
|
160
|
-
- `FormService`
|
|
161
|
-
- `FormResultService`
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
## Configuration Reference
|
|
99
|
+
});
|
|
166
100
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
101
|
+
// Multi-tenant
|
|
102
|
+
FormBuilderModule.forRootAsync({
|
|
103
|
+
global: true,
|
|
104
|
+
bootstrapAppConfig: {
|
|
105
|
+
databaseMode: 'multi-tenant',
|
|
106
|
+
enableCompanyFeature: true,
|
|
107
|
+
},
|
|
108
|
+
imports: [ConfigModule],
|
|
109
|
+
useFactory: (cfg: ConfigService) => ({
|
|
110
|
+
tenantDefaultDatabaseConfig: {
|
|
111
|
+
type: 'mysql',
|
|
112
|
+
host: cfg.get('TENANT_DB_HOST'),
|
|
113
|
+
port: cfg.get<number>('TENANT_DB_PORT'),
|
|
114
|
+
username: cfg.get('TENANT_DB_USER'),
|
|
115
|
+
password: cfg.get('TENANT_DB_PASSWORD'),
|
|
116
|
+
database: cfg.get('TENANT_DB_NAME'),
|
|
117
|
+
},
|
|
118
|
+
tenants: cfg.get<ITenantDatabaseConfig[]>('TENANTS'),
|
|
119
|
+
}),
|
|
120
|
+
inject: [ConfigService],
|
|
121
|
+
});
|
|
175
122
|
```
|
|
176
123
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
| Field | Type | Default | Effect |
|
|
180
|
-
|-------|------|---------|--------|
|
|
181
|
-
| `databaseMode` | `'single' \| 'multi-tenant'` | `'single'` | Controls DataSource resolution per request |
|
|
182
|
-
| `enableCompanyFeature` | `boolean` | `false` | Uses `FormWithCompany` entity when `true` |
|
|
183
|
-
|
|
184
|
-
---
|
|
185
|
-
|
|
186
|
-
## Feature Toggles
|
|
187
|
-
|
|
188
|
-
| Feature | Config | Default | Effect |
|
|
189
|
-
|---------|--------|---------|--------|
|
|
190
|
-
| Company scoping | `enableCompanyFeature: true` | `false` | All queries filtered by `companyId` from JWT; uses `FormWithCompany` entity |
|
|
191
|
-
| Multi-tenant | `databaseMode: 'multi-tenant'` | `'single'` | Per-tenant DataSource connections |
|
|
192
|
-
|
|
193
|
-
---
|
|
194
|
-
|
|
195
|
-
## API Endpoints
|
|
196
|
-
|
|
197
|
-
All endpoints use **POST**. Authentication depends on the form's `accessType`.
|
|
198
|
-
|
|
199
|
-
### Forms — `POST /form-builder/form/*`
|
|
200
|
-
|
|
201
|
-
| Endpoint | Auth | Description |
|
|
202
|
-
|----------|------|-------------|
|
|
203
|
-
| `POST /form-builder/form/insert` | `form.create` | Create a new form with JSON schema |
|
|
204
|
-
| `POST /form-builder/form/get-all` | `form.read` | List all forms (admin) |
|
|
205
|
-
| `POST /form-builder/form/get/:id` | `form.read` | Get form by ID |
|
|
206
|
-
| `POST /form-builder/form/get-by-slug/:slug` | Varies* | Get form by URL slug |
|
|
207
|
-
| `POST /form-builder/form/update` | `form.update` | Update form (increments schema version if schema changes) |
|
|
208
|
-
| `POST /form-builder/form/delete` | `form.delete` | Delete form |
|
|
209
|
-
| `POST /form-builder/form/publish` | `form.update` | Publish draft form |
|
|
210
|
-
| `POST /form-builder/form/unpublish` | `form.update` | Unpublish form |
|
|
211
|
-
|
|
212
|
-
*`get-by-slug` respects the form's `accessType` — PUBLIC forms are accessible without auth.
|
|
213
|
-
|
|
214
|
-
### Form Results (Submissions) — `POST /form-builder/result/*`
|
|
215
|
-
|
|
216
|
-
| Endpoint | Auth | Description |
|
|
217
|
-
|----------|------|-------------|
|
|
218
|
-
| `POST /form-builder/result/insert` | Varies* | Submit a form result |
|
|
219
|
-
| `POST /form-builder/result/get-all` | `form-result.read` | List all submissions |
|
|
220
|
-
| `POST /form-builder/result/get/:id` | `form-result.read` | Get submission by ID |
|
|
221
|
-
| `POST /form-builder/result/update` | `form-result.update` | Update a submission |
|
|
222
|
-
| `POST /form-builder/result/delete` | `form-result.delete` | Delete a submission |
|
|
223
|
-
| `POST /form-builder/result/save-draft` | Varies* | Save a partial draft submission |
|
|
224
|
-
| `POST /form-builder/result/finalize` | Varies* | Convert draft to final submission |
|
|
225
|
-
| `POST /form-builder/result/get-my-results` | JWT | Get current user's own submissions |
|
|
226
|
-
|
|
227
|
-
*Depends on the form's `accessType`.
|
|
124
|
+
**Exported services** (available for injection after registration):
|
|
125
|
+
`FormService`, `FormResultService`, `FormBuilderConfigService`, `FormBuilderDataSourceProvider`
|
|
228
126
|
|
|
229
127
|
---
|
|
230
128
|
|
|
231
|
-
## Entities
|
|
232
|
-
|
|
233
|
-
### Core Entities (always registered)
|
|
234
|
-
|
|
235
|
-
| Entity | Table | Description |
|
|
236
|
-
|--------|-------|-------------|
|
|
237
|
-
| `Form` | `form_builder_form` | Form definition with JSON schema, access type, version |
|
|
238
|
-
| `FormResult` | `form_builder_result` | Submission data + schema version snapshot |
|
|
239
|
-
|
|
240
|
-
### Company Feature Entities (`enableCompanyFeature: true`)
|
|
241
|
-
|
|
242
|
-
| Entity | Table | Description |
|
|
243
|
-
|--------|-------|-------------|
|
|
244
|
-
| `FormWithCompany` | `form_builder_form` | Same as Form + `companyId` |
|
|
129
|
+
## 2. Entities
|
|
245
130
|
|
|
246
131
|
```typescript
|
|
247
|
-
import {
|
|
132
|
+
import { getFormBuilderEntitiesByConfig } from '@flusys/nestjs-form-builder/entities';
|
|
248
133
|
|
|
249
134
|
TypeOrmModule.forRoot({
|
|
250
135
|
entities: [
|
|
251
|
-
...
|
|
136
|
+
...getFormBuilderEntitiesByConfig(false), // match enableCompanyFeature in bootstrapAppConfig
|
|
252
137
|
],
|
|
253
|
-
})
|
|
138
|
+
});
|
|
254
139
|
```
|
|
255
140
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
Each form has an `accessType` field that controls who can access it:
|
|
261
|
-
|
|
262
|
-
| Access Type | Description |
|
|
263
|
-
|-------------|-------------|
|
|
264
|
-
| `PUBLIC` | No authentication required. Anyone can view and submit. |
|
|
265
|
-
| `AUTHENTICATED` | Requires valid JWT token. Any logged-in user can submit. |
|
|
266
|
-
| `ACTION_GROUP` | Requires a specific IAM action permission (from `nestjs-iam`). |
|
|
141
|
+
| `enableCompanyFeature` | Entities registered |
|
|
142
|
+
| ---------------------- | ------------------------------- |
|
|
143
|
+
| `false` | `Form`, `FormResult` |
|
|
144
|
+
| `true` | `FormWithCompany`, `FormResult` |
|
|
267
145
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
```json
|
|
271
|
-
POST /form-builder/form/insert
|
|
272
|
-
{
|
|
273
|
-
"name": "Employee Survey",
|
|
274
|
-
"slug": "employee-survey",
|
|
275
|
-
"accessType": "AUTHENTICATED",
|
|
276
|
-
"schema": { /* JSON schema */ },
|
|
277
|
-
"isPublished": true
|
|
278
|
-
}
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
**ACTION_GROUP form:**
|
|
282
|
-
|
|
283
|
-
```json
|
|
284
|
-
{
|
|
285
|
-
"name": "Finance Report",
|
|
286
|
-
"slug": "finance-report",
|
|
287
|
-
"accessType": "ACTION_GROUP",
|
|
288
|
-
"requiredAction": "finance.submit-report",
|
|
289
|
-
"schema": { /* JSON schema */ }
|
|
290
|
-
}
|
|
291
|
-
```
|
|
146
|
+
The service selects the correct entity automatically at request time — no manual switching needed.
|
|
292
147
|
|
|
293
148
|
---
|
|
294
149
|
|
|
295
|
-
## Schema Versioning
|
|
296
|
-
|
|
297
|
-
When you update a form's `schema` field, the `schemaVersion` counter is automatically incremented. Every form submission stores the `schemaVersion` at the time of submission.
|
|
298
|
-
|
|
299
|
-
This allows:
|
|
300
|
-
- Viewing historical submissions against the exact schema that was active
|
|
301
|
-
- Running analytics on form data across schema versions
|
|
302
|
-
- Preventing schema changes from breaking existing submission records
|
|
303
|
-
|
|
304
|
-
```json
|
|
305
|
-
// Version 1 schema
|
|
306
|
-
{ "fields": [{ "name": "firstName", "type": "text" }] }
|
|
307
|
-
|
|
308
|
-
// After update (version 2) - new field added
|
|
309
|
-
{ "fields": [{ "name": "firstName", "type": "text" }, { "name": "lastName", "type": "text" }] }
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
Historical submissions still reference `schemaVersion: 1` so you know they were submitted before the `lastName` field existed.
|
|
313
|
-
|
|
314
|
-
---
|
|
315
|
-
|
|
316
|
-
## Draft Support
|
|
317
|
-
|
|
318
|
-
Users can save partial form data as a draft and finalize later:
|
|
319
|
-
|
|
320
|
-
```json
|
|
321
|
-
// Save draft (no validation on all required fields)
|
|
322
|
-
POST /form-builder/result/save-draft
|
|
323
|
-
{
|
|
324
|
-
"formId": "uuid",
|
|
325
|
-
"data": { "firstName": "John" }
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Finalize (validates all required fields)
|
|
329
|
-
POST /form-builder/result/finalize
|
|
330
|
-
{
|
|
331
|
-
"draftId": "uuid",
|
|
332
|
-
"data": { "firstName": "John", "lastName": "Doe" }
|
|
333
|
-
}
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
Draft submissions have `isDraft: true`. Finalized submissions have `isDraft: false`. Users can retrieve their own drafts via `POST /form-builder/result/get-my-results`.
|
|
337
|
-
|
|
338
|
-
---
|
|
339
|
-
|
|
340
|
-
## Computed Fields Engine
|
|
341
|
-
|
|
342
|
-
Server-side computed fields are calculated from submission data using declarative rules:
|
|
343
|
-
|
|
344
|
-
```json
|
|
345
|
-
POST /form-builder/form/insert
|
|
346
|
-
{
|
|
347
|
-
"name": "Loan Calculator",
|
|
348
|
-
"schema": {
|
|
349
|
-
"fields": [
|
|
350
|
-
{ "name": "principal", "type": "number", "label": "Loan Amount" },
|
|
351
|
-
{ "name": "rate", "type": "number", "label": "Interest Rate (%)" },
|
|
352
|
-
{ "name": "years", "type": "number", "label": "Term (Years)" }
|
|
353
|
-
],
|
|
354
|
-
"computedFields": [
|
|
355
|
-
{
|
|
356
|
-
"name": "monthlyPayment",
|
|
357
|
-
"label": "Monthly Payment",
|
|
358
|
-
"expression": "(principal * (rate / 1200)) / (1 - Math.pow(1 + rate / 1200, -years * 12))",
|
|
359
|
-
"dependsOn": ["principal", "rate", "years"]
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
"name": "totalPayment",
|
|
363
|
-
"label": "Total Payment",
|
|
364
|
-
"expression": "monthlyPayment * years * 12",
|
|
365
|
-
"dependsOn": ["monthlyPayment", "years"]
|
|
366
|
-
}
|
|
367
|
-
]
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
Computed values are evaluated server-side at submission time and stored with the result.
|
|
373
|
-
|
|
374
|
-
---
|
|
375
|
-
|
|
376
|
-
## Exported Services
|
|
377
|
-
|
|
378
|
-
| Service | Description |
|
|
379
|
-
|---------|-------------|
|
|
380
|
-
| `FormService` | Form CRUD, slug lookup, publish/unpublish |
|
|
381
|
-
| `FormResultService` | Submission CRUD, draft management, computed field evaluation |
|
|
382
|
-
| `FormBuilderConfigService` | Exposes runtime config and feature flags |
|
|
383
|
-
| `FormBuilderDataSourceProvider` | Dynamic DataSource resolution per request |
|
|
384
|
-
|
|
385
|
-
---
|
|
386
|
-
|
|
387
|
-
## Programmatic Usage
|
|
388
|
-
|
|
389
|
-
```typescript
|
|
390
|
-
import { FormService, FormResultService } from '@flusys/nestjs-form-builder';
|
|
391
|
-
|
|
392
|
-
@Injectable()
|
|
393
|
-
export class SurveyService {
|
|
394
|
-
constructor(
|
|
395
|
-
@Inject(FormService) private readonly formService: FormService,
|
|
396
|
-
@Inject(FormResultService) private readonly resultService: FormResultService,
|
|
397
|
-
) {}
|
|
398
|
-
|
|
399
|
-
async getPublicForm(slug: string) {
|
|
400
|
-
return this.formService.getBySlug(slug);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
async submitForm(formId: string, data: Record<string, any>, userId: string) {
|
|
404
|
-
return this.resultService.submit({ formId, data, userId });
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
async getFormResults(formId: string) {
|
|
408
|
-
return this.resultService.getAll({ filter: { formId } }, null);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
---
|
|
414
|
-
|
|
415
|
-
## Troubleshooting
|
|
416
|
-
|
|
417
|
-
**`Form not found` for a public form**
|
|
418
|
-
|
|
419
|
-
Check that the form is published (`isPublished: true`). Unpublished forms are not returned by the public API.
|
|
420
|
-
|
|
421
|
-
---
|
|
422
|
-
|
|
423
|
-
**Draft finalization fails validation**
|
|
424
|
-
|
|
425
|
-
The `finalize` endpoint applies full schema validation. Ensure all required fields in the form schema are present in the submitted data.
|
|
426
|
-
|
|
427
|
-
---
|
|
428
|
-
|
|
429
|
-
**Computed fields not appearing in submission**
|
|
430
|
-
|
|
431
|
-
Computed fields are evaluated at submission time. Check that `dependsOn` field names exactly match the form field `name` values (case-sensitive).
|
|
432
|
-
|
|
433
|
-
---
|
|
434
|
-
|
|
435
|
-
**`No metadata for entity`**
|
|
436
|
-
|
|
437
|
-
Register entities in your `TypeOrmModule`:
|
|
438
|
-
```typescript
|
|
439
|
-
entities: [...FormBuilderModule.getEntities({ enableCompanyFeature: false })]
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
---
|
|
443
150
|
|
|
444
151
|
## License
|
|
445
152
|
|
|
446
153
|
MIT © FLUSYS
|
|
447
|
-
|
|
448
|
-
---
|
|
449
|
-
|
|
450
|
-
> Part of the **FLUSYS** framework — a full-stack monorepo powering Angular 21 + NestJS 11 applications.
|
|
@@ -10,9 +10,6 @@ function _export(target, all) {
|
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
12
|
_export(exports, {
|
|
13
|
-
get FORM_BUILDER_MODULE_MESSAGES () {
|
|
14
|
-
return FORM_BUILDER_MODULE_MESSAGES;
|
|
15
|
-
},
|
|
16
13
|
get FORM_MESSAGES () {
|
|
17
14
|
return FORM_MESSAGES;
|
|
18
15
|
},
|
|
@@ -21,42 +18,21 @@ _export(exports, {
|
|
|
21
18
|
}
|
|
22
19
|
});
|
|
23
20
|
const FORM_MESSAGES = {
|
|
24
|
-
CREATE_SUCCESS: 'form.create.success',
|
|
25
|
-
CREATE_MANY_SUCCESS: 'form.create.many.success',
|
|
26
|
-
GET_SUCCESS: 'form.get.success',
|
|
27
|
-
GET_ALL_SUCCESS: 'form.get.all.success',
|
|
28
|
-
UPDATE_SUCCESS: 'form.update.success',
|
|
29
|
-
UPDATE_MANY_SUCCESS: 'form.update.many.success',
|
|
30
|
-
DELETE_SUCCESS: 'form.delete.success',
|
|
31
|
-
RESTORE_SUCCESS: 'form.restore.success',
|
|
32
21
|
NOT_FOUND: 'form.not.found',
|
|
33
|
-
SCHEMA_SUCCESS: 'form.schema.success',
|
|
34
|
-
PUBLISH_SUCCESS: 'form.publish.success',
|
|
35
|
-
UNPUBLISH_SUCCESS: 'form.unpublish.success',
|
|
36
|
-
DUPLICATE_SUCCESS: 'form.duplicate.success',
|
|
37
22
|
NOT_PUBLIC: 'form.not.public',
|
|
38
23
|
AUTH_REQUIRED: 'form.auth.required',
|
|
39
24
|
ACCESS_DENIED: 'form.access.denied',
|
|
40
25
|
INVALID_ACCESS_TYPE: 'form.invalid.access.type',
|
|
41
|
-
PERMISSION_CHECK_FAILED: 'form.permission.check.failed'
|
|
26
|
+
PERMISSION_CHECK_FAILED: 'form.permission.check.failed',
|
|
27
|
+
GET_SUCCESS: 'form.get.success',
|
|
28
|
+
GET_ACCESS_INFO_SUCCESS: 'form.get.access.info.success'
|
|
42
29
|
};
|
|
43
30
|
const FORM_RESULT_MESSAGES = {
|
|
44
|
-
CREATE_SUCCESS: 'form.result.create.success',
|
|
45
|
-
CREATE_MANY_SUCCESS: 'form.result.create.many.success',
|
|
46
|
-
GET_SUCCESS: 'form.result.get.success',
|
|
47
31
|
GET_ALL_SUCCESS: 'form.result.get.all.success',
|
|
48
|
-
UPDATE_SUCCESS: 'form.result.update.success',
|
|
49
|
-
UPDATE_MANY_SUCCESS: 'form.result.update.many.success',
|
|
50
|
-
DELETE_SUCCESS: 'form.result.delete.success',
|
|
51
|
-
RESTORE_SUCCESS: 'form.result.restore.success',
|
|
52
32
|
NOT_FOUND: 'form.result.not.found',
|
|
53
|
-
SUBMIT_SUCCESS: 'form.result.submit.success',
|
|
54
|
-
EXPORT_SUCCESS: 'form.result.export.success',
|
|
55
|
-
STATS_SUCCESS: 'form.result.stats.success',
|
|
56
33
|
HAS_SUBMITTED_SUCCESS: 'form.result.has.submitted.success',
|
|
57
|
-
HAS_NOT_SUBMITTED_SUCCESS: 'form.result.has.not.submitted.success'
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
FORM_RESULT: FORM_RESULT_MESSAGES
|
|
34
|
+
HAS_NOT_SUBMITTED_SUCCESS: 'form.result.has.not.submitted.success',
|
|
35
|
+
SUBMIT_SUCCESS: 'form.result.submit.success',
|
|
36
|
+
DRAFT_GET_SUCCESS: 'form.result.draft.get.success',
|
|
37
|
+
DRAFT_UPDATE_SUCCESS: 'form.result.draft.update.success'
|
|
62
38
|
};
|
|
@@ -9,12 +9,12 @@ Object.defineProperty(exports, "FormResultController", {
|
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
const _classes = require("@flusys/nestjs-shared/classes");
|
|
12
|
-
const _config = require("../config");
|
|
13
12
|
const _decorators = require("@flusys/nestjs-shared/decorators");
|
|
14
13
|
const _guards = require("@flusys/nestjs-shared/guards");
|
|
15
14
|
const _interfaces = require("@flusys/nestjs-shared/interfaces");
|
|
16
15
|
const _common = require("@nestjs/common");
|
|
17
16
|
const _swagger = require("@nestjs/swagger");
|
|
17
|
+
const _config = require("../config");
|
|
18
18
|
const _dtos = require("../dtos");
|
|
19
19
|
const _formresultservice = require("../services/form-result.service");
|
|
20
20
|
function _define_property(obj, key, value) {
|
|
@@ -45,7 +45,7 @@ function _ts_param(paramIndex, decorator) {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
let FormResultController = class FormResultController extends (0, _classes.createApiController)(_dtos.CreateFormResultDto, _dtos.UpdateFormResultDto, _dtos.FormResultResponseDto, {
|
|
48
|
-
entityName: '
|
|
48
|
+
entityName: 'form.result',
|
|
49
49
|
security: {
|
|
50
50
|
insert: {
|
|
51
51
|
level: 'permission',
|
|
@@ -95,24 +95,41 @@ let FormResultController = class FormResultController extends (0, _classes.creat
|
|
|
95
95
|
/**
|
|
96
96
|
* Submit form with authentication
|
|
97
97
|
*/ async submitForm(dto, user) {
|
|
98
|
-
|
|
98
|
+
const data = await this.formResultService.submitForm(dto, user, false);
|
|
99
|
+
return {
|
|
100
|
+
success: true,
|
|
101
|
+
message: 'Form submitted successfully',
|
|
102
|
+
messageKey: _config.FORM_RESULT_MESSAGES.SUBMIT_SUCCESS,
|
|
103
|
+
data
|
|
104
|
+
};
|
|
99
105
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
async submitPublicForm(dto) {
|
|
107
|
+
const data = await this.formResultService.submitForm(dto, null, true);
|
|
108
|
+
return {
|
|
109
|
+
success: true,
|
|
110
|
+
message: 'Form submitted successfully',
|
|
111
|
+
messageKey: _config.FORM_RESULT_MESSAGES.SUBMIT_SUCCESS,
|
|
112
|
+
data
|
|
113
|
+
};
|
|
105
114
|
}
|
|
106
115
|
// Draft Endpoints
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
async getMyDraft(dto, user) {
|
|
117
|
+
const data = await this.formResultService.getMyDraft(dto.formId, user);
|
|
118
|
+
return {
|
|
119
|
+
success: true,
|
|
120
|
+
message: 'Draft retrieved',
|
|
121
|
+
messageKey: _config.FORM_RESULT_MESSAGES.DRAFT_GET_SUCCESS,
|
|
122
|
+
data
|
|
123
|
+
};
|
|
111
124
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
125
|
+
async updateDraft(dto, user) {
|
|
126
|
+
const data = await this.formResultService.updateDraft(dto.draftId, dto, user);
|
|
127
|
+
return {
|
|
128
|
+
success: true,
|
|
129
|
+
message: 'Draft updated successfully',
|
|
130
|
+
messageKey: _config.FORM_RESULT_MESSAGES.DRAFT_UPDATE_SUCCESS,
|
|
131
|
+
data
|
|
132
|
+
};
|
|
116
133
|
}
|
|
117
134
|
// Query Endpoints
|
|
118
135
|
/**
|