@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,140 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Body,
|
|
3
|
+
Controller,
|
|
4
|
+
Delete,
|
|
5
|
+
Get,
|
|
6
|
+
Param,
|
|
7
|
+
ParseUUIDPipe,
|
|
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
|
+
} from '@nestjs/swagger';
|
|
21
|
+
import { DeletedResponseDto } from '../../../common/dto/mutation-response.dto';
|
|
22
|
+
import { ApiProtectedErrorResponses } from '../../../common/swagger/api-error-responses';
|
|
23
|
+
import { PermissionGuard } from '../../access-control/application/services/permission.guard';
|
|
24
|
+
import { RequirePermissions } from '../../access-control/presentation/permissions.decorator';
|
|
25
|
+
import { JwtAuthGuard } from '../../auth/infrastructure/passport/jwt-auth.guard';
|
|
26
|
+
import { OrgScopeGuard } from '../../request-context/presentation/org-scope.guard';
|
|
27
|
+
import { ReportsViewsService } from '../application/services/reports-views.service';
|
|
28
|
+
import { CreateSavedViewDto } from '../dto/create-saved-view.dto';
|
|
29
|
+
import { ListViewsQueryDto } from '../dto/list-views-query.dto';
|
|
30
|
+
import {
|
|
31
|
+
SavedViewListResponseDto,
|
|
32
|
+
SavedViewResponseDto,
|
|
33
|
+
} from '../dto/saved-view-response.dto';
|
|
34
|
+
import { UpdateSavedViewDto } from '../dto/update-saved-view.dto';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Saved views (design §4, finding #9): personal views need reports.read;
|
|
38
|
+
* creating a SHARED view (ownerId null) needs reports.update — and the engine
|
|
39
|
+
* re-checks reports.update on every mutation of a shared view, whatever the
|
|
40
|
+
* route-level key says.
|
|
41
|
+
*/
|
|
42
|
+
@ApiTags('Reports')
|
|
43
|
+
@ApiBearerAuth()
|
|
44
|
+
@ApiParam({ name: 'orgId', description: 'Organisation ID.', format: 'uuid' })
|
|
45
|
+
@ApiProtectedErrorResponses(404, 409)
|
|
46
|
+
@Controller('organisations/:orgId/reports')
|
|
47
|
+
@UseGuards(JwtAuthGuard, OrgScopeGuard, PermissionGuard)
|
|
48
|
+
export class ReportsViewsController {
|
|
49
|
+
constructor(private readonly reportsViewsService: ReportsViewsService) {}
|
|
50
|
+
|
|
51
|
+
@Get(':key/views')
|
|
52
|
+
@RequirePermissions('reports.read')
|
|
53
|
+
@ApiParam({ name: 'key', description: 'Report definition key.' })
|
|
54
|
+
@ApiOperation({
|
|
55
|
+
summary:
|
|
56
|
+
'List saved views (personal + shared), each with a compatibility verdict against the requested version (§4).',
|
|
57
|
+
})
|
|
58
|
+
@ApiOkResponse({
|
|
59
|
+
description: 'Saved views with compatibility verdicts.',
|
|
60
|
+
type: SavedViewListResponseDto,
|
|
61
|
+
})
|
|
62
|
+
list(
|
|
63
|
+
@Param('orgId') orgId: string,
|
|
64
|
+
@Param('key') key: string,
|
|
65
|
+
@Query() query: ListViewsQueryDto,
|
|
66
|
+
) {
|
|
67
|
+
return this.reportsViewsService.list(orgId, key, query);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@Post(':key/views')
|
|
71
|
+
@RequirePermissions('reports.read')
|
|
72
|
+
@ApiParam({ name: 'key', description: 'Report definition key.' })
|
|
73
|
+
@ApiOperation({ summary: 'Create a PERSONAL saved view.' })
|
|
74
|
+
@ApiCreatedResponse({
|
|
75
|
+
description: 'Created view.',
|
|
76
|
+
type: SavedViewResponseDto,
|
|
77
|
+
})
|
|
78
|
+
createPersonal(
|
|
79
|
+
@Param('orgId') orgId: string,
|
|
80
|
+
@Param('key') key: string,
|
|
81
|
+
@Body() dto: CreateSavedViewDto,
|
|
82
|
+
) {
|
|
83
|
+
return this.reportsViewsService.createPersonal(orgId, key, dto);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@Post(':key/views/shared')
|
|
87
|
+
@RequirePermissions('reports.update')
|
|
88
|
+
@ApiParam({ name: 'key', description: 'Report definition key.' })
|
|
89
|
+
@ApiOperation({ summary: 'Create a SHARED (org-wide) saved view (finding #9).' })
|
|
90
|
+
@ApiCreatedResponse({
|
|
91
|
+
description: 'Created shared view.',
|
|
92
|
+
type: SavedViewResponseDto,
|
|
93
|
+
})
|
|
94
|
+
createShared(
|
|
95
|
+
@Param('orgId') orgId: string,
|
|
96
|
+
@Param('key') key: string,
|
|
97
|
+
@Body() dto: CreateSavedViewDto,
|
|
98
|
+
) {
|
|
99
|
+
return this.reportsViewsService.createShared(orgId, key, dto);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@Patch(':key/views/:viewId')
|
|
103
|
+
@RequirePermissions('reports.read')
|
|
104
|
+
@ApiParam({ name: 'key', description: 'Report definition key.' })
|
|
105
|
+
@ApiParam({ name: 'viewId', description: 'Saved view ID.', format: 'uuid' })
|
|
106
|
+
@ApiOperation({
|
|
107
|
+
summary: 'Rename / respec a saved view. Personal: owner only. Shared: reports.update.',
|
|
108
|
+
})
|
|
109
|
+
@ApiOkResponse({
|
|
110
|
+
description: 'Updated view.',
|
|
111
|
+
type: SavedViewResponseDto,
|
|
112
|
+
})
|
|
113
|
+
update(
|
|
114
|
+
@Param('orgId') orgId: string,
|
|
115
|
+
@Param('key') key: string,
|
|
116
|
+
@Param('viewId', ParseUUIDPipe) viewId: string,
|
|
117
|
+
@Body() dto: UpdateSavedViewDto,
|
|
118
|
+
) {
|
|
119
|
+
return this.reportsViewsService.update(orgId, key, viewId, dto);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@Delete(':key/views/:viewId')
|
|
123
|
+
@RequirePermissions('reports.read')
|
|
124
|
+
@ApiParam({ name: 'key', description: 'Report definition key.' })
|
|
125
|
+
@ApiParam({ name: 'viewId', description: 'Saved view ID.', format: 'uuid' })
|
|
126
|
+
@ApiOperation({
|
|
127
|
+
summary: 'Delete a saved view. Personal: owner only. Shared: reports.update.',
|
|
128
|
+
})
|
|
129
|
+
@ApiOkResponse({
|
|
130
|
+
description: 'Delete confirmation.',
|
|
131
|
+
type: DeletedResponseDto,
|
|
132
|
+
})
|
|
133
|
+
delete(
|
|
134
|
+
@Param('orgId') orgId: string,
|
|
135
|
+
@Param('key') key: string,
|
|
136
|
+
@Param('viewId', ParseUUIDPipe) viewId: string,
|
|
137
|
+
) {
|
|
138
|
+
return this.reportsViewsService.delete(orgId, key, viewId);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { FormsModule } from '../forms/forms.module';
|
|
3
|
+
import { ReportsModule } from './reports.module';
|
|
4
|
+
import { FormReportSourceProvider } from './infrastructure/forms-adapter/form-report-source.adapter';
|
|
5
|
+
import {
|
|
6
|
+
EditSubmissionRowAction,
|
|
7
|
+
UpdateSubmissionStatusRowAction,
|
|
8
|
+
} from './infrastructure/forms-adapter/form-row-actions';
|
|
9
|
+
import { ReportsFormsBridgeBootstrap } from './infrastructure/forms-adapter/forms-bridge-bootstrap.service';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* OPTIONAL bridge between the report and form engines (report design
|
|
13
|
+
* §3.2/§13). Importing it adds form-backed reports (`source.kind: "form"`) and
|
|
14
|
+
* the delegated editSubmission/updateStatus grid verbs to the running app.
|
|
15
|
+
*
|
|
16
|
+
* It is the single artifact aware of BOTH products: it imports FormsModule
|
|
17
|
+
* (for SubmissionService, the form definition store, and the forms request
|
|
18
|
+
* context) and ReportsModule (for the engine's shared source-provider and
|
|
19
|
+
* row-action registries), and wires the form adapter into them at boot.
|
|
20
|
+
*
|
|
21
|
+
* Drop this import from AppModule and ReportsModule keeps working over custom
|
|
22
|
+
* sources alone — the standalone guarantee, made structural at the glue layer.
|
|
23
|
+
*/
|
|
24
|
+
@Module({
|
|
25
|
+
imports: [FormsModule, ReportsModule],
|
|
26
|
+
providers: [
|
|
27
|
+
FormReportSourceProvider,
|
|
28
|
+
EditSubmissionRowAction,
|
|
29
|
+
UpdateSubmissionStatusRowAction,
|
|
30
|
+
ReportsFormsBridgeBootstrap,
|
|
31
|
+
],
|
|
32
|
+
})
|
|
33
|
+
export class ReportsFormsModule {}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { ConfigService } from '@nestjs/config';
|
|
3
|
+
import { DiscoveryModule } from '@nestjs/core';
|
|
4
|
+
import {
|
|
5
|
+
createInMemoryResultCache,
|
|
6
|
+
ReportActionService,
|
|
7
|
+
ReportDefinitionService,
|
|
8
|
+
ReportExportService,
|
|
9
|
+
ReportQueryService,
|
|
10
|
+
ReportRowActionRegistry,
|
|
11
|
+
ReportSourceRegistry,
|
|
12
|
+
ReportTagService,
|
|
13
|
+
ReportViewService,
|
|
14
|
+
SourceProviderRegistry,
|
|
15
|
+
type ResultCache,
|
|
16
|
+
} from '@ftisindia/report-builder';
|
|
17
|
+
import { AuthModule } from '../auth/auth.module';
|
|
18
|
+
import { ReportsActionsService } from './application/services/reports-actions.service';
|
|
19
|
+
import { ReportsDefinitionsService } from './application/services/reports-definitions.service';
|
|
20
|
+
import { ReportsExportsService } from './application/services/reports-exports.service';
|
|
21
|
+
import { ReportsExportDispatcherService } from './application/services/reports-export-dispatcher.service';
|
|
22
|
+
import { ReportsQueriesService } from './application/services/reports-queries.service';
|
|
23
|
+
import { ReportsSettingsReader } from './application/services/reports-settings-reader.service';
|
|
24
|
+
import { ReportsViewsService } from './application/services/reports-views.service';
|
|
25
|
+
import { CaslReportsAuthorization } from './infrastructure/casl-reports-authorization';
|
|
26
|
+
import { PrismaReportsAuditSink } from './infrastructure/audit-sink.adapter';
|
|
27
|
+
import { ReportsPrismaTxRunner } from './infrastructure/prisma-tx-runner';
|
|
28
|
+
import { PrismaQueryExecutor } from './infrastructure/prisma-query-executor';
|
|
29
|
+
import { PrismaSnapshotRunner } from './infrastructure/prisma-snapshot-runner';
|
|
30
|
+
import { PrismaReportsCatalog } from './infrastructure/prisma-catalog.adapter';
|
|
31
|
+
import { ReportsJobQueueNoop } from './infrastructure/reports-job-queue.adapter';
|
|
32
|
+
import { LocalDiskReportExportStorage } from './infrastructure/storage/local-disk-export-storage.adapter';
|
|
33
|
+
import { RequestReportsContext } from './infrastructure/request-reports-context';
|
|
34
|
+
import { ReportsRegistryBootstrapService } from './infrastructure/registry/registry-bootstrap.service';
|
|
35
|
+
import { ReportsSchemaCheckService } from './infrastructure/schema-check/reports-schema-check.service';
|
|
36
|
+
import {
|
|
37
|
+
PrismaBulkActionRunStore,
|
|
38
|
+
PrismaExportJobStore,
|
|
39
|
+
PrismaReportDefinitionStore,
|
|
40
|
+
PrismaRowTagStore,
|
|
41
|
+
PrismaSavedViewStore,
|
|
42
|
+
} from './infrastructure/stores';
|
|
43
|
+
import { ReportsActionsController } from './presentation/reports-actions.controller';
|
|
44
|
+
import { ReportsDefinitionsController } from './presentation/reports-definitions.controller';
|
|
45
|
+
import { ReportsExportController } from './presentation/reports-export.controller';
|
|
46
|
+
import { ReportsExportJobsController } from './presentation/reports-export-jobs.controller';
|
|
47
|
+
import { ReportsQueryController } from './presentation/reports-query.controller';
|
|
48
|
+
import { ReportsViewsController } from './presentation/reports-views.controller';
|
|
49
|
+
import { OrgMembersReportSource } from './sources/org-members.source';
|
|
50
|
+
import { REPORTS_RESULT_CACHE, REPORTS_TOKEN_SECRET } from './reports.tokens';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The reports glue module — binds the framework-free @ftisindia/report-builder
|
|
54
|
+
* engine to this app's services (request context, CASL RBAC, AuditService,
|
|
55
|
+
* Prisma) and ships reports-OWNED async-export infrastructure (its own worker
|
|
56
|
+
* over ReportExportJob + org-scoped file storage). It imports NO FormsModule:
|
|
57
|
+
* form-backed sources and the delegated grid verbs live in the optional
|
|
58
|
+
* ReportsFormsModule bridge (§3.2 standalone guarantee). Engine services are
|
|
59
|
+
* composed here via factories over the adapters and stores; app extensions
|
|
60
|
+
* register through the @ReportSource/@ReportRowAction decorators.
|
|
61
|
+
*
|
|
62
|
+
* The engine core knows nothing about the form builder (the standalone
|
|
63
|
+
* guarantee, report design §3.2); the forms adapter wired below is what makes
|
|
64
|
+
* `kind: "form"` sources exist in THIS app.
|
|
65
|
+
*/
|
|
66
|
+
@Module({
|
|
67
|
+
imports: [AuthModule, DiscoveryModule],
|
|
68
|
+
controllers: [
|
|
69
|
+
// Listed before the definitions controller so the literal 'exports'
|
|
70
|
+
// segment wins over the ':key' parameter on the shared base path.
|
|
71
|
+
ReportsExportJobsController,
|
|
72
|
+
ReportsDefinitionsController,
|
|
73
|
+
ReportsQueryController,
|
|
74
|
+
ReportsViewsController,
|
|
75
|
+
ReportsActionsController,
|
|
76
|
+
ReportsExportController,
|
|
77
|
+
],
|
|
78
|
+
providers: [
|
|
79
|
+
// Adapters over the ecosystem seams.
|
|
80
|
+
RequestReportsContext,
|
|
81
|
+
CaslReportsAuthorization,
|
|
82
|
+
PrismaReportsAuditSink,
|
|
83
|
+
ReportsPrismaTxRunner,
|
|
84
|
+
PrismaQueryExecutor,
|
|
85
|
+
PrismaSnapshotRunner,
|
|
86
|
+
PrismaReportsCatalog,
|
|
87
|
+
// Reports-OWNED async-export infrastructure — no forms outbox / UploadedFile.
|
|
88
|
+
ReportsJobQueueNoop,
|
|
89
|
+
LocalDiskReportExportStorage,
|
|
90
|
+
ReportsExportDispatcherService,
|
|
91
|
+
// Prisma store implementations of the engine ports.
|
|
92
|
+
PrismaReportDefinitionStore,
|
|
93
|
+
PrismaSavedViewStore,
|
|
94
|
+
PrismaExportJobStore,
|
|
95
|
+
PrismaBulkActionRunStore,
|
|
96
|
+
PrismaRowTagStore,
|
|
97
|
+
ReportsSettingsReader,
|
|
98
|
+
// Engine registries — singletons; extensions land via the bootstrap scan.
|
|
99
|
+
{ provide: ReportSourceRegistry, useFactory: () => new ReportSourceRegistry() },
|
|
100
|
+
{ provide: SourceProviderRegistry, useFactory: () => new SourceProviderRegistry() },
|
|
101
|
+
{ provide: ReportRowActionRegistry, useFactory: () => new ReportRowActionRegistry() },
|
|
102
|
+
// Result cache (§5.7) — in-memory by default; rebind the token for Redis.
|
|
103
|
+
{ provide: REPORTS_RESULT_CACHE, useFactory: () => createInMemoryResultCache() },
|
|
104
|
+
// HMAC key bytes for cursors and action tokens (resolved in reports.config).
|
|
105
|
+
{
|
|
106
|
+
provide: REPORTS_TOKEN_SECRET,
|
|
107
|
+
useFactory: (config: ConfigService) => {
|
|
108
|
+
const secret = config.get<Uint8Array>('reports.tokenSecret');
|
|
109
|
+
if (secret === undefined || secret.length === 0) {
|
|
110
|
+
throw new Error('reports.tokenSecret failed to resolve — check reports.config.ts.');
|
|
111
|
+
}
|
|
112
|
+
return secret;
|
|
113
|
+
},
|
|
114
|
+
inject: [ConfigService],
|
|
115
|
+
},
|
|
116
|
+
// Engine orchestration services, composed over the seams above.
|
|
117
|
+
{
|
|
118
|
+
provide: ReportDefinitionService,
|
|
119
|
+
useFactory: (
|
|
120
|
+
store: PrismaReportDefinitionStore,
|
|
121
|
+
providers: SourceProviderRegistry,
|
|
122
|
+
rowActions: ReportRowActionRegistry,
|
|
123
|
+
authz: CaslReportsAuthorization,
|
|
124
|
+
audit: PrismaReportsAuditSink,
|
|
125
|
+
txRunner: ReportsPrismaTxRunner,
|
|
126
|
+
catalog: PrismaReportsCatalog,
|
|
127
|
+
executor: PrismaQueryExecutor,
|
|
128
|
+
settings: ReportsSettingsReader,
|
|
129
|
+
secret: Uint8Array,
|
|
130
|
+
) =>
|
|
131
|
+
new ReportDefinitionService({
|
|
132
|
+
store,
|
|
133
|
+
providers,
|
|
134
|
+
rowActions,
|
|
135
|
+
authz,
|
|
136
|
+
audit,
|
|
137
|
+
txRunner,
|
|
138
|
+
catalog,
|
|
139
|
+
executor,
|
|
140
|
+
policyFor: (orgId) => settings.policyFor(orgId),
|
|
141
|
+
secret,
|
|
142
|
+
}),
|
|
143
|
+
inject: [
|
|
144
|
+
PrismaReportDefinitionStore,
|
|
145
|
+
SourceProviderRegistry,
|
|
146
|
+
ReportRowActionRegistry,
|
|
147
|
+
CaslReportsAuthorization,
|
|
148
|
+
PrismaReportsAuditSink,
|
|
149
|
+
ReportsPrismaTxRunner,
|
|
150
|
+
PrismaReportsCatalog,
|
|
151
|
+
PrismaQueryExecutor,
|
|
152
|
+
ReportsSettingsReader,
|
|
153
|
+
REPORTS_TOKEN_SECRET,
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
provide: ReportQueryService,
|
|
158
|
+
useFactory: (
|
|
159
|
+
store: PrismaReportDefinitionStore,
|
|
160
|
+
providers: SourceProviderRegistry,
|
|
161
|
+
rowActions: ReportRowActionRegistry,
|
|
162
|
+
tags: PrismaRowTagStore,
|
|
163
|
+
executor: PrismaQueryExecutor,
|
|
164
|
+
authz: CaslReportsAuthorization,
|
|
165
|
+
cache: ResultCache,
|
|
166
|
+
settings: ReportsSettingsReader,
|
|
167
|
+
secret: Uint8Array,
|
|
168
|
+
) =>
|
|
169
|
+
new ReportQueryService({
|
|
170
|
+
store,
|
|
171
|
+
providers,
|
|
172
|
+
rowActions,
|
|
173
|
+
tags,
|
|
174
|
+
executor,
|
|
175
|
+
authz,
|
|
176
|
+
cache,
|
|
177
|
+
policyFor: (orgId) => settings.policyFor(orgId),
|
|
178
|
+
secret,
|
|
179
|
+
}),
|
|
180
|
+
inject: [
|
|
181
|
+
PrismaReportDefinitionStore,
|
|
182
|
+
SourceProviderRegistry,
|
|
183
|
+
ReportRowActionRegistry,
|
|
184
|
+
PrismaRowTagStore,
|
|
185
|
+
PrismaQueryExecutor,
|
|
186
|
+
CaslReportsAuthorization,
|
|
187
|
+
REPORTS_RESULT_CACHE,
|
|
188
|
+
ReportsSettingsReader,
|
|
189
|
+
REPORTS_TOKEN_SECRET,
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
provide: ReportViewService,
|
|
194
|
+
useFactory: (
|
|
195
|
+
views: PrismaSavedViewStore,
|
|
196
|
+
definitions: PrismaReportDefinitionStore,
|
|
197
|
+
authz: CaslReportsAuthorization,
|
|
198
|
+
audit: PrismaReportsAuditSink,
|
|
199
|
+
txRunner: ReportsPrismaTxRunner,
|
|
200
|
+
) => new ReportViewService({ views, definitions, authz, audit, txRunner }),
|
|
201
|
+
inject: [
|
|
202
|
+
PrismaSavedViewStore,
|
|
203
|
+
PrismaReportDefinitionStore,
|
|
204
|
+
CaslReportsAuthorization,
|
|
205
|
+
PrismaReportsAuditSink,
|
|
206
|
+
ReportsPrismaTxRunner,
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
provide: ReportTagService,
|
|
211
|
+
useFactory: (
|
|
212
|
+
tags: PrismaRowTagStore,
|
|
213
|
+
authz: CaslReportsAuthorization,
|
|
214
|
+
audit: PrismaReportsAuditSink,
|
|
215
|
+
settings: ReportsSettingsReader,
|
|
216
|
+
) =>
|
|
217
|
+
new ReportTagService({
|
|
218
|
+
tags,
|
|
219
|
+
authz,
|
|
220
|
+
audit,
|
|
221
|
+
policyFor: (orgId) => settings.policyFor(orgId),
|
|
222
|
+
}),
|
|
223
|
+
inject: [
|
|
224
|
+
PrismaRowTagStore,
|
|
225
|
+
CaslReportsAuthorization,
|
|
226
|
+
PrismaReportsAuditSink,
|
|
227
|
+
ReportsSettingsReader,
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
provide: ReportActionService,
|
|
232
|
+
useFactory: (
|
|
233
|
+
definitions: PrismaReportDefinitionStore,
|
|
234
|
+
providers: SourceProviderRegistry,
|
|
235
|
+
actions: ReportRowActionRegistry,
|
|
236
|
+
runs: PrismaBulkActionRunStore,
|
|
237
|
+
executor: PrismaQueryExecutor,
|
|
238
|
+
authz: CaslReportsAuthorization,
|
|
239
|
+
audit: PrismaReportsAuditSink,
|
|
240
|
+
txRunner: ReportsPrismaTxRunner,
|
|
241
|
+
cache: ResultCache,
|
|
242
|
+
settings: ReportsSettingsReader,
|
|
243
|
+
secret: Uint8Array,
|
|
244
|
+
) =>
|
|
245
|
+
new ReportActionService({
|
|
246
|
+
definitions,
|
|
247
|
+
providers,
|
|
248
|
+
actions,
|
|
249
|
+
runs,
|
|
250
|
+
executor,
|
|
251
|
+
authz,
|
|
252
|
+
audit,
|
|
253
|
+
txRunner,
|
|
254
|
+
cache,
|
|
255
|
+
policyFor: (orgId) => settings.policyFor(orgId),
|
|
256
|
+
secret,
|
|
257
|
+
// Glue row actions are Nest providers with their own injection —
|
|
258
|
+
// the engine's service-locator seam stays empty in this app.
|
|
259
|
+
services: {},
|
|
260
|
+
}),
|
|
261
|
+
inject: [
|
|
262
|
+
PrismaReportDefinitionStore,
|
|
263
|
+
SourceProviderRegistry,
|
|
264
|
+
ReportRowActionRegistry,
|
|
265
|
+
PrismaBulkActionRunStore,
|
|
266
|
+
PrismaQueryExecutor,
|
|
267
|
+
CaslReportsAuthorization,
|
|
268
|
+
PrismaReportsAuditSink,
|
|
269
|
+
ReportsPrismaTxRunner,
|
|
270
|
+
REPORTS_RESULT_CACHE,
|
|
271
|
+
ReportsSettingsReader,
|
|
272
|
+
REPORTS_TOKEN_SECRET,
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
provide: ReportExportService,
|
|
277
|
+
useFactory: (
|
|
278
|
+
definitions: PrismaReportDefinitionStore,
|
|
279
|
+
providers: SourceProviderRegistry,
|
|
280
|
+
jobs: PrismaExportJobStore,
|
|
281
|
+
queue: ReportsJobQueueNoop,
|
|
282
|
+
sink: LocalDiskReportExportStorage,
|
|
283
|
+
snapshot: PrismaSnapshotRunner,
|
|
284
|
+
executor: PrismaQueryExecutor,
|
|
285
|
+
authz: CaslReportsAuthorization,
|
|
286
|
+
audit: PrismaReportsAuditSink,
|
|
287
|
+
txRunner: ReportsPrismaTxRunner,
|
|
288
|
+
settings: ReportsSettingsReader,
|
|
289
|
+
secret: Uint8Array,
|
|
290
|
+
) =>
|
|
291
|
+
new ReportExportService({
|
|
292
|
+
definitions,
|
|
293
|
+
providers,
|
|
294
|
+
jobs,
|
|
295
|
+
queue,
|
|
296
|
+
sink,
|
|
297
|
+
snapshot,
|
|
298
|
+
executor,
|
|
299
|
+
authz,
|
|
300
|
+
audit,
|
|
301
|
+
txRunner,
|
|
302
|
+
policyFor: (orgId) => settings.policyFor(orgId),
|
|
303
|
+
secret,
|
|
304
|
+
}),
|
|
305
|
+
inject: [
|
|
306
|
+
PrismaReportDefinitionStore,
|
|
307
|
+
SourceProviderRegistry,
|
|
308
|
+
PrismaExportJobStore,
|
|
309
|
+
ReportsJobQueueNoop,
|
|
310
|
+
LocalDiskReportExportStorage,
|
|
311
|
+
PrismaSnapshotRunner,
|
|
312
|
+
PrismaQueryExecutor,
|
|
313
|
+
CaslReportsAuthorization,
|
|
314
|
+
PrismaReportsAuditSink,
|
|
315
|
+
ReportsPrismaTxRunner,
|
|
316
|
+
ReportsSettingsReader,
|
|
317
|
+
REPORTS_TOKEN_SECRET,
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
// The example custom source (form-backed sources + delegated form verbs
|
|
321
|
+
// live in the optional ReportsFormsModule, not here — §3.2 standalone).
|
|
322
|
+
OrgMembersReportSource,
|
|
323
|
+
// Startup wiring + background work.
|
|
324
|
+
ReportsRegistryBootstrapService,
|
|
325
|
+
ReportsSchemaCheckService,
|
|
326
|
+
// Thin HTTP-facing services.
|
|
327
|
+
ReportsDefinitionsService,
|
|
328
|
+
ReportsQueriesService,
|
|
329
|
+
ReportsViewsService,
|
|
330
|
+
ReportsActionsService,
|
|
331
|
+
ReportsExportsService,
|
|
332
|
+
],
|
|
333
|
+
exports: [ReportSourceRegistry, SourceProviderRegistry, ReportRowActionRegistry],
|
|
334
|
+
})
|
|
335
|
+
export class ReportsModule {}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DI tokens for swappable reports seams. The defaults are bound in
|
|
3
|
+
* reports.module.ts; apps override them with custom providers (e.g. a
|
|
4
|
+
* Redis-backed ResultCache) without touching the module internals.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Binds the engine's ResultCache port (report design §5.7). */
|
|
8
|
+
export const REPORTS_RESULT_CACHE = Symbol('REPORTS_RESULT_CACHE');
|
|
9
|
+
|
|
10
|
+
/** Key bytes for cursor/action-token HMACs (report design §5.3/§6.3). */
|
|
11
|
+
export const REPORTS_TOKEN_SECRET = Symbol('REPORTS_TOKEN_SECRET');
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import type { ReportSourceDef, SourceManifest, SourceQuery } from '@ftisindia/report-builder';
|
|
3
|
+
import { ReportSource } from '../infrastructure/registry/report-extension.decorators';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `kind: "custom"` example source (report design §3.2) over the template's own
|
|
7
|
+
* Membership/User/Role tables — the Phase-1 milestone proof (§14): a complete
|
|
8
|
+
* report with NO form-builder involvement, demonstrating the standalone
|
|
9
|
+
* guarantee before form integration exists.
|
|
10
|
+
*
|
|
11
|
+
* The contract split (§3): this class declares STRUCTURE only — the compiler
|
|
12
|
+
* owns query construction. It never sees a QuerySpec, so it cannot be injected
|
|
13
|
+
* and cannot bypass the allowlist.
|
|
14
|
+
*
|
|
15
|
+
* Nullability is read off prisma/schema.prisma, not assumed: User.displayName
|
|
16
|
+
* and User.email are nullable (String?), so their sortable columns are
|
|
17
|
+
* COALESCE-wrapped to '' and declared nullable: false — NULL poisons keyset
|
|
18
|
+
* predicates (§5.3). Role.name and the Membership columns are NOT NULL.
|
|
19
|
+
*
|
|
20
|
+
* Index specs are honest documentation (§5.2): they name the indexes an app
|
|
21
|
+
* WOULD create to publish this source on the indexed tier (org-leading btrees
|
|
22
|
+
* are impossible for User-side columns — User carries no orgId — so those
|
|
23
|
+
* specs live on "User" without the org prefix). The shipped example report
|
|
24
|
+
* runs on the LIVE tier, where the catalog lint does not require them.
|
|
25
|
+
*/
|
|
26
|
+
@Injectable()
|
|
27
|
+
@ReportSource()
|
|
28
|
+
export class OrgMembersReportSource implements ReportSourceDef {
|
|
29
|
+
readonly key = 'org-members';
|
|
30
|
+
|
|
31
|
+
manifest(): SourceManifest {
|
|
32
|
+
return {
|
|
33
|
+
// Membership id — the stable row identity tags and actions bind to (§7).
|
|
34
|
+
rowId: 'm."id"',
|
|
35
|
+
orgScoped: true,
|
|
36
|
+
columns: [
|
|
37
|
+
{
|
|
38
|
+
id: 'displayName',
|
|
39
|
+
sql: `COALESCE(u."displayName", '')`,
|
|
40
|
+
type: 'text',
|
|
41
|
+
sortable: true,
|
|
42
|
+
filterable: true,
|
|
43
|
+
searchable: true,
|
|
44
|
+
search: { mode: 'trgm' },
|
|
45
|
+
nullable: false,
|
|
46
|
+
collation: 'C',
|
|
47
|
+
index: {
|
|
48
|
+
name: 'idx_rb_member_display',
|
|
49
|
+
kind: 'btree',
|
|
50
|
+
table: 'User',
|
|
51
|
+
expr: `COALESCE("displayName", '') COLLATE "C"`,
|
|
52
|
+
},
|
|
53
|
+
// A btree cannot carry gin_trgm_ops — search gets its own index.
|
|
54
|
+
searchIndex: {
|
|
55
|
+
name: 'idx_rb_member_display_trgm',
|
|
56
|
+
kind: 'gin',
|
|
57
|
+
table: 'User',
|
|
58
|
+
expr: `COALESCE("displayName", '') gin_trgm_ops`,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'email',
|
|
63
|
+
// User.email is String? in schema.prisma — surrogate '' keeps the
|
|
64
|
+
// sort key non-null (§5.3).
|
|
65
|
+
sql: `COALESCE(u."email", '')`,
|
|
66
|
+
type: 'text',
|
|
67
|
+
sortable: true,
|
|
68
|
+
filterable: true,
|
|
69
|
+
nullable: false,
|
|
70
|
+
collation: 'C',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'role',
|
|
74
|
+
sql: 'r."name"',
|
|
75
|
+
type: 'text',
|
|
76
|
+
sortable: true,
|
|
77
|
+
filterable: true,
|
|
78
|
+
nullable: false,
|
|
79
|
+
collation: 'C',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'status',
|
|
83
|
+
// MembershipStatus enum → text for binds and projection.
|
|
84
|
+
sql: 'm."status"::text',
|
|
85
|
+
type: 'enum',
|
|
86
|
+
filterable: true,
|
|
87
|
+
nullable: false,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'joinedAt',
|
|
91
|
+
sql: 'm."createdAt"',
|
|
92
|
+
type: 'datetime',
|
|
93
|
+
valueKind: 'native',
|
|
94
|
+
sortable: true,
|
|
95
|
+
nullable: false,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** FROM/JOIN only — WHERE/ORDER/LIMIT belong to the compiler (§3). */
|
|
102
|
+
baseQuery(): SourceQuery {
|
|
103
|
+
return {
|
|
104
|
+
from:
|
|
105
|
+
'"Membership" m' +
|
|
106
|
+
' JOIN "User" u ON u."id" = m."userId"' +
|
|
107
|
+
' JOIN "Role" r ON r."id" = m."roleId"',
|
|
108
|
+
orgColumn: 'm."orgId"',
|
|
109
|
+
primaryTable: 'Membership',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|