@ftisindia/create-app 0.1.4 → 0.1.6
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 +31 -0
- package/template/README.md +61 -0
- package/template/_gitignore +6 -0
- package/template/_package.json +6 -0
- package/template/docs/FORMS.md +169 -0
- package/template/docs/FORMS_CHECKLIST.md +61 -0
- package/template/docs/REPORTS.md +246 -0
- package/template/docs/REPORTS_CHECKLIST.md +97 -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/schema.prisma +285 -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 +29 -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/app.config.ts +6 -1
- package/template/src/config/env.validation.ts +45 -0
- package/template/src/config/forms.config.ts +12 -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 +16 -0
- package/template/src/main.ts +16 -12
- package/template/src/modules/access-control/access-control.module.ts +2 -1
- 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 +35 -0
- package/template/src/modules/access-control/presentation/current-access-control.controller.ts +40 -0
- 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.handler.ts +41 -0
- package/template/src/modules/forms/application/services/outbox-dispatcher.service.ts +109 -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 +226 -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 +133 -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/application/services/organisations.service.ts +67 -1
- package/template/src/modules/organisations/dto/organisation-response.dto.ts +52 -0
- package/template/src/modules/organisations/presentation/organisations.controller.ts +25 -3
- 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 +124 -0
- package/template/src/modules/reports/application/services/reports-exports.service.ts +74 -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 +79 -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-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 +309 -0
- package/template/test/forms-permission-sync.spec.ts +27 -0
- package/template/test/forms-public.e2e-spec.ts +269 -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-webhooks.e2e-spec.ts +261 -0
- package/template/test/frontend-bootstrap.spec.ts +181 -0
- package/template/test/reports-advanced.e2e-spec.ts +368 -0
- package/template/test/reports-permission-sync.spec.ts +30 -0
- package/template/test/reports-query.e2e-spec.ts +350 -0
- package/template/test/reports-tiers.e2e-spec.ts +257 -0
- package/template/test/route-registry.validator.spec.ts +34 -0
- package/template/test/security.e2e-spec.ts +134 -2
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Body,
|
|
3
|
+
Controller,
|
|
4
|
+
Get,
|
|
5
|
+
HttpCode,
|
|
6
|
+
Param,
|
|
7
|
+
ParseIntPipe,
|
|
8
|
+
Patch,
|
|
9
|
+
Post,
|
|
10
|
+
Query,
|
|
11
|
+
UseGuards,
|
|
12
|
+
} from '@nestjs/common';
|
|
13
|
+
import {
|
|
14
|
+
ApiBearerAuth,
|
|
15
|
+
ApiCreatedResponse,
|
|
16
|
+
ApiOkResponse,
|
|
17
|
+
ApiOperation,
|
|
18
|
+
ApiParam,
|
|
19
|
+
ApiTags,
|
|
20
|
+
ApiUnprocessableEntityResponse,
|
|
21
|
+
} from '@nestjs/swagger';
|
|
22
|
+
import { ErrorResponseDto } from '../../../common/dto/error-response.dto';
|
|
23
|
+
import { ApiProtectedErrorResponses } from '../../../common/swagger/api-error-responses';
|
|
24
|
+
import { PermissionGuard } from '../../access-control/application/services/permission.guard';
|
|
25
|
+
import { RequirePermissions } from '../../access-control/presentation/permissions.decorator';
|
|
26
|
+
import { JwtAuthGuard } from '../../auth/infrastructure/passport/jwt-auth.guard';
|
|
27
|
+
import { OrgScopeGuard } from '../../request-context/presentation/org-scope.guard';
|
|
28
|
+
import { FormsDefinitionsService } from '../application/services/forms-definitions.service';
|
|
29
|
+
import { CreateFormDefinitionDto } from '../dto/create-form-definition.dto';
|
|
30
|
+
import {
|
|
31
|
+
FormDefinitionListResponseDto,
|
|
32
|
+
FormDefinitionResponseDto,
|
|
33
|
+
} from '../dto/form-definition-response.dto';
|
|
34
|
+
import { FormRenderResponseDto } from '../dto/form-render-response.dto';
|
|
35
|
+
import { ListFormDefinitionsQueryDto } from '../dto/list-form-definitions-query.dto';
|
|
36
|
+
import { SetPublicAccessDto } from '../dto/set-public-access.dto';
|
|
37
|
+
import { UpdateFormDefinitionDto } from '../dto/update-form-definition.dto';
|
|
38
|
+
|
|
39
|
+
@ApiTags('Forms')
|
|
40
|
+
@ApiBearerAuth()
|
|
41
|
+
@ApiParam({ name: 'orgId', description: 'Organisation ID.', format: 'uuid' })
|
|
42
|
+
@ApiProtectedErrorResponses(404, 409)
|
|
43
|
+
@ApiUnprocessableEntityResponse({
|
|
44
|
+
description: 'The form definition failed engine validation or linting.',
|
|
45
|
+
type: ErrorResponseDto,
|
|
46
|
+
})
|
|
47
|
+
@Controller('organisations/:orgId/forms')
|
|
48
|
+
@UseGuards(JwtAuthGuard, OrgScopeGuard, PermissionGuard)
|
|
49
|
+
export class FormsDefinitionsController {
|
|
50
|
+
constructor(private readonly formsDefinitionsService: FormsDefinitionsService) {}
|
|
51
|
+
|
|
52
|
+
@Get()
|
|
53
|
+
@RequirePermissions('forms.read')
|
|
54
|
+
@ApiOperation({ summary: 'List form definitions.' })
|
|
55
|
+
@ApiOkResponse({
|
|
56
|
+
description: 'Form definitions.',
|
|
57
|
+
type: FormDefinitionListResponseDto,
|
|
58
|
+
})
|
|
59
|
+
list(@Param('orgId') orgId: string, @Query() query: ListFormDefinitionsQueryDto) {
|
|
60
|
+
return this.formsDefinitionsService.list(orgId, query);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Post()
|
|
64
|
+
@RequirePermissions('forms.create')
|
|
65
|
+
@ApiOperation({ summary: 'Create a new draft form definition version.' })
|
|
66
|
+
@ApiCreatedResponse({
|
|
67
|
+
description: 'Created draft definition.',
|
|
68
|
+
type: FormDefinitionResponseDto,
|
|
69
|
+
})
|
|
70
|
+
create(@Param('orgId') orgId: string, @Body() dto: CreateFormDefinitionDto) {
|
|
71
|
+
return this.formsDefinitionsService.create(orgId, dto);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@Get(':formKey')
|
|
75
|
+
@RequirePermissions('forms.read')
|
|
76
|
+
@ApiParam({ name: 'formKey', description: 'Form definition key.' })
|
|
77
|
+
@ApiOperation({ summary: 'Return the latest version of a form definition.' })
|
|
78
|
+
@ApiOkResponse({
|
|
79
|
+
description: 'Latest definition version.',
|
|
80
|
+
type: FormDefinitionResponseDto,
|
|
81
|
+
})
|
|
82
|
+
getLatest(@Param('orgId') orgId: string, @Param('formKey') formKey: string) {
|
|
83
|
+
return this.formsDefinitionsService.getLatest(orgId, formKey);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@Get(':formKey/render')
|
|
87
|
+
@RequirePermissions('formSubmissions.create')
|
|
88
|
+
@ApiParam({ name: 'formKey', description: 'Form definition key.' })
|
|
89
|
+
@ApiOperation({
|
|
90
|
+
summary: 'Return the published definition plus resolved data-source options.',
|
|
91
|
+
})
|
|
92
|
+
@ApiOkResponse({
|
|
93
|
+
description: 'Renderable definition.',
|
|
94
|
+
type: FormRenderResponseDto,
|
|
95
|
+
})
|
|
96
|
+
render(@Param('orgId') orgId: string, @Param('formKey') formKey: string) {
|
|
97
|
+
return this.formsDefinitionsService.render(orgId, formKey);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@Get(':formKey/versions/:version')
|
|
101
|
+
@RequirePermissions('forms.read')
|
|
102
|
+
@ApiParam({ name: 'formKey', description: 'Form definition key.' })
|
|
103
|
+
@ApiParam({ name: 'version', description: 'Definition version number.', type: Number })
|
|
104
|
+
@ApiOperation({ summary: 'Return a specific form definition version.' })
|
|
105
|
+
@ApiOkResponse({
|
|
106
|
+
description: 'Definition version.',
|
|
107
|
+
type: FormDefinitionResponseDto,
|
|
108
|
+
})
|
|
109
|
+
getVersion(
|
|
110
|
+
@Param('orgId') orgId: string,
|
|
111
|
+
@Param('formKey') formKey: string,
|
|
112
|
+
@Param('version', ParseIntPipe) version: number,
|
|
113
|
+
) {
|
|
114
|
+
return this.formsDefinitionsService.getVersion(orgId, formKey, version);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@Patch(':formKey/versions/:version')
|
|
118
|
+
@RequirePermissions('forms.update')
|
|
119
|
+
@ApiParam({ name: 'formKey', description: 'Form definition key.' })
|
|
120
|
+
@ApiParam({ name: 'version', description: 'Definition version number.', type: Number })
|
|
121
|
+
@ApiOperation({ summary: 'Update a draft form definition version.' })
|
|
122
|
+
@ApiOkResponse({
|
|
123
|
+
description: 'Updated draft definition.',
|
|
124
|
+
type: FormDefinitionResponseDto,
|
|
125
|
+
})
|
|
126
|
+
update(
|
|
127
|
+
@Param('orgId') orgId: string,
|
|
128
|
+
@Param('formKey') formKey: string,
|
|
129
|
+
@Param('version', ParseIntPipe) version: number,
|
|
130
|
+
@Body() dto: UpdateFormDefinitionDto,
|
|
131
|
+
) {
|
|
132
|
+
return this.formsDefinitionsService.update(orgId, formKey, version, dto);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@Post(':formKey/versions/:version/publish')
|
|
136
|
+
@HttpCode(200)
|
|
137
|
+
@RequirePermissions('forms.publish')
|
|
138
|
+
@ApiParam({ name: 'formKey', description: 'Form definition key.' })
|
|
139
|
+
@ApiParam({ name: 'version', description: 'Definition version number.', type: Number })
|
|
140
|
+
@ApiOperation({ summary: 'Publish a draft form definition version.' })
|
|
141
|
+
@ApiOkResponse({
|
|
142
|
+
description: 'Published definition.',
|
|
143
|
+
type: FormDefinitionResponseDto,
|
|
144
|
+
})
|
|
145
|
+
publish(
|
|
146
|
+
@Param('orgId') orgId: string,
|
|
147
|
+
@Param('formKey') formKey: string,
|
|
148
|
+
@Param('version', ParseIntPipe) version: number,
|
|
149
|
+
) {
|
|
150
|
+
return this.formsDefinitionsService.publish(orgId, formKey, version);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@Post(':formKey/versions/:version/archive')
|
|
154
|
+
@HttpCode(200)
|
|
155
|
+
@RequirePermissions('forms.archive')
|
|
156
|
+
@ApiParam({ name: 'formKey', description: 'Form definition key.' })
|
|
157
|
+
@ApiParam({ name: 'version', description: 'Definition version number.', type: Number })
|
|
158
|
+
@ApiOperation({ summary: 'Archive a form definition version.' })
|
|
159
|
+
@ApiOkResponse({
|
|
160
|
+
description: 'Archived definition.',
|
|
161
|
+
type: FormDefinitionResponseDto,
|
|
162
|
+
})
|
|
163
|
+
archive(
|
|
164
|
+
@Param('orgId') orgId: string,
|
|
165
|
+
@Param('formKey') formKey: string,
|
|
166
|
+
@Param('version', ParseIntPipe) version: number,
|
|
167
|
+
) {
|
|
168
|
+
return this.formsDefinitionsService.archive(orgId, formKey, version);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@Post(':formKey/versions/:version/public-access')
|
|
172
|
+
@HttpCode(200)
|
|
173
|
+
@RequirePermissions('forms.managePublicAccess')
|
|
174
|
+
@ApiParam({ name: 'formKey', description: 'Form definition key.' })
|
|
175
|
+
@ApiParam({ name: 'version', description: 'Definition version number.', type: Number })
|
|
176
|
+
@ApiOperation({
|
|
177
|
+
summary: 'Flip the public/private access setting on a form definition version.',
|
|
178
|
+
})
|
|
179
|
+
@ApiOkResponse({
|
|
180
|
+
description: 'Updated definition.',
|
|
181
|
+
type: FormDefinitionResponseDto,
|
|
182
|
+
})
|
|
183
|
+
setPublicAccess(
|
|
184
|
+
@Param('orgId') orgId: string,
|
|
185
|
+
@Param('formKey') formKey: string,
|
|
186
|
+
@Param('version', ParseIntPipe) version: number,
|
|
187
|
+
@Body() dto: SetPublicAccessDto,
|
|
188
|
+
) {
|
|
189
|
+
return this.formsDefinitionsService.setPublicAccess(orgId, formKey, version, dto);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Controller,
|
|
3
|
+
Get,
|
|
4
|
+
Param,
|
|
5
|
+
ParseUUIDPipe,
|
|
6
|
+
Post,
|
|
7
|
+
Query,
|
|
8
|
+
UploadedFile,
|
|
9
|
+
UseGuards,
|
|
10
|
+
UseInterceptors,
|
|
11
|
+
} from '@nestjs/common';
|
|
12
|
+
import {
|
|
13
|
+
ApiBearerAuth,
|
|
14
|
+
ApiBody,
|
|
15
|
+
ApiConsumes,
|
|
16
|
+
ApiOkResponse,
|
|
17
|
+
ApiOperation,
|
|
18
|
+
ApiParam,
|
|
19
|
+
ApiTags,
|
|
20
|
+
} from '@nestjs/swagger';
|
|
21
|
+
import { ApiProtectedErrorResponses } from '../../../common/swagger/api-error-responses';
|
|
22
|
+
import { CurrentUser } from '../../auth/presentation/current-user.decorator';
|
|
23
|
+
import { JwtAuthGuard } from '../../auth/infrastructure/passport/jwt-auth.guard';
|
|
24
|
+
import { AuthenticatedUser } from '../../auth/types/authenticated-user';
|
|
25
|
+
import { PermissionGuard } from '../../access-control/application/services/permission.guard';
|
|
26
|
+
import { RequirePermissions } from '../../access-control/presentation/permissions.decorator';
|
|
27
|
+
import { OrgScopeGuard } from '../../request-context/presentation/org-scope.guard';
|
|
28
|
+
import { FileUploadResponseDto } from '../dto/file-upload-response.dto';
|
|
29
|
+
import { UploadFileQueryDto } from '../dto/upload-file-query.dto';
|
|
30
|
+
import { FormsFilesService } from '../application/services/forms-files.service';
|
|
31
|
+
import { FormsUploadInterceptor } from './forms-upload.interceptor';
|
|
32
|
+
|
|
33
|
+
@ApiTags('Forms')
|
|
34
|
+
@ApiBearerAuth()
|
|
35
|
+
@ApiParam({ name: 'orgId', description: 'Organisation ID.', format: 'uuid' })
|
|
36
|
+
@ApiParam({ name: 'formKey', description: 'Form definition key.' })
|
|
37
|
+
@ApiProtectedErrorResponses(404, 409)
|
|
38
|
+
@Controller('organisations/:orgId/forms/:formKey/files')
|
|
39
|
+
@UseGuards(JwtAuthGuard, OrgScopeGuard, PermissionGuard)
|
|
40
|
+
export class FormsFilesController {
|
|
41
|
+
constructor(private readonly filesService: FormsFilesService) {}
|
|
42
|
+
|
|
43
|
+
@Post()
|
|
44
|
+
@RequirePermissions('formSubmissions.create')
|
|
45
|
+
@UseInterceptors(FormsUploadInterceptor)
|
|
46
|
+
@ApiConsumes('multipart/form-data')
|
|
47
|
+
@ApiBody({
|
|
48
|
+
schema: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: { file: { type: 'string', format: 'binary' } },
|
|
51
|
+
required: ['file'],
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
@ApiOperation({
|
|
55
|
+
summary:
|
|
56
|
+
'Upload a file for a form field (before submit). The submission then references the returned fileId.',
|
|
57
|
+
})
|
|
58
|
+
@ApiOkResponse({ description: 'Uploaded file reference.', type: FileUploadResponseDto })
|
|
59
|
+
upload(
|
|
60
|
+
@CurrentUser() user: AuthenticatedUser,
|
|
61
|
+
@Param('orgId') orgId: string,
|
|
62
|
+
@Param('formKey') formKey: string,
|
|
63
|
+
@Query() query: UploadFileQueryDto,
|
|
64
|
+
@UploadedFile() file: { originalname: string; size: number; buffer: Buffer } | undefined,
|
|
65
|
+
) {
|
|
66
|
+
return this.filesService.upload(orgId, formKey, query, file, user);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@Get(':fileId')
|
|
70
|
+
@RequirePermissions('formSubmissions.read')
|
|
71
|
+
@ApiOperation({ summary: 'Return metadata for an uploaded file.' })
|
|
72
|
+
@ApiOkResponse({ description: 'Uploaded file metadata.', type: FileUploadResponseDto })
|
|
73
|
+
getMeta(
|
|
74
|
+
@Param('orgId') orgId: string,
|
|
75
|
+
@Param('fileId', ParseUUIDPipe) fileId: string,
|
|
76
|
+
) {
|
|
77
|
+
return this.filesService.getMeta(orgId, fileId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Body,
|
|
3
|
+
Controller,
|
|
4
|
+
Get,
|
|
5
|
+
Header,
|
|
6
|
+
HttpCode,
|
|
7
|
+
Param,
|
|
8
|
+
ParseUUIDPipe,
|
|
9
|
+
Post,
|
|
10
|
+
Query,
|
|
11
|
+
Res,
|
|
12
|
+
UseGuards,
|
|
13
|
+
} from '@nestjs/common';
|
|
14
|
+
import type { Response } from 'express';
|
|
15
|
+
import {
|
|
16
|
+
ApiBearerAuth,
|
|
17
|
+
ApiOkResponse,
|
|
18
|
+
ApiOperation,
|
|
19
|
+
ApiParam,
|
|
20
|
+
ApiTags,
|
|
21
|
+
ApiUnprocessableEntityResponse,
|
|
22
|
+
} from '@nestjs/swagger';
|
|
23
|
+
import { Throttle } from '@nestjs/throttler';
|
|
24
|
+
import { ErrorResponseDto } from '../../../common/dto/error-response.dto';
|
|
25
|
+
import { ApiProtectedErrorResponses } from '../../../common/swagger/api-error-responses';
|
|
26
|
+
import { PermissionGuard } from '../../access-control/application/services/permission.guard';
|
|
27
|
+
import { RequirePermissions } from '../../access-control/presentation/permissions.decorator';
|
|
28
|
+
import { JwtAuthGuard } from '../../auth/infrastructure/passport/jwt-auth.guard';
|
|
29
|
+
import { CurrentUser } from '../../auth/presentation/current-user.decorator';
|
|
30
|
+
import { AuthenticatedUser } from '../../auth/types/authenticated-user';
|
|
31
|
+
import { OrgScopeGuard } from '../../request-context/presentation/org-scope.guard';
|
|
32
|
+
import { FormsExportService } from '../application/services/forms-export.service';
|
|
33
|
+
import { FormsSubmissionsService } from '../application/services/forms-submissions.service';
|
|
34
|
+
import { ExportSubmissionsQueryDto } from '../dto/export-submissions-query.dto';
|
|
35
|
+
import { ListSubmissionsQueryDto } from '../dto/list-submissions-query.dto';
|
|
36
|
+
import {
|
|
37
|
+
SubmissionListResponseDto,
|
|
38
|
+
SubmissionResponseDto,
|
|
39
|
+
SubmitResultResponseDto,
|
|
40
|
+
ValidationResultResponseDto,
|
|
41
|
+
} from '../dto/submission-response.dto';
|
|
42
|
+
import { SubmitFormDto } from '../dto/submit-form.dto';
|
|
43
|
+
import { ValidateSubmissionDto } from '../dto/validate-submission.dto';
|
|
44
|
+
|
|
45
|
+
@ApiTags('Forms')
|
|
46
|
+
@ApiBearerAuth()
|
|
47
|
+
@ApiParam({ name: 'orgId', description: 'Organisation ID.', format: 'uuid' })
|
|
48
|
+
@ApiParam({ name: 'formKey', description: 'Form definition key.' })
|
|
49
|
+
@ApiProtectedErrorResponses(404, 409)
|
|
50
|
+
@ApiUnprocessableEntityResponse({
|
|
51
|
+
description: 'The submission data failed engine validation.',
|
|
52
|
+
type: ErrorResponseDto,
|
|
53
|
+
})
|
|
54
|
+
@Controller('organisations/:orgId/forms/:formKey/submissions')
|
|
55
|
+
@UseGuards(JwtAuthGuard, OrgScopeGuard, PermissionGuard)
|
|
56
|
+
export class FormsSubmissionsController {
|
|
57
|
+
constructor(
|
|
58
|
+
private readonly formsSubmissionsService: FormsSubmissionsService,
|
|
59
|
+
private readonly formsExportService: FormsExportService,
|
|
60
|
+
) {}
|
|
61
|
+
|
|
62
|
+
@Post()
|
|
63
|
+
@HttpCode(200)
|
|
64
|
+
@Throttle({ default: { limit: 30, ttl: 60_000 } })
|
|
65
|
+
@RequirePermissions('formSubmissions.create')
|
|
66
|
+
@ApiOperation({ summary: 'Submit form data or save a draft submission.' })
|
|
67
|
+
@ApiOkResponse({
|
|
68
|
+
description: 'Pipeline result.',
|
|
69
|
+
type: SubmitResultResponseDto,
|
|
70
|
+
})
|
|
71
|
+
submit(
|
|
72
|
+
@CurrentUser() user: AuthenticatedUser,
|
|
73
|
+
@Param('orgId') orgId: string,
|
|
74
|
+
@Param('formKey') formKey: string,
|
|
75
|
+
@Body() dto: SubmitFormDto,
|
|
76
|
+
) {
|
|
77
|
+
return this.formsSubmissionsService.submit(orgId, formKey, dto, user);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@Post('validate')
|
|
81
|
+
@HttpCode(200)
|
|
82
|
+
@RequirePermissions('formSubmissions.create')
|
|
83
|
+
@ApiOperation({ summary: 'Validate submission data without side effects.' })
|
|
84
|
+
@ApiOkResponse({
|
|
85
|
+
description: 'Validation result.',
|
|
86
|
+
type: ValidationResultResponseDto,
|
|
87
|
+
})
|
|
88
|
+
validate(
|
|
89
|
+
@Param('orgId') orgId: string,
|
|
90
|
+
@Param('formKey') formKey: string,
|
|
91
|
+
@Body() dto: ValidateSubmissionDto,
|
|
92
|
+
) {
|
|
93
|
+
return this.formsSubmissionsService.validate(orgId, formKey, dto);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@Get()
|
|
97
|
+
@RequirePermissions('formSubmissions.read')
|
|
98
|
+
@ApiOperation({ summary: 'List submissions for a form.' })
|
|
99
|
+
@ApiOkResponse({
|
|
100
|
+
description: 'Form submissions.',
|
|
101
|
+
type: SubmissionListResponseDto,
|
|
102
|
+
})
|
|
103
|
+
list(
|
|
104
|
+
@Param('orgId') orgId: string,
|
|
105
|
+
@Param('formKey') formKey: string,
|
|
106
|
+
@Query() query: ListSubmissionsQueryDto,
|
|
107
|
+
) {
|
|
108
|
+
return this.formsSubmissionsService.list(orgId, formKey, query);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Declared before GET :submissionId so the literal segment wins.
|
|
112
|
+
@Get('export')
|
|
113
|
+
@RequirePermissions('formSubmissions.export')
|
|
114
|
+
@Header('Cache-Control', 'no-store')
|
|
115
|
+
@ApiOperation({
|
|
116
|
+
summary:
|
|
117
|
+
'Export submissions (PII egress — separately permissioned and always audited). Defaults to reportable fields.',
|
|
118
|
+
})
|
|
119
|
+
@ApiOkResponse({ description: 'Export payload (JSON object or CSV body).' })
|
|
120
|
+
async export(
|
|
121
|
+
@CurrentUser() user: AuthenticatedUser,
|
|
122
|
+
@Param('orgId') orgId: string,
|
|
123
|
+
@Param('formKey') formKey: string,
|
|
124
|
+
@Query() query: ExportSubmissionsQueryDto,
|
|
125
|
+
@Res({ passthrough: true }) res: Response,
|
|
126
|
+
) {
|
|
127
|
+
const result = await this.formsExportService.export(orgId, formKey, query, user);
|
|
128
|
+
if (result.format === 'csv') {
|
|
129
|
+
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
130
|
+
res.setHeader(
|
|
131
|
+
'Content-Disposition',
|
|
132
|
+
`attachment; filename="${formKey}-submissions.csv"`,
|
|
133
|
+
);
|
|
134
|
+
return result.csv;
|
|
135
|
+
}
|
|
136
|
+
return { columns: result.columns, rows: result.rows };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@Get(':submissionId')
|
|
140
|
+
@RequirePermissions('formSubmissions.read')
|
|
141
|
+
@ApiParam({
|
|
142
|
+
name: 'submissionId',
|
|
143
|
+
description: 'Submission ID.',
|
|
144
|
+
format: 'uuid',
|
|
145
|
+
})
|
|
146
|
+
@ApiOperation({ summary: 'Return a single submission.' })
|
|
147
|
+
@ApiOkResponse({
|
|
148
|
+
description: 'Submission.',
|
|
149
|
+
type: SubmissionResponseDto,
|
|
150
|
+
})
|
|
151
|
+
get(@Param('orgId') orgId: string, @Param('submissionId', ParseUUIDPipe) submissionId: string) {
|
|
152
|
+
return this.formsSubmissionsService.get(orgId, submissionId);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CallHandler,
|
|
3
|
+
ExecutionContext,
|
|
4
|
+
Injectable,
|
|
5
|
+
NestInterceptor,
|
|
6
|
+
} from '@nestjs/common';
|
|
7
|
+
import { ConfigService } from '@nestjs/config';
|
|
8
|
+
import { FileInterceptor } from '@nestjs/platform-express';
|
|
9
|
+
import { memoryStorage } from 'multer';
|
|
10
|
+
import type { Observable } from 'rxjs';
|
|
11
|
+
|
|
12
|
+
@Injectable()
|
|
13
|
+
export class FormsUploadInterceptor implements NestInterceptor {
|
|
14
|
+
private readonly delegate: NestInterceptor;
|
|
15
|
+
|
|
16
|
+
constructor(config: ConfigService) {
|
|
17
|
+
const maxUploadMb = config.get<number>('forms.maxUploadMb') ?? 25;
|
|
18
|
+
const Interceptor = FileInterceptor('file', {
|
|
19
|
+
storage: memoryStorage(),
|
|
20
|
+
limits: { fileSize: maxUploadMb * 1024 * 1024 },
|
|
21
|
+
});
|
|
22
|
+
this.delegate = new Interceptor();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
intercept(
|
|
26
|
+
context: ExecutionContext,
|
|
27
|
+
next: CallHandler,
|
|
28
|
+
): Observable<unknown> | Promise<Observable<unknown>> {
|
|
29
|
+
return this.delegate.intercept(context, next) as
|
|
30
|
+
| Observable<unknown>
|
|
31
|
+
| Promise<Observable<unknown>>;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Body, Controller, Get, HttpCode, Param, Post, UseGuards } from '@nestjs/common';
|
|
2
|
+
import { ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger';
|
|
3
|
+
import { Throttle } from '@nestjs/throttler';
|
|
4
|
+
import { ApiProtectedErrorResponses } from '../../../common/swagger/api-error-responses';
|
|
5
|
+
import { Public } from '../../access-control/presentation/public.decorator';
|
|
6
|
+
import { OrgScopeGuard } from '../../request-context/presentation/org-scope.guard';
|
|
7
|
+
import { FormsPublicService } from '../application/services/forms-public.service';
|
|
8
|
+
import { FormRenderResponseDto } from '../dto/form-render-response.dto';
|
|
9
|
+
import { PublicSubmitFormDto } from '../dto/public-submit-form.dto';
|
|
10
|
+
import { SubmitResultResponseDto } from '../dto/submission-response.dto';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Anonymous endpoints for definitions whose settings.access === 'public'
|
|
14
|
+
* (flipping that setting is gated by forms.managePublicAccess and audited).
|
|
15
|
+
* Like the auth routes, these carry no JwtAuthGuard/PermissionGuard and are
|
|
16
|
+
* NOT in the route-permission registry; they are throttled tighter than the
|
|
17
|
+
* app default, and the engine enforces the remaining Tier-1 abuse controls
|
|
18
|
+
* (per-IP/day caps, captcha, ip/ua stamping).
|
|
19
|
+
*/
|
|
20
|
+
@ApiTags('Forms (public)')
|
|
21
|
+
@ApiParam({ name: 'orgId', description: 'Organisation ID.', format: 'uuid' })
|
|
22
|
+
@ApiParam({ name: 'formKey', description: 'Form definition key.' })
|
|
23
|
+
@ApiProtectedErrorResponses(404, 429)
|
|
24
|
+
@Controller('public/organisations/:orgId/forms/:formKey')
|
|
25
|
+
@UseGuards(OrgScopeGuard)
|
|
26
|
+
export class PublicFormsController {
|
|
27
|
+
constructor(private readonly publicService: FormsPublicService) {}
|
|
28
|
+
|
|
29
|
+
@Get('render')
|
|
30
|
+
@Public()
|
|
31
|
+
@Throttle({ default: { limit: 30, ttl: 60_000 } })
|
|
32
|
+
@ApiOperation({ summary: 'Render a public form (definition + resolved options).' })
|
|
33
|
+
@ApiOkResponse({ description: 'Renderable definition.', type: FormRenderResponseDto })
|
|
34
|
+
render(@Param('orgId') orgId: string, @Param('formKey') formKey: string) {
|
|
35
|
+
return this.publicService.render(orgId, formKey);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@Post('submissions')
|
|
39
|
+
@Public()
|
|
40
|
+
@HttpCode(200)
|
|
41
|
+
@Throttle({ default: { limit: 10, ttl: 60_000 } })
|
|
42
|
+
@ApiOperation({ summary: 'Submit a public form anonymously.' })
|
|
43
|
+
@ApiOkResponse({ description: 'Submission result.', type: SubmitResultResponseDto })
|
|
44
|
+
submit(
|
|
45
|
+
@Param('orgId') orgId: string,
|
|
46
|
+
@Param('formKey') formKey: string,
|
|
47
|
+
@Body() dto: PublicSubmitFormDto,
|
|
48
|
+
) {
|
|
49
|
+
return this.publicService.submit(orgId, formKey, dto);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -48,6 +48,7 @@ export class InvitationResponseDto {
|
|
|
48
48
|
invitedByUserId!: string;
|
|
49
49
|
|
|
50
50
|
@ApiPropertyOptional({
|
|
51
|
+
type: String,
|
|
51
52
|
example: '7efd9a26-fbec-4511-ae37-7fb1c35e5215',
|
|
52
53
|
format: 'uuid',
|
|
53
54
|
nullable: true,
|
|
@@ -67,6 +68,7 @@ export class InvitationResponseDto {
|
|
|
67
68
|
createdAt!: string;
|
|
68
69
|
|
|
69
70
|
@ApiPropertyOptional({
|
|
71
|
+
type: String,
|
|
70
72
|
example: '2026-06-01T10:35:00.000Z',
|
|
71
73
|
format: 'date-time',
|
|
72
74
|
nullable: true,
|
|
@@ -74,6 +76,7 @@ export class InvitationResponseDto {
|
|
|
74
76
|
acceptedAt?: string | null;
|
|
75
77
|
|
|
76
78
|
@ApiPropertyOptional({
|
|
79
|
+
type: String,
|
|
77
80
|
example: '2026-06-01T10:40:00.000Z',
|
|
78
81
|
format: 'date-time',
|
|
79
82
|
nullable: true,
|
|
@@ -97,6 +100,7 @@ export class InvitationListResponseDto {
|
|
|
97
100
|
items!: InvitationResponseDto[];
|
|
98
101
|
|
|
99
102
|
@ApiPropertyOptional({
|
|
103
|
+
type: String,
|
|
100
104
|
example: '17a21ad7-bd1b-42d8-b809-0a3892bb60c3',
|
|
101
105
|
format: 'uuid',
|
|
102
106
|
nullable: true,
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { ConflictException, Injectable } from '@nestjs/common';
|
|
2
|
-
import { Prisma } from '@prisma/client';
|
|
2
|
+
import { MembershipStatus, OrganisationStatus, Prisma } from '@prisma/client';
|
|
3
|
+
import {
|
|
4
|
+
PaginationQueryDto,
|
|
5
|
+
resolvePageLimit,
|
|
6
|
+
toPage,
|
|
7
|
+
} from '../../../../common/dto/pagination-query.dto';
|
|
3
8
|
import { PrismaService } from '../../../../database/prisma/prisma.service';
|
|
4
9
|
import { RequestContextService } from '../../../request-context/application/services/request-context.service';
|
|
5
10
|
import { AuthenticatedUser } from '../../../auth/types/authenticated-user';
|
|
@@ -18,6 +23,67 @@ export class OrganisationsService {
|
|
|
18
23
|
private readonly requestContext: RequestContextService,
|
|
19
24
|
) {}
|
|
20
25
|
|
|
26
|
+
async listMine(currentUser: AuthenticatedUser, query: PaginationQueryDto) {
|
|
27
|
+
const limit = resolvePageLimit(query.limit);
|
|
28
|
+
|
|
29
|
+
const rows = await this.prisma.membership.findMany({
|
|
30
|
+
where: {
|
|
31
|
+
userId: currentUser.id,
|
|
32
|
+
status: MembershipStatus.ACTIVE,
|
|
33
|
+
organisation: {
|
|
34
|
+
status: OrganisationStatus.ACTIVE,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
select: {
|
|
38
|
+
id: true,
|
|
39
|
+
roleId: true,
|
|
40
|
+
isOwner: true,
|
|
41
|
+
isBillingContact: true,
|
|
42
|
+
organisation: {
|
|
43
|
+
select: {
|
|
44
|
+
id: true,
|
|
45
|
+
name: true,
|
|
46
|
+
slug: true,
|
|
47
|
+
status: true,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
role: {
|
|
51
|
+
select: {
|
|
52
|
+
id: true,
|
|
53
|
+
name: true,
|
|
54
|
+
description: true,
|
|
55
|
+
isSystemSeeded: true,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
orderBy: [{ isOwner: 'desc' }, { createdAt: 'asc' }, { id: 'asc' }],
|
|
60
|
+
take: limit + 1,
|
|
61
|
+
...(query.cursor
|
|
62
|
+
? {
|
|
63
|
+
cursor: { id: query.cursor },
|
|
64
|
+
skip: 1,
|
|
65
|
+
}
|
|
66
|
+
: {}),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const page = toPage(rows, limit);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
items: page.items.map((membership) => ({
|
|
73
|
+
id: membership.organisation.id,
|
|
74
|
+
name: membership.organisation.name,
|
|
75
|
+
slug: membership.organisation.slug,
|
|
76
|
+
status: membership.organisation.status,
|
|
77
|
+
membershipId: membership.id,
|
|
78
|
+
roleId: membership.roleId,
|
|
79
|
+
role: membership.role,
|
|
80
|
+
isOwner: membership.isOwner,
|
|
81
|
+
isBillingContact: membership.isBillingContact,
|
|
82
|
+
})),
|
|
83
|
+
nextCursor: page.nextCursor,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
21
87
|
async createOrganisation(currentUser: AuthenticatedUser, dto: CreateOrganisationDto) {
|
|
22
88
|
const name = dto.name.trim();
|
|
23
89
|
const slug = dto.slug?.trim() || (await this.createAvailableSlug(name));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ApiProperty } from '@nestjs/swagger';
|
|
2
2
|
import { OrganisationStatus } from '@prisma/client';
|
|
3
3
|
import { MembershipResponseDto } from '../../../common/dto/membership-response.dto';
|
|
4
|
+
import { RoleSummaryDto } from '../../../common/dto/role-summary.dto';
|
|
4
5
|
|
|
5
6
|
export class OrganisationResponseDto {
|
|
6
7
|
@ApiProperty({
|
|
@@ -60,3 +61,54 @@ export class CreateOrganisationResponseDto {
|
|
|
60
61
|
@ApiProperty({ type: [SeededOrganisationSettingDto] })
|
|
61
62
|
settings!: SeededOrganisationSettingDto[];
|
|
62
63
|
}
|
|
64
|
+
|
|
65
|
+
export class MyOrganisationResponseDto {
|
|
66
|
+
@ApiProperty({
|
|
67
|
+
example: '2c67399d-670c-4025-a5fd-1ea9a211891e',
|
|
68
|
+
format: 'uuid',
|
|
69
|
+
})
|
|
70
|
+
id!: string;
|
|
71
|
+
|
|
72
|
+
@ApiProperty({ example: 'Acme Operations' })
|
|
73
|
+
name!: string;
|
|
74
|
+
|
|
75
|
+
@ApiProperty({ example: 'acme-operations' })
|
|
76
|
+
slug!: string;
|
|
77
|
+
|
|
78
|
+
@ApiProperty({ enum: OrganisationStatus, example: OrganisationStatus.ACTIVE })
|
|
79
|
+
status!: OrganisationStatus;
|
|
80
|
+
|
|
81
|
+
@ApiProperty({
|
|
82
|
+
example: '0a57fb4a-95c6-4f7e-bd5a-f96dbe0599e3',
|
|
83
|
+
format: 'uuid',
|
|
84
|
+
})
|
|
85
|
+
membershipId!: string;
|
|
86
|
+
|
|
87
|
+
@ApiProperty({
|
|
88
|
+
example: 'f602c057-04f4-4ef8-8c84-1b7c62fbf8c5',
|
|
89
|
+
format: 'uuid',
|
|
90
|
+
})
|
|
91
|
+
roleId!: string;
|
|
92
|
+
|
|
93
|
+
@ApiProperty({ type: RoleSummaryDto })
|
|
94
|
+
role!: RoleSummaryDto;
|
|
95
|
+
|
|
96
|
+
@ApiProperty({ example: true })
|
|
97
|
+
isOwner!: boolean;
|
|
98
|
+
|
|
99
|
+
@ApiProperty({ example: true })
|
|
100
|
+
isBillingContact!: boolean;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export class MyOrganisationListResponseDto {
|
|
104
|
+
@ApiProperty({ type: [MyOrganisationResponseDto] })
|
|
105
|
+
items!: MyOrganisationResponseDto[];
|
|
106
|
+
|
|
107
|
+
@ApiProperty({
|
|
108
|
+
type: String,
|
|
109
|
+
example: '2c67399d-670c-4025-a5fd-1ea9a211891e',
|
|
110
|
+
format: 'uuid',
|
|
111
|
+
nullable: true,
|
|
112
|
+
})
|
|
113
|
+
nextCursor!: string | null;
|
|
114
|
+
}
|