@flusys/nestjs-form-builder 8.0.3 → 8.1.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 +47 -6
- package/config/message-keys.d.ts +9 -0
- package/controllers/form-email-auth.controller.d.ts +11 -0
- package/controllers/form-result.controller.d.ts +6 -3
- package/controllers/form.controller.d.ts +5 -2
- package/controllers/index.d.ts +1 -0
- package/dtos/form-email-auth.dto.d.ts +20 -0
- package/dtos/form-result.dto.d.ts +2 -0
- package/dtos/form.dto.d.ts +3 -0
- package/dtos/index.d.ts +1 -0
- package/entities/form-email-verification.entity.d.ts +11 -0
- package/entities/form-result.entity.d.ts +1 -0
- package/entities/index.d.ts +4 -2
- package/enums/form-access-type.enum.d.ts +2 -1
- package/fesm/{262.js → 432.js} +561 -37
- package/fesm/{98.js → 720.js} +364 -36
- package/fesm/config/index.js +11 -2
- package/fesm/controllers/index.js +6 -5
- package/fesm/dtos/index.js +170 -4
- package/fesm/entities/index.js +136 -6
- package/fesm/enums/index.js +1 -0
- package/fesm/index.js +66 -21
- package/fesm/interfaces/index.js +23 -2
- package/fesm/modules/index.js +22 -12
- package/fesm/services/index.js +8 -4
- package/interfaces/form-builder-email-provider.interface.d.ts +4 -0
- package/interfaces/form-builder-module.interface.d.ts +3 -1
- package/interfaces/form-result.interface.d.ts +1 -0
- package/interfaces/index.d.ts +1 -0
- package/package.json +3 -3
- package/services/form-email-auth.service.d.ts +29 -0
- package/services/form-result.service.d.ts +10 -1
- package/services/form.service.d.ts +2 -0
- package/services/index.d.ts +1 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @flusys/nestjs-form-builder
|
|
2
2
|
|
|
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.
|
|
3
|
+
Dynamic form management for NestJS — JSON schema storage, schema versioning, access control (PUBLIC / AUTHENTICATED / ACTION_GROUP / EMAIL_VERIFIED), 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)
|
|
@@ -122,7 +122,7 @@ FormBuilderModule.forRootAsync({
|
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
**Exported services** (available for injection after registration):
|
|
125
|
-
`FormService`, `FormResultService`, `FormBuilderConfigService`, `FormBuilderDataSourceProvider`
|
|
125
|
+
`FormService`, `FormResultService`, `FormEmailAuthService`, `FormBuilderConfigService`, `FormBuilderDataSourceProvider`
|
|
126
126
|
|
|
127
127
|
---
|
|
128
128
|
|
|
@@ -138,15 +138,56 @@ TypeOrmModule.forRoot({
|
|
|
138
138
|
});
|
|
139
139
|
```
|
|
140
140
|
|
|
141
|
-
| `enableCompanyFeature` | Entities registered
|
|
142
|
-
| ---------------------- |
|
|
143
|
-
| `false` | `Form`, `FormResult`
|
|
144
|
-
| `true` | `FormWithCompany`, `FormResult` |
|
|
141
|
+
| `enableCompanyFeature` | Entities registered |
|
|
142
|
+
| ---------------------- | --------------------------------------------------------- |
|
|
143
|
+
| `false` | `Form`, `FormResult`, `FormEmailVerification` |
|
|
144
|
+
| `true` | `FormWithCompany`, `FormResult`, `FormEmailVerification` |
|
|
145
145
|
|
|
146
146
|
The service selects the correct entity automatically at request time — no manual switching needed.
|
|
147
147
|
|
|
148
148
|
---
|
|
149
149
|
|
|
150
|
+
## 3. EMAIL_VERIFIED Access Type
|
|
151
|
+
|
|
152
|
+
A fourth `FormAccessType`, alongside `PUBLIC`/`AUTHENTICATED`/`ACTION_GROUP`, that lets an anonymous visitor prove ownership of an email via a one-time code before filling out an otherwise-public form. An already-logged-in visitor is auto-identified by their account email and skips the code entirely. The resolved email becomes a real server-side identity (`FormResult.submitterEmail`) that the existing `responseMode` form setting (`single` | `multiple`, in `schema.settings`) is enforced against — `single` blocks a second submission from the same email, `multiple` allows repeats without re-verifying (a 30-day sliding access-token session).
|
|
153
|
+
|
|
154
|
+
**Wiring an email sender** — like `AUTH_EMAIL_PROVIDER`, this is a Provider Interface: without it, `EMAIL_VERIFIED` forms simply can't send codes.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { FORM_BUILDER_EMAIL_PROVIDER, type IFormBuilderEmailProvider } from '@flusys/nestjs-form-builder/interfaces';
|
|
158
|
+
import { EmailSendService } from '@flusys/nestjs-email';
|
|
159
|
+
|
|
160
|
+
const formBuilderEmailProvider: Provider = {
|
|
161
|
+
provide: FORM_BUILDER_EMAIL_PROVIDER,
|
|
162
|
+
useFactory: (emailSendService: EmailSendService): IFormBuilderEmailProvider => ({
|
|
163
|
+
async sendOtpEmail(email, code, formTitle, expiryMinutes) {
|
|
164
|
+
await emailSendService.sendTemplateEmail({
|
|
165
|
+
templateSlug: 'form-email-otp',
|
|
166
|
+
to: email,
|
|
167
|
+
variables: { code, formTitle, expiryMinutes: String(expiryMinutes) },
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
}),
|
|
171
|
+
inject: [EmailSendService],
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
FormBuilderModule.forRoot({
|
|
175
|
+
// ...
|
|
176
|
+
providers: [formBuilderEmailProvider],
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**New endpoints:**
|
|
181
|
+
|
|
182
|
+
| Endpoint | Guard | Purpose |
|
|
183
|
+
| --- | --- | --- |
|
|
184
|
+
| `POST form-builder/form-email-auth/request-otp` | `@Public()`, throttled 3/5min | Send a 6-digit code to an email for a given `EMAIL_VERIFIED` form |
|
|
185
|
+
| `POST form-builder/form-email-auth/verify-otp` | `@Public()`, throttled 10/min | Verify the code, returns an opaque `accessToken` (30-day sliding expiry) |
|
|
186
|
+
| `POST form-builder/form/email-verified/:id` | `OptionalJwtGuard` | Load the form schema; resolves identity from a JWT if present, else `{ accessToken }` in the body |
|
|
187
|
+
| `POST form-builder/result/submit-public` | `OptionalJwtGuard` | Now also accepts `EMAIL_VERIFIED` forms — pass `{ accessToken }` for anonymous visitors, nothing for JWT-authenticated ones |
|
|
188
|
+
| `POST form-builder/result/has-submitted-email` | `OptionalJwtGuard` | Anonymous-path counterpart to `has-submitted`, resolving identity the same way |
|
|
189
|
+
|
|
190
|
+
---
|
|
150
191
|
|
|
151
192
|
## License
|
|
152
193
|
|
package/config/message-keys.d.ts
CHANGED
|
@@ -7,6 +7,14 @@ export declare const FORM_MESSAGES: {
|
|
|
7
7
|
readonly PERMISSION_CHECK_FAILED: "form.permission.check.failed";
|
|
8
8
|
readonly GET_SUCCESS: "form.get.success";
|
|
9
9
|
readonly GET_ACCESS_INFO_SUCCESS: "form.get.access.info.success";
|
|
10
|
+
readonly EMAIL_NOT_CONFIGURED: "form.email.not.configured";
|
|
11
|
+
readonly EMAIL_VERIFICATION_REQUIRED: "form.email.verification.required";
|
|
12
|
+
readonly OTP_SENT: "form.email.otp.sent";
|
|
13
|
+
readonly OTP_SEND_FAILED: "form.email.otp.send.failed";
|
|
14
|
+
readonly OTP_INVALID: "form.email.otp.invalid";
|
|
15
|
+
readonly OTP_EXPIRED: "form.email.otp.expired";
|
|
16
|
+
readonly OTP_TOO_MANY_ATTEMPTS: "form.email.otp.too.many.attempts";
|
|
17
|
+
readonly OTP_VERIFIED: "form.email.otp.verified";
|
|
10
18
|
};
|
|
11
19
|
export declare const FORM_RESULT_MESSAGES: {
|
|
12
20
|
readonly GET_ALL_SUCCESS: "form.result.get.all.success";
|
|
@@ -16,4 +24,5 @@ export declare const FORM_RESULT_MESSAGES: {
|
|
|
16
24
|
readonly SUBMIT_SUCCESS: "form.result.submit.success";
|
|
17
25
|
readonly DRAFT_GET_SUCCESS: "form.result.draft.get.success";
|
|
18
26
|
readonly DRAFT_UPDATE_SUCCESS: "form.result.draft.update.success";
|
|
27
|
+
readonly ALREADY_SUBMITTED: "form.result.already.submitted";
|
|
19
28
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MessageResponseDto, SingleResponseDto } from '@flusys/nestjs-shared/dtos';
|
|
2
|
+
import { RequestFormEmailOtpDto, VerifyFormEmailOtpDto, VerifyOtpResponseDto } from '../dtos/index.js';
|
|
3
|
+
import { FormEmailAuthService } from '../services/form-email-auth.service.js';
|
|
4
|
+
import { FormService } from '../services/form.service.js';
|
|
5
|
+
export declare class FormEmailAuthController {
|
|
6
|
+
private readonly formEmailAuthService;
|
|
7
|
+
private readonly formService;
|
|
8
|
+
constructor(formEmailAuthService: FormEmailAuthService, formService: FormService);
|
|
9
|
+
requestOtp(dto: RequestFormEmailOtpDto): Promise<MessageResponseDto>;
|
|
10
|
+
verifyOtp(dto: VerifyFormEmailOtpDto): Promise<SingleResponseDto<VerifyOtpResponseDto>>;
|
|
11
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ListResponseDto, SingleResponseDto } from '@flusys/nestjs-shared/dtos';
|
|
2
2
|
import { type ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
3
|
-
import { CreateFormResultDto, FormResultResponseDto, GetMyDraftDto, GetResultsByFormDto, SubmitFormDto, UpdateDraftDto, UpdateFormResultDto } from '../dtos/index.js';
|
|
3
|
+
import { CreateFormResultDto, FormResultResponseDto, GetMyDraftDto, GetResultsByFormDto, HasSubmittedEmailDto, SubmitFormDto, UpdateDraftDto, UpdateFormResultDto } from '../dtos/index.js';
|
|
4
|
+
import { FormEmailAuthService } from '../services/form-email-auth.service.js';
|
|
4
5
|
import { FormResultService } from '../services/form-result.service.js';
|
|
5
6
|
declare const FormResultController_base: abstract new (service: FormResultService) => {
|
|
6
7
|
readonly enabledEndpoints: import("@flusys/nestjs-shared").ApiEndpoint[] | "all";
|
|
@@ -19,12 +20,14 @@ declare const FormResultController_base: abstract new (service: FormResultServic
|
|
|
19
20
|
};
|
|
20
21
|
export declare class FormResultController extends FormResultController_base {
|
|
21
22
|
formResultService: FormResultService;
|
|
22
|
-
|
|
23
|
+
private readonly formEmailAuthService;
|
|
24
|
+
constructor(formResultService: FormResultService, formEmailAuthService: FormEmailAuthService);
|
|
23
25
|
submitForm(dto: SubmitFormDto, user: ILoggedUserInfo): Promise<SingleResponseDto<FormResultResponseDto>>;
|
|
24
|
-
submitPublicForm(dto: SubmitFormDto): Promise<SingleResponseDto<FormResultResponseDto>>;
|
|
26
|
+
submitPublicForm(dto: SubmitFormDto, user: ILoggedUserInfo | undefined): Promise<SingleResponseDto<FormResultResponseDto>>;
|
|
25
27
|
getMyDraft(dto: GetMyDraftDto, user: ILoggedUserInfo): Promise<SingleResponseDto<FormResultResponseDto | null>>;
|
|
26
28
|
updateDraft(dto: UpdateDraftDto, user: ILoggedUserInfo): Promise<SingleResponseDto<FormResultResponseDto>>;
|
|
27
29
|
getByFormId(dto: GetResultsByFormDto, user: ILoggedUserInfo): Promise<ListResponseDto<FormResultResponseDto>>;
|
|
28
30
|
hasUserSubmitted(dto: GetMyDraftDto, user: ILoggedUserInfo): Promise<SingleResponseDto<boolean>>;
|
|
31
|
+
hasSubmittedEmail(dto: HasSubmittedEmailDto, user: ILoggedUserInfo | undefined): Promise<SingleResponseDto<boolean>>;
|
|
29
32
|
}
|
|
30
33
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SingleResponseDto } from '@flusys/nestjs-shared/dtos';
|
|
2
2
|
import { type ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
3
|
-
import { CreateFormDto, FormAccessInfoResponseDto, FormResponseDto, PublicFormResponseDto, UpdateFormDto } from '../dtos/index.js';
|
|
3
|
+
import { CreateFormDto, EmailVerifiedFormResponseDto, FormAccessInfoResponseDto, FormResponseDto, LoadEmailVerifiedFormDto, PublicFormResponseDto, UpdateFormDto } from '../dtos/index.js';
|
|
4
|
+
import { FormEmailAuthService } from '../services/form-email-auth.service.js';
|
|
4
5
|
import { FormService } from '../services/form.service.js';
|
|
5
6
|
declare const FormController_base: abstract new (service: FormService) => {
|
|
6
7
|
readonly enabledEndpoints: import("@flusys/nestjs-shared").ApiEndpoint[] | "all";
|
|
@@ -19,10 +20,12 @@ declare const FormController_base: abstract new (service: FormService) => {
|
|
|
19
20
|
};
|
|
20
21
|
export declare class FormController extends FormController_base {
|
|
21
22
|
formService: FormService;
|
|
22
|
-
|
|
23
|
+
private readonly formEmailAuthService;
|
|
24
|
+
constructor(formService: FormService, formEmailAuthService: FormEmailAuthService);
|
|
23
25
|
getFormAccessInfo(id: string): Promise<SingleResponseDto<FormAccessInfoResponseDto>>;
|
|
24
26
|
getPublicForm(id: string): Promise<SingleResponseDto<PublicFormResponseDto>>;
|
|
25
27
|
getAuthenticatedForm(id: string, user: ILoggedUserInfo): Promise<SingleResponseDto<PublicFormResponseDto>>;
|
|
28
|
+
getEmailVerifiedForm(id: string, dto: LoadEmailVerifiedFormDto, user: ILoggedUserInfo | undefined): Promise<SingleResponseDto<EmailVerifiedFormResponseDto>>;
|
|
26
29
|
getBySlug(slug: string, _user: ILoggedUserInfo): Promise<SingleResponseDto<FormResponseDto | null>>;
|
|
27
30
|
getPublicFormBySlug(slug: string): Promise<SingleResponseDto<PublicFormResponseDto | null>>;
|
|
28
31
|
}
|
package/controllers/index.d.ts
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare class RequestFormEmailOtpDto {
|
|
2
|
+
formId: string;
|
|
3
|
+
email: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class VerifyFormEmailOtpDto {
|
|
6
|
+
formId: string;
|
|
7
|
+
email: string;
|
|
8
|
+
code: string;
|
|
9
|
+
}
|
|
10
|
+
export declare class VerifyOtpResponseDto {
|
|
11
|
+
accessToken: string;
|
|
12
|
+
expiresAt: Date;
|
|
13
|
+
}
|
|
14
|
+
export declare class LoadEmailVerifiedFormDto {
|
|
15
|
+
accessToken?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class HasSubmittedEmailDto {
|
|
18
|
+
formId: string;
|
|
19
|
+
accessToken?: string;
|
|
20
|
+
}
|
|
@@ -3,6 +3,7 @@ export declare class SubmitFormDto {
|
|
|
3
3
|
formId: string;
|
|
4
4
|
data: Record<string, unknown>;
|
|
5
5
|
isDraft?: boolean;
|
|
6
|
+
accessToken?: string;
|
|
6
7
|
}
|
|
7
8
|
export declare class CreateFormResultDto extends SubmitFormDto {
|
|
8
9
|
schemaVersionSnapshot?: Record<string, unknown>;
|
|
@@ -30,6 +31,7 @@ export declare class FormResultResponseDto extends IdentityResponseDto {
|
|
|
30
31
|
schemaVersion: number;
|
|
31
32
|
data: Record<string, unknown>;
|
|
32
33
|
submittedById: string | null;
|
|
34
|
+
submitterEmail: string | null;
|
|
33
35
|
submittedAt: Date;
|
|
34
36
|
isDraft: boolean;
|
|
35
37
|
}
|
package/dtos/form.dto.d.ts
CHANGED
|
@@ -34,4 +34,7 @@ export declare class PublicFormResponseDto extends PublicFormResponseDto_base {
|
|
|
34
34
|
declare const FormAccessInfoResponseDto_base: import("@nestjs/common").Type<Pick<FormResponseDto, "description" | "name" | "accessType" | "actionGroups" | "isActive" | "id">>;
|
|
35
35
|
export declare class FormAccessInfoResponseDto extends FormAccessInfoResponseDto_base {
|
|
36
36
|
}
|
|
37
|
+
export declare class EmailVerifiedFormResponseDto extends PublicFormResponseDto {
|
|
38
|
+
verifiedEmail: string;
|
|
39
|
+
}
|
|
37
40
|
export {};
|
package/dtos/index.d.ts
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Identity } from '@flusys/nestjs-shared';
|
|
2
|
+
export declare class FormEmailVerification extends Identity {
|
|
3
|
+
formId: string;
|
|
4
|
+
email: string;
|
|
5
|
+
otpHash: string | null;
|
|
6
|
+
otpExpiresAt: Date | null;
|
|
7
|
+
otpAttempts: number;
|
|
8
|
+
verifiedAt: Date | null;
|
|
9
|
+
accessTokenHash: string | null;
|
|
10
|
+
accessTokenExpiresAt: Date | null;
|
|
11
|
+
}
|
package/entities/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export * from './form.entity.js';
|
|
2
2
|
export * from './form-result.entity.js';
|
|
3
|
+
export * from './form-email-verification.entity.js';
|
|
3
4
|
export * from './form-with-company.entity.js';
|
|
5
|
+
import { FormEmailVerification } from './form-email-verification.entity.js';
|
|
4
6
|
import { Form } from './form.entity.js';
|
|
5
7
|
import { FormResult } from './form-result.entity.js';
|
|
6
8
|
import { FormWithCompany } from './form-with-company.entity.js';
|
|
7
|
-
export declare const FormCoreEntities: (typeof Form | typeof FormResult)[];
|
|
8
|
-
export declare const FormCompanyEntities: (typeof FormResult | typeof FormWithCompany)[];
|
|
9
|
+
export declare const FormCoreEntities: (typeof Form | typeof FormResult | typeof FormEmailVerification)[];
|
|
10
|
+
export declare const FormCompanyEntities: (typeof FormResult | typeof FormEmailVerification | typeof FormWithCompany)[];
|
|
9
11
|
export declare function getFormBuilderEntitiesByConfig(enableCompanyFeature: boolean): any[];
|
|
10
12
|
export { Form as FormBase } from './form.entity.js';
|