@ftisindia/create-app 0.1.5 → 0.2.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/package.json +1 -1
- package/template/.env.example +28 -0
- package/template/README.md +51 -0
- package/template/_gitignore +6 -0
- package/template/_package.json +10 -1
- package/template/docs/FORMS.md +188 -0
- package/template/docs/FORMS_CHECKLIST.md +69 -0
- package/template/docs/REPORTS.md +255 -0
- package/template/docs/REPORTS_CHECKLIST.md +152 -0
- package/template/prisma/migrations/20260612000000_add_form_builder/migration.sql +147 -0
- package/template/prisma/migrations/20260613000000_add_report_builder/migration.sql +129 -0
- package/template/prisma/migrations/20260616000000_add_form_outbox_claimed_by/migration.sql +5 -0
- package/template/prisma/schema.prisma +289 -0
- package/template/scripts/export-openapi.ts +85 -0
- package/template/scripts/gen-form.mjs +149 -0
- package/template/scripts/push-form.ts +124 -0
- package/template/src/app.module.ts +30 -8
- package/template/src/common/dto/membership-response.dto.ts +1 -0
- package/template/src/common/dto/role-summary.dto.ts +3 -3
- package/template/src/common/dto/user-summary.dto.ts +3 -3
- package/template/src/config/env.validation.ts +28 -0
- package/template/src/config/forms.config.ts +13 -0
- package/template/src/config/index.ts +2 -0
- package/template/src/config/openapi.ts +12 -0
- package/template/src/config/reports-secret.ts +15 -0
- package/template/src/config/reports.config.ts +18 -0
- package/template/src/main.ts +3 -12
- package/template/src/modules/access-control/dto/access-control-response.dto.ts +3 -0
- package/template/src/modules/access-control/dto/current-access-control-response.dto.ts +5 -1
- package/template/src/modules/access-control/types/permission-key.ts +27 -0
- package/template/src/modules/access-control/types/route-permission-registry.ts +183 -0
- package/template/src/modules/audit/dto/audit-response.dto.ts +7 -3
- package/template/src/modules/auth/auth.module.ts +3 -1
- package/template/src/modules/auth/dto/auth-response.dto.ts +1 -1
- package/template/src/modules/forms/application/services/file-gc.service.ts +85 -0
- package/template/src/modules/forms/application/services/forms-definitions.service.ts +137 -0
- package/template/src/modules/forms/application/services/forms-error.mapper.ts +64 -0
- package/template/src/modules/forms/application/services/forms-export.service.ts +210 -0
- package/template/src/modules/forms/application/services/forms-files.service.ts +164 -0
- package/template/src/modules/forms/application/services/forms-public.service.ts +49 -0
- package/template/src/modules/forms/application/services/forms-settings-reader.service.ts +53 -0
- package/template/src/modules/forms/application/services/forms-submissions.service.ts +103 -0
- package/template/src/modules/forms/application/services/handlers/authenticate.action.ts +37 -0
- package/template/src/modules/forms/application/services/handlers/logging-email.handler.ts +22 -0
- package/template/src/modules/forms/application/services/handlers/send-confirmation-email.action.ts +40 -0
- package/template/src/modules/forms/application/services/handlers/webhook-delivery.transport.ts +319 -0
- package/template/src/modules/forms/application/services/handlers/webhook.handler.ts +89 -0
- package/template/src/modules/forms/application/services/outbox-dispatcher.service.ts +131 -0
- package/template/src/modules/forms/dto/create-form-definition.dto.ts +12 -0
- package/template/src/modules/forms/dto/data-source-response.dto.ts +19 -0
- package/template/src/modules/forms/dto/export-submissions-query.dto.ts +33 -0
- package/template/src/modules/forms/dto/file-upload-response.dto.ts +24 -0
- package/template/src/modules/forms/dto/form-definition-response.dto.ts +50 -0
- package/template/src/modules/forms/dto/form-render-response.dto.ts +17 -0
- package/template/src/modules/forms/dto/list-form-definitions-query.dto.ts +10 -0
- package/template/src/modules/forms/dto/list-submissions-query.dto.ts +10 -0
- package/template/src/modules/forms/dto/public-submit-form.dto.ts +24 -0
- package/template/src/modules/forms/dto/set-public-access.dto.ts +8 -0
- package/template/src/modules/forms/dto/submission-response.dto.ts +99 -0
- package/template/src/modules/forms/dto/submit-form.dto.ts +50 -0
- package/template/src/modules/forms/dto/update-form-definition.dto.ts +12 -0
- package/template/src/modules/forms/dto/upload-file-query.dto.ts +33 -0
- package/template/src/modules/forms/dto/validate-submission.dto.ts +22 -0
- package/template/src/modules/forms/examples/abstract-submission.form.json +80 -0
- package/template/src/modules/forms/examples/login.form.json +24 -0
- package/template/src/modules/forms/examples/registration.form.json +44 -0
- package/template/src/modules/forms/forms.module.ts +228 -0
- package/template/src/modules/forms/forms.tokens.ts +6 -0
- package/template/src/modules/forms/infrastructure/audit-sink.adapter.ts +30 -0
- package/template/src/modules/forms/infrastructure/casl-forms-authorization.ts +31 -0
- package/template/src/modules/forms/infrastructure/prisma-tx-runner.ts +17 -0
- package/template/src/modules/forms/infrastructure/registry/form-extension.decorators.ts +17 -0
- package/template/src/modules/forms/infrastructure/registry/registry-bootstrap.service.ts +82 -0
- package/template/src/modules/forms/infrastructure/request-forms-context.ts +60 -0
- package/template/src/modules/forms/infrastructure/schema-check/forms-schema-check.service.ts +76 -0
- package/template/src/modules/forms/infrastructure/storage/local-disk-storage.adapter.ts +43 -0
- package/template/src/modules/forms/infrastructure/stores/index.ts +5 -0
- package/template/src/modules/forms/infrastructure/stores/prisma-action-log.store.ts +37 -0
- package/template/src/modules/forms/infrastructure/stores/prisma-file.store.ts +108 -0
- package/template/src/modules/forms/infrastructure/stores/prisma-form-definition.store.ts +147 -0
- package/template/src/modules/forms/infrastructure/stores/prisma-outbox.store.ts +156 -0
- package/template/src/modules/forms/infrastructure/stores/prisma-submission.store.ts +164 -0
- package/template/src/modules/forms/presentation/forms-data-sources.controller.ts +58 -0
- package/template/src/modules/forms/presentation/forms-definitions.controller.ts +191 -0
- package/template/src/modules/forms/presentation/forms-files.controller.ts +79 -0
- package/template/src/modules/forms/presentation/forms-submissions.controller.ts +154 -0
- package/template/src/modules/forms/presentation/forms-upload.interceptor.ts +33 -0
- package/template/src/modules/forms/presentation/public-forms.controller.ts +51 -0
- package/template/src/modules/invitations/dto/invitation-response.dto.ts +4 -0
- package/template/src/modules/organisations/dto/organisation-response.dto.ts +1 -0
- package/template/src/modules/reports/application/services/reports-actions.service.ts +54 -0
- package/template/src/modules/reports/application/services/reports-definitions.service.ts +66 -0
- package/template/src/modules/reports/application/services/reports-error.mapper.ts +97 -0
- package/template/src/modules/reports/application/services/reports-export-dispatcher.service.ts +205 -0
- package/template/src/modules/reports/application/services/reports-exports.service.ts +78 -0
- package/template/src/modules/reports/application/services/reports-queries.service.ts +35 -0
- package/template/src/modules/reports/application/services/reports-settings-reader.service.ts +49 -0
- package/template/src/modules/reports/application/services/reports-views.service.ts +79 -0
- package/template/src/modules/reports/dto/action-result-response.dto.ts +21 -0
- package/template/src/modules/reports/dto/create-report-definition.dto.ts +86 -0
- package/template/src/modules/reports/dto/create-saved-view.dto.ts +26 -0
- package/template/src/modules/reports/dto/execute-action.dto.ts +71 -0
- package/template/src/modules/reports/dto/export-job-response.dto.ts +60 -0
- package/template/src/modules/reports/dto/export-request.dto.ts +34 -0
- package/template/src/modules/reports/dto/list-reports-query.dto.ts +10 -0
- package/template/src/modules/reports/dto/list-views-query.dto.ts +17 -0
- package/template/src/modules/reports/dto/prepare-action-response.dto.ts +14 -0
- package/template/src/modules/reports/dto/prepare-action.dto.ts +27 -0
- package/template/src/modules/reports/dto/query-response.dto.ts +64 -0
- package/template/src/modules/reports/dto/query-spec.dto.ts +120 -0
- package/template/src/modules/reports/dto/report-definition-response.dto.ts +64 -0
- package/template/src/modules/reports/dto/report-meta-query.dto.ts +16 -0
- package/template/src/modules/reports/dto/report-meta-response.dto.ts +113 -0
- package/template/src/modules/reports/dto/saved-view-response.dto.ts +66 -0
- package/template/src/modules/reports/dto/update-report-definition.dto.ts +9 -0
- package/template/src/modules/reports/dto/update-saved-view.dto.ts +27 -0
- package/template/src/modules/reports/examples/abstract-review-board.report.json +54 -0
- package/template/src/modules/reports/examples/org-members.report.json +55 -0
- package/template/src/modules/reports/infrastructure/audit-sink.adapter.ts +31 -0
- package/template/src/modules/reports/infrastructure/casl-reports-authorization.ts +39 -0
- package/template/src/modules/reports/infrastructure/forms-adapter/form-report-source.adapter.ts +292 -0
- package/template/src/modules/reports/infrastructure/forms-adapter/form-row-actions.ts +171 -0
- package/template/src/modules/reports/infrastructure/forms-adapter/forms-bridge-bootstrap.service.ts +32 -0
- package/template/src/modules/reports/infrastructure/prisma-catalog.adapter.ts +95 -0
- package/template/src/modules/reports/infrastructure/prisma-query-executor.ts +103 -0
- package/template/src/modules/reports/infrastructure/prisma-snapshot-runner.ts +47 -0
- package/template/src/modules/reports/infrastructure/prisma-tx-runner.ts +18 -0
- package/template/src/modules/reports/infrastructure/registry/registry-bootstrap.service.ts +61 -0
- package/template/src/modules/reports/infrastructure/registry/report-extension.decorators.ts +14 -0
- package/template/src/modules/reports/infrastructure/reports-job-queue.adapter.ts +28 -0
- package/template/src/modules/reports/infrastructure/request-reports-context.ts +42 -0
- package/template/src/modules/reports/infrastructure/schema-check/reports-schema-check.service.ts +116 -0
- package/template/src/modules/reports/infrastructure/storage/local-disk-export-storage.adapter.ts +92 -0
- package/template/src/modules/reports/infrastructure/stores/index.ts +5 -0
- package/template/src/modules/reports/infrastructure/stores/prisma-bulk-action-run.store.ts +89 -0
- package/template/src/modules/reports/infrastructure/stores/prisma-export-job.store.ts +93 -0
- package/template/src/modules/reports/infrastructure/stores/prisma-report-definition.store.ts +171 -0
- package/template/src/modules/reports/infrastructure/stores/prisma-row-tag.store.ts +110 -0
- package/template/src/modules/reports/infrastructure/stores/prisma-saved-view.store.ts +144 -0
- package/template/src/modules/reports/presentation/reports-actions.controller.ts +83 -0
- package/template/src/modules/reports/presentation/reports-definitions.controller.ts +156 -0
- package/template/src/modules/reports/presentation/reports-export-jobs.controller.ts +61 -0
- package/template/src/modules/reports/presentation/reports-export.controller.ts +76 -0
- package/template/src/modules/reports/presentation/reports-query.controller.ts +52 -0
- package/template/src/modules/reports/presentation/reports-views.controller.ts +140 -0
- package/template/src/modules/reports/reports-forms.module.ts +33 -0
- package/template/src/modules/reports/reports.module.ts +335 -0
- package/template/src/modules/reports/reports.tokens.ts +11 -0
- package/template/src/modules/reports/sources/org-members.source.ts +112 -0
- package/template/src/modules/settings/types/setting-definitions.ts +94 -0
- package/template/test/forms-captcha.e2e-spec.ts +163 -0
- package/template/test/forms-definitions.e2e-spec.ts +394 -0
- package/template/test/forms-export.e2e-spec.ts +390 -0
- package/template/test/forms-files.e2e-spec.ts +345 -0
- package/template/test/forms-outbox.e2e-spec.ts +570 -0
- package/template/test/forms-permission-sync.spec.ts +27 -0
- package/template/test/forms-public.e2e-spec.ts +293 -0
- package/template/test/forms-schema-check.e2e-spec.ts +65 -0
- package/template/test/forms-submissions.e2e-spec.ts +500 -0
- package/template/test/forms-throttling.e2e-spec.ts +146 -0
- package/template/test/forms-webhooks.e2e-spec.ts +403 -0
- package/template/test/jest-e2e.json +1 -0
- package/template/test/reports-advanced.e2e-spec.ts +381 -0
- package/template/test/reports-permission-sync.spec.ts +30 -0
- package/template/test/reports-query.e2e-spec.ts +402 -0
- package/template/test/reports-tiers.e2e-spec.ts +343 -0
- package/template/test/route-registry.validator.spec.ts +22 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
|
+
import { IsIn, IsOptional } from 'class-validator';
|
|
3
|
+
import { PaginationQueryDto } from '../../../common/dto/pagination-query.dto';
|
|
4
|
+
|
|
5
|
+
export class ListFormDefinitionsQueryDto extends PaginationQueryDto {
|
|
6
|
+
@ApiPropertyOptional({ enum: ['DRAFT', 'PUBLISHED', 'ARCHIVED'] })
|
|
7
|
+
@IsOptional()
|
|
8
|
+
@IsIn(['DRAFT', 'PUBLISHED', 'ARCHIVED'])
|
|
9
|
+
status?: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED';
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
|
+
import { IsIn, IsOptional } from 'class-validator';
|
|
3
|
+
import { PaginationQueryDto } from '../../../common/dto/pagination-query.dto';
|
|
4
|
+
|
|
5
|
+
export class ListSubmissionsQueryDto extends PaginationQueryDto {
|
|
6
|
+
@ApiPropertyOptional({ enum: ['DRAFT', 'SUBMITTED'] })
|
|
7
|
+
@IsOptional()
|
|
8
|
+
@IsIn(['DRAFT', 'SUBMITTED'])
|
|
9
|
+
status?: 'DRAFT' | 'SUBMITTED';
|
|
10
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
|
+
import { IsInt, IsObject, IsOptional, IsString, MaxLength, Min } from 'class-validator';
|
|
3
|
+
|
|
4
|
+
export class PublicSubmitFormDto {
|
|
5
|
+
@ApiProperty({
|
|
6
|
+
type: 'object',
|
|
7
|
+
additionalProperties: true,
|
|
8
|
+
description: 'The submission payload, validated against the form definition.',
|
|
9
|
+
})
|
|
10
|
+
@IsObject()
|
|
11
|
+
data!: Record<string, unknown>;
|
|
12
|
+
|
|
13
|
+
@ApiPropertyOptional({ description: 'Specific definition version; defaults to latest published.' })
|
|
14
|
+
@IsOptional()
|
|
15
|
+
@IsInt()
|
|
16
|
+
@Min(1)
|
|
17
|
+
version?: number;
|
|
18
|
+
|
|
19
|
+
@ApiPropertyOptional({ description: 'Captcha token, required when the form enables captcha.' })
|
|
20
|
+
@IsOptional()
|
|
21
|
+
@IsString()
|
|
22
|
+
@MaxLength(4096)
|
|
23
|
+
captchaToken?: string;
|
|
24
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
|
+
|
|
3
|
+
export class SubmissionResponseDto {
|
|
4
|
+
@ApiProperty({
|
|
5
|
+
example: '0a57fb4a-95c6-4f7e-bd5a-f96dbe0599e3',
|
|
6
|
+
format: 'uuid',
|
|
7
|
+
})
|
|
8
|
+
id!: string;
|
|
9
|
+
|
|
10
|
+
@ApiProperty({
|
|
11
|
+
example: '2c67399d-670c-4025-a5fd-1ea9a211891e',
|
|
12
|
+
format: 'uuid',
|
|
13
|
+
})
|
|
14
|
+
orgId!: string;
|
|
15
|
+
|
|
16
|
+
@ApiProperty({ example: 'abstract-submission' })
|
|
17
|
+
formKey!: string;
|
|
18
|
+
|
|
19
|
+
@ApiProperty({ example: 1, description: 'The definition version the data was captured under.' })
|
|
20
|
+
formVersion!: number;
|
|
21
|
+
|
|
22
|
+
@ApiProperty({ type: 'object', additionalProperties: true })
|
|
23
|
+
data!: Record<string, unknown>;
|
|
24
|
+
|
|
25
|
+
@ApiProperty({ enum: ['DRAFT', 'SUBMITTED'], example: 'SUBMITTED' })
|
|
26
|
+
status!: 'DRAFT' | 'SUBMITTED';
|
|
27
|
+
|
|
28
|
+
@ApiProperty({ example: false })
|
|
29
|
+
locked!: boolean;
|
|
30
|
+
|
|
31
|
+
@ApiPropertyOptional({
|
|
32
|
+
type: String,
|
|
33
|
+
example: '4a4f0d8a-4bd2-469f-a6a9-3e1cb6a2b456',
|
|
34
|
+
format: 'uuid',
|
|
35
|
+
nullable: true,
|
|
36
|
+
})
|
|
37
|
+
createdBy?: string | null;
|
|
38
|
+
|
|
39
|
+
@ApiProperty({ example: '2026-06-01T10:30:00.000Z', format: 'date-time' })
|
|
40
|
+
createdAt!: string;
|
|
41
|
+
|
|
42
|
+
@ApiProperty({ example: '2026-06-01T10:30:00.000Z', format: 'date-time' })
|
|
43
|
+
updatedAt!: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class SubmissionListResponseDto {
|
|
47
|
+
@ApiProperty({ type: [SubmissionResponseDto] })
|
|
48
|
+
items!: SubmissionResponseDto[];
|
|
49
|
+
|
|
50
|
+
@ApiPropertyOptional({
|
|
51
|
+
type: String,
|
|
52
|
+
example: '0a57fb4a-95c6-4f7e-bd5a-f96dbe0599e3',
|
|
53
|
+
format: 'uuid',
|
|
54
|
+
nullable: true,
|
|
55
|
+
})
|
|
56
|
+
nextCursor!: string | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class SubmitResultResponseDto {
|
|
60
|
+
@ApiProperty({
|
|
61
|
+
type: String,
|
|
62
|
+
example: '0a57fb4a-95c6-4f7e-bd5a-f96dbe0599e3',
|
|
63
|
+
format: 'uuid',
|
|
64
|
+
nullable: true,
|
|
65
|
+
})
|
|
66
|
+
submissionId!: string | null;
|
|
67
|
+
|
|
68
|
+
@ApiProperty({
|
|
69
|
+
type: 'object',
|
|
70
|
+
additionalProperties: true,
|
|
71
|
+
description: 'Outputs of every executed action, keyed by action name.',
|
|
72
|
+
})
|
|
73
|
+
outputs!: Record<string, unknown>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class ValidationErrorItemDto {
|
|
77
|
+
@ApiProperty({
|
|
78
|
+
example: 'authors.0.email',
|
|
79
|
+
description: 'Dotted data path. Empty string means the whole document.',
|
|
80
|
+
})
|
|
81
|
+
path!: string;
|
|
82
|
+
|
|
83
|
+
@ApiProperty({ example: 'REQUIRED' })
|
|
84
|
+
code!: string;
|
|
85
|
+
|
|
86
|
+
@ApiProperty({ example: 'This field is required.' })
|
|
87
|
+
message!: string;
|
|
88
|
+
|
|
89
|
+
@ApiPropertyOptional({ example: 'require-corresponding-author' })
|
|
90
|
+
ruleId?: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export class ValidationResultResponseDto {
|
|
94
|
+
@ApiProperty({ example: false })
|
|
95
|
+
valid!: boolean;
|
|
96
|
+
|
|
97
|
+
@ApiProperty({ type: [ValidationErrorItemDto] })
|
|
98
|
+
errors!: ValidationErrorItemDto[];
|
|
99
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
|
+
import {
|
|
3
|
+
IsIn,
|
|
4
|
+
IsInt,
|
|
5
|
+
IsObject,
|
|
6
|
+
IsOptional,
|
|
7
|
+
IsString,
|
|
8
|
+
IsUUID,
|
|
9
|
+
MaxLength,
|
|
10
|
+
Min,
|
|
11
|
+
} from 'class-validator';
|
|
12
|
+
|
|
13
|
+
export class SubmitFormDto {
|
|
14
|
+
@ApiProperty({ enum: ['draft', 'submit'], example: 'submit' })
|
|
15
|
+
@IsIn(['draft', 'submit'])
|
|
16
|
+
mode!: 'draft' | 'submit';
|
|
17
|
+
|
|
18
|
+
@ApiPropertyOptional({
|
|
19
|
+
example: 'submit',
|
|
20
|
+
maxLength: 64,
|
|
21
|
+
description: 'Button name in the definition actions map. Defaults to the mode.',
|
|
22
|
+
})
|
|
23
|
+
@IsOptional()
|
|
24
|
+
@IsString()
|
|
25
|
+
@MaxLength(64)
|
|
26
|
+
button?: string;
|
|
27
|
+
|
|
28
|
+
@ApiPropertyOptional({
|
|
29
|
+
example: 3,
|
|
30
|
+
minimum: 1,
|
|
31
|
+
description: 'Specific definition version; defaults to the latest published one.',
|
|
32
|
+
})
|
|
33
|
+
@IsOptional()
|
|
34
|
+
@IsInt()
|
|
35
|
+
@Min(1)
|
|
36
|
+
version?: number;
|
|
37
|
+
|
|
38
|
+
@ApiProperty({ type: 'object', additionalProperties: true })
|
|
39
|
+
@IsObject()
|
|
40
|
+
data!: Record<string, unknown>;
|
|
41
|
+
|
|
42
|
+
@ApiPropertyOptional({
|
|
43
|
+
example: '0a57fb4a-95c6-4f7e-bd5a-f96dbe0599e3',
|
|
44
|
+
format: 'uuid',
|
|
45
|
+
description: 'Present when editing an existing draft submission.',
|
|
46
|
+
})
|
|
47
|
+
@IsOptional()
|
|
48
|
+
@IsUUID()
|
|
49
|
+
submissionId?: string;
|
|
50
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
2
|
+
import { IsObject } from 'class-validator';
|
|
3
|
+
|
|
4
|
+
export class UpdateFormDefinitionDto {
|
|
5
|
+
@ApiProperty({
|
|
6
|
+
type: 'object',
|
|
7
|
+
additionalProperties: true,
|
|
8
|
+
description: 'The FormDefinition document (validated against the engine meta-schema).',
|
|
9
|
+
})
|
|
10
|
+
@IsObject()
|
|
11
|
+
definition!: Record<string, unknown>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Type } from 'class-transformer';
|
|
2
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
3
|
+
import { IsInt, IsOptional, IsString, IsUUID, MaxLength, Min } from 'class-validator';
|
|
4
|
+
|
|
5
|
+
export class UploadFileQueryDto {
|
|
6
|
+
@ApiProperty({
|
|
7
|
+
description: 'File field name in the target form definition.',
|
|
8
|
+
example: 'manuscript',
|
|
9
|
+
})
|
|
10
|
+
@IsString()
|
|
11
|
+
@MaxLength(128)
|
|
12
|
+
field!: string;
|
|
13
|
+
|
|
14
|
+
@ApiPropertyOptional({
|
|
15
|
+
example: 3,
|
|
16
|
+
minimum: 1,
|
|
17
|
+
description: 'Specific definition version; defaults to the latest published one.',
|
|
18
|
+
})
|
|
19
|
+
@IsOptional()
|
|
20
|
+
@Type(() => Number)
|
|
21
|
+
@IsInt()
|
|
22
|
+
@Min(1)
|
|
23
|
+
version?: number;
|
|
24
|
+
|
|
25
|
+
@ApiPropertyOptional({
|
|
26
|
+
example: '0a57fb4a-95c6-4f7e-bd5a-f96dbe0599e3',
|
|
27
|
+
format: 'uuid',
|
|
28
|
+
description: 'Existing draft submission whose form version should be used.',
|
|
29
|
+
})
|
|
30
|
+
@IsOptional()
|
|
31
|
+
@IsUUID()
|
|
32
|
+
submissionId?: string;
|
|
33
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
|
+
import { IsIn, IsInt, IsObject, IsOptional, Min } from 'class-validator';
|
|
3
|
+
|
|
4
|
+
export class ValidateSubmissionDto {
|
|
5
|
+
@ApiProperty({ enum: ['draft', 'submit'], example: 'submit' })
|
|
6
|
+
@IsIn(['draft', 'submit'])
|
|
7
|
+
mode!: 'draft' | 'submit';
|
|
8
|
+
|
|
9
|
+
@ApiPropertyOptional({
|
|
10
|
+
example: 3,
|
|
11
|
+
minimum: 1,
|
|
12
|
+
description: 'Specific definition version; defaults to the latest published one.',
|
|
13
|
+
})
|
|
14
|
+
@IsOptional()
|
|
15
|
+
@IsInt()
|
|
16
|
+
@Min(1)
|
|
17
|
+
version?: number;
|
|
18
|
+
|
|
19
|
+
@ApiProperty({ type: 'object', additionalProperties: true })
|
|
20
|
+
@IsObject()
|
|
21
|
+
data!: Record<string, unknown>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"key": "abstract-submission",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"title": "Scientific abstract submission",
|
|
5
|
+
"fields": [
|
|
6
|
+
{
|
|
7
|
+
"type": "text",
|
|
8
|
+
"name": "title",
|
|
9
|
+
"label": "Abstract title",
|
|
10
|
+
"validators": { "required": true, "maxLength": 200 },
|
|
11
|
+
"reportable": true
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"type": "group",
|
|
15
|
+
"name": "authors",
|
|
16
|
+
"label": "Authors",
|
|
17
|
+
"repeatable": true,
|
|
18
|
+
"min": 1,
|
|
19
|
+
"max": 10,
|
|
20
|
+
"fields": [
|
|
21
|
+
{ "type": "text", "name": "fullName", "validators": { "required": true } },
|
|
22
|
+
{ "type": "email", "name": "email", "validators": { "required": true } },
|
|
23
|
+
{ "type": "boolean", "name": "isPresenting" }
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"type": "select",
|
|
28
|
+
"name": "track",
|
|
29
|
+
"label": "Conference track",
|
|
30
|
+
"dataSource": { "key": "conference-tracks" },
|
|
31
|
+
"validators": { "required": true },
|
|
32
|
+
"reportable": true,
|
|
33
|
+
"indexHint": true
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"type": "text",
|
|
37
|
+
"name": "body",
|
|
38
|
+
"label": "Abstract body",
|
|
39
|
+
"validators": { "required": true, "maxLength": 3000 },
|
|
40
|
+
"ui": { "widget": "richtext" }
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"type": "file",
|
|
44
|
+
"name": "manuscript",
|
|
45
|
+
"label": "Full manuscript (PDF)",
|
|
46
|
+
"accept": ["application/pdf"],
|
|
47
|
+
"maxSizeMb": 20
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"type": "boolean",
|
|
51
|
+
"name": "needsFunding",
|
|
52
|
+
"label": "Requesting funding?"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"type": "text",
|
|
56
|
+
"name": "fundingSource",
|
|
57
|
+
"label": "Funding source",
|
|
58
|
+
"ui": { "showWhen": "needsFunding" }
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"type": "hidden",
|
|
62
|
+
"name": "submittedBy",
|
|
63
|
+
"source": "context",
|
|
64
|
+
"path": "user.id"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"rules": [
|
|
68
|
+
{
|
|
69
|
+
"id": "funding-required",
|
|
70
|
+
"enforceOn": ["submit"],
|
|
71
|
+
"if": { "==": [{ "var": "data.needsFunding" }, true] },
|
|
72
|
+
"then": { "require": ["fundingSource"] },
|
|
73
|
+
"message": "Funding source is required when requesting funding."
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"actions": {
|
|
77
|
+
"submit": ["validateAll", "persist", "lockEditing", "sendConfirmationEmail"],
|
|
78
|
+
"saveDraft": ["persistDraft"]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"key": "login",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"title": "Sign in",
|
|
5
|
+
"settings": { "access": "public" },
|
|
6
|
+
"fields": [
|
|
7
|
+
{
|
|
8
|
+
"type": "email",
|
|
9
|
+
"name": "email",
|
|
10
|
+
"label": "Email address",
|
|
11
|
+
"validators": { "required": true }
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"type": "password",
|
|
15
|
+
"name": "password",
|
|
16
|
+
"label": "Password",
|
|
17
|
+
"validators": { "required": true, "minLength": 8 },
|
|
18
|
+
"ui": { "widget": "password" }
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"actions": {
|
|
22
|
+
"submit": ["authenticate"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"key": "registration",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"title": "Event registration",
|
|
5
|
+
"fields": [
|
|
6
|
+
{
|
|
7
|
+
"type": "text",
|
|
8
|
+
"name": "fullName",
|
|
9
|
+
"label": "Full name",
|
|
10
|
+
"validators": { "required": true, "maxLength": 120 },
|
|
11
|
+
"reportable": true
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"type": "email",
|
|
15
|
+
"name": "email",
|
|
16
|
+
"label": "Email address",
|
|
17
|
+
"validators": { "required": true },
|
|
18
|
+
"reportable": true,
|
|
19
|
+
"indexHint": true
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"type": "select",
|
|
23
|
+
"name": "ticketType",
|
|
24
|
+
"label": "Ticket type",
|
|
25
|
+
"validators": { "required": true, "options": ["standard", "student", "vip"] },
|
|
26
|
+
"reportable": true
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"type": "boolean",
|
|
30
|
+
"name": "newsletter",
|
|
31
|
+
"label": "Subscribe to updates?"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"type": "hidden",
|
|
35
|
+
"name": "registeredBy",
|
|
36
|
+
"source": "context",
|
|
37
|
+
"path": "user.id"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"actions": {
|
|
41
|
+
"submit": ["validateAll", "persist", "sendConfirmationEmail"],
|
|
42
|
+
"saveDraft": ["persistDraft"]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { DiscoveryModule } from '@nestjs/core';
|
|
3
|
+
import {
|
|
4
|
+
ActionRegistry,
|
|
5
|
+
DataSourceRegistry,
|
|
6
|
+
DataSourceResolver,
|
|
7
|
+
DefinitionService,
|
|
8
|
+
FieldTypeRegistry,
|
|
9
|
+
LifecycleRegistry,
|
|
10
|
+
OutboxHandlerRegistry,
|
|
11
|
+
SubmissionService,
|
|
12
|
+
ValidationEngine,
|
|
13
|
+
registerCoreFieldTypes,
|
|
14
|
+
} from '@ftisindia/form-builder';
|
|
15
|
+
import { AuthModule } from '../auth/auth.module';
|
|
16
|
+
import { FileGcService } from './application/services/file-gc.service';
|
|
17
|
+
import { FormsDefinitionsService } from './application/services/forms-definitions.service';
|
|
18
|
+
import { FormsExportService } from './application/services/forms-export.service';
|
|
19
|
+
import { FormsFilesService } from './application/services/forms-files.service';
|
|
20
|
+
import { FormsPublicService } from './application/services/forms-public.service';
|
|
21
|
+
import { FormsSettingsReader } from './application/services/forms-settings-reader.service';
|
|
22
|
+
import { FormsSubmissionsService } from './application/services/forms-submissions.service';
|
|
23
|
+
import { AuthenticateActionHandler } from './application/services/handlers/authenticate.action';
|
|
24
|
+
import { LoggingEmailHandler } from './application/services/handlers/logging-email.handler';
|
|
25
|
+
import { SendConfirmationEmailAction } from './application/services/handlers/send-confirmation-email.action';
|
|
26
|
+
import { WebhookOutboxHandler } from './application/services/handlers/webhook.handler';
|
|
27
|
+
import { OutboxDispatcherService } from './application/services/outbox-dispatcher.service';
|
|
28
|
+
import { CaslFormsAuthorization } from './infrastructure/casl-forms-authorization';
|
|
29
|
+
import { PrismaFormsAuditSink } from './infrastructure/audit-sink.adapter';
|
|
30
|
+
import { PrismaTxRunner } from './infrastructure/prisma-tx-runner';
|
|
31
|
+
import { RequestFormsContext } from './infrastructure/request-forms-context';
|
|
32
|
+
import { RegistryBootstrapService } from './infrastructure/registry/registry-bootstrap.service';
|
|
33
|
+
import { FormsSchemaCheckService } from './infrastructure/schema-check/forms-schema-check.service';
|
|
34
|
+
import { LocalDiskStorageAdapter } from './infrastructure/storage/local-disk-storage.adapter';
|
|
35
|
+
import { PrismaActionLogStore } from './infrastructure/stores/prisma-action-log.store';
|
|
36
|
+
import { PrismaFileStore } from './infrastructure/stores/prisma-file.store';
|
|
37
|
+
import { PrismaFormDefinitionStore } from './infrastructure/stores/prisma-form-definition.store';
|
|
38
|
+
import { PrismaOutboxStore } from './infrastructure/stores/prisma-outbox.store';
|
|
39
|
+
import { PrismaSubmissionStore } from './infrastructure/stores/prisma-submission.store';
|
|
40
|
+
import { FormsDataSourcesController } from './presentation/forms-data-sources.controller';
|
|
41
|
+
import { FormsDefinitionsController } from './presentation/forms-definitions.controller';
|
|
42
|
+
import { FormsFilesController } from './presentation/forms-files.controller';
|
|
43
|
+
import { FormsSubmissionsController } from './presentation/forms-submissions.controller';
|
|
44
|
+
import { FormsUploadInterceptor } from './presentation/forms-upload.interceptor';
|
|
45
|
+
import { PublicFormsController } from './presentation/public-forms.controller';
|
|
46
|
+
import { FORMS_CAPTCHA_VERIFIER } from './forms.tokens';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The forms glue module — the ONLY place that binds the framework-free
|
|
50
|
+
* @ftisindia/form-builder engine to this app's services (request context,
|
|
51
|
+
* CASL RBAC, AuditService, Prisma). Engine services are composed here via
|
|
52
|
+
* factories over the four adapters and five stores; app extensions register
|
|
53
|
+
* through the @FormFieldType/@FormActionHandler/@FormDataSource decorators.
|
|
54
|
+
*/
|
|
55
|
+
@Module({
|
|
56
|
+
imports: [AuthModule, DiscoveryModule],
|
|
57
|
+
controllers: [
|
|
58
|
+
// Listed before the definitions controller so the literal 'data-sources'
|
|
59
|
+
// segment wins over the ':formKey' parameter on the shared base path.
|
|
60
|
+
FormsDataSourcesController,
|
|
61
|
+
FormsDefinitionsController,
|
|
62
|
+
FormsFilesController,
|
|
63
|
+
FormsSubmissionsController,
|
|
64
|
+
PublicFormsController,
|
|
65
|
+
],
|
|
66
|
+
providers: [
|
|
67
|
+
// Adapters over the ecosystem seams.
|
|
68
|
+
RequestFormsContext,
|
|
69
|
+
CaslFormsAuthorization,
|
|
70
|
+
PrismaFormsAuditSink,
|
|
71
|
+
PrismaTxRunner,
|
|
72
|
+
// Prisma store implementations of the engine ports.
|
|
73
|
+
PrismaFormDefinitionStore,
|
|
74
|
+
PrismaSubmissionStore,
|
|
75
|
+
PrismaOutboxStore,
|
|
76
|
+
PrismaFileStore,
|
|
77
|
+
PrismaActionLogStore,
|
|
78
|
+
FormsSettingsReader,
|
|
79
|
+
{ provide: FORMS_CAPTCHA_VERIFIER, useValue: undefined },
|
|
80
|
+
// Engine registries — singletons; core field types registered up front.
|
|
81
|
+
{
|
|
82
|
+
provide: FieldTypeRegistry,
|
|
83
|
+
useFactory: () => {
|
|
84
|
+
const registry = new FieldTypeRegistry();
|
|
85
|
+
registerCoreFieldTypes(registry);
|
|
86
|
+
return registry;
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{ provide: ActionRegistry, useFactory: () => new ActionRegistry() },
|
|
90
|
+
{ provide: DataSourceRegistry, useFactory: () => new DataSourceRegistry() },
|
|
91
|
+
{ provide: LifecycleRegistry, useFactory: () => new LifecycleRegistry() },
|
|
92
|
+
{ provide: OutboxHandlerRegistry, useFactory: () => new OutboxHandlerRegistry() },
|
|
93
|
+
{
|
|
94
|
+
provide: ValidationEngine,
|
|
95
|
+
useFactory: (fieldTypes: FieldTypeRegistry) => new ValidationEngine(fieldTypes),
|
|
96
|
+
inject: [FieldTypeRegistry],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
provide: DataSourceResolver,
|
|
100
|
+
useFactory: (dataSources: DataSourceRegistry) => new DataSourceResolver(dataSources),
|
|
101
|
+
inject: [DataSourceRegistry],
|
|
102
|
+
},
|
|
103
|
+
// Engine orchestration services, composed over the adapters and stores.
|
|
104
|
+
{
|
|
105
|
+
provide: DefinitionService,
|
|
106
|
+
useFactory: (
|
|
107
|
+
store: PrismaFormDefinitionStore,
|
|
108
|
+
authz: CaslFormsAuthorization,
|
|
109
|
+
audit: PrismaFormsAuditSink,
|
|
110
|
+
txRunner: PrismaTxRunner,
|
|
111
|
+
fieldTypes: FieldTypeRegistry,
|
|
112
|
+
actions: ActionRegistry,
|
|
113
|
+
dataSources: DataSourceRegistry,
|
|
114
|
+
resolver: DataSourceResolver,
|
|
115
|
+
settings: FormsSettingsReader,
|
|
116
|
+
) =>
|
|
117
|
+
new DefinitionService({
|
|
118
|
+
store,
|
|
119
|
+
authz,
|
|
120
|
+
audit,
|
|
121
|
+
txRunner,
|
|
122
|
+
fieldTypes,
|
|
123
|
+
actions,
|
|
124
|
+
dataSources,
|
|
125
|
+
resolver,
|
|
126
|
+
policyFor: (orgId) => settings.policyFor(orgId),
|
|
127
|
+
}),
|
|
128
|
+
inject: [
|
|
129
|
+
PrismaFormDefinitionStore,
|
|
130
|
+
CaslFormsAuthorization,
|
|
131
|
+
PrismaFormsAuditSink,
|
|
132
|
+
PrismaTxRunner,
|
|
133
|
+
FieldTypeRegistry,
|
|
134
|
+
ActionRegistry,
|
|
135
|
+
DataSourceRegistry,
|
|
136
|
+
DataSourceResolver,
|
|
137
|
+
FormsSettingsReader,
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
provide: SubmissionService,
|
|
142
|
+
useFactory: (
|
|
143
|
+
definitions: PrismaFormDefinitionStore,
|
|
144
|
+
submissions: PrismaSubmissionStore,
|
|
145
|
+
files: PrismaFileStore,
|
|
146
|
+
outbox: PrismaOutboxStore,
|
|
147
|
+
actionLog: PrismaActionLogStore,
|
|
148
|
+
audit: PrismaFormsAuditSink,
|
|
149
|
+
txRunner: PrismaTxRunner,
|
|
150
|
+
authz: CaslFormsAuthorization,
|
|
151
|
+
fieldTypes: FieldTypeRegistry,
|
|
152
|
+
actions: ActionRegistry,
|
|
153
|
+
lifecycles: LifecycleRegistry,
|
|
154
|
+
validation: ValidationEngine,
|
|
155
|
+
resolver: DataSourceResolver,
|
|
156
|
+
settings: FormsSettingsReader,
|
|
157
|
+
) =>
|
|
158
|
+
new SubmissionService({
|
|
159
|
+
definitions,
|
|
160
|
+
submissions,
|
|
161
|
+
files,
|
|
162
|
+
outbox,
|
|
163
|
+
actionLog,
|
|
164
|
+
audit,
|
|
165
|
+
txRunner,
|
|
166
|
+
authz,
|
|
167
|
+
fieldTypes,
|
|
168
|
+
actions,
|
|
169
|
+
lifecycles,
|
|
170
|
+
validation,
|
|
171
|
+
resolver,
|
|
172
|
+
captcha: settings.captchaVerifier,
|
|
173
|
+
policyFor: (orgId) => settings.policyFor(orgId),
|
|
174
|
+
}),
|
|
175
|
+
inject: [
|
|
176
|
+
PrismaFormDefinitionStore,
|
|
177
|
+
PrismaSubmissionStore,
|
|
178
|
+
PrismaFileStore,
|
|
179
|
+
PrismaOutboxStore,
|
|
180
|
+
PrismaActionLogStore,
|
|
181
|
+
PrismaFormsAuditSink,
|
|
182
|
+
PrismaTxRunner,
|
|
183
|
+
CaslFormsAuthorization,
|
|
184
|
+
FieldTypeRegistry,
|
|
185
|
+
ActionRegistry,
|
|
186
|
+
LifecycleRegistry,
|
|
187
|
+
ValidationEngine,
|
|
188
|
+
DataSourceResolver,
|
|
189
|
+
FormsSettingsReader,
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
// Startup wiring + background work.
|
|
193
|
+
RegistryBootstrapService,
|
|
194
|
+
FormsSchemaCheckService,
|
|
195
|
+
OutboxDispatcherService,
|
|
196
|
+
LocalDiskStorageAdapter,
|
|
197
|
+
FileGcService,
|
|
198
|
+
FormsUploadInterceptor,
|
|
199
|
+
// Default outbox handlers + shipped form actions.
|
|
200
|
+
LoggingEmailHandler,
|
|
201
|
+
WebhookOutboxHandler,
|
|
202
|
+
AuthenticateActionHandler,
|
|
203
|
+
SendConfirmationEmailAction,
|
|
204
|
+
// Thin HTTP-facing services.
|
|
205
|
+
FormsDefinitionsService,
|
|
206
|
+
FormsSubmissionsService,
|
|
207
|
+
FormsFilesService,
|
|
208
|
+
FormsPublicService,
|
|
209
|
+
FormsExportService,
|
|
210
|
+
],
|
|
211
|
+
exports: [
|
|
212
|
+
FieldTypeRegistry,
|
|
213
|
+
ActionRegistry,
|
|
214
|
+
DataSourceRegistry,
|
|
215
|
+
LifecycleRegistry,
|
|
216
|
+
OutboxHandlerRegistry,
|
|
217
|
+
OutboxDispatcherService,
|
|
218
|
+
// Consumed ONLY by the optional ReportsFormsModule bridge (report design
|
|
219
|
+
// §3.2/§13): the form definition store backs the form→report source
|
|
220
|
+
// adapter, and SubmissionService + the forms request context let the
|
|
221
|
+
// delegated editSubmission/updateStatus grid verbs run the form engine's
|
|
222
|
+
// own validation pipeline. ReportsModule itself imports none of this.
|
|
223
|
+
PrismaFormDefinitionStore,
|
|
224
|
+
SubmissionService,
|
|
225
|
+
RequestFormsContext,
|
|
226
|
+
],
|
|
227
|
+
})
|
|
228
|
+
export class FormsModule {}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional-provider tokens for the forms module's seams. Bind an
|
|
3
|
+
* implementation in your AppModule to activate the seam; absence fails
|
|
4
|
+
* closed (e.g. captcha-enabled definitions refuse to publish).
|
|
5
|
+
*/
|
|
6
|
+
export const FORMS_CAPTCHA_VERIFIER = 'FORMS_CAPTCHA_VERIFIER';
|