@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.
Files changed (167) hide show
  1. package/package.json +1 -1
  2. package/template/.env.example +28 -0
  3. package/template/README.md +51 -0
  4. package/template/_gitignore +6 -0
  5. package/template/_package.json +10 -1
  6. package/template/docs/FORMS.md +188 -0
  7. package/template/docs/FORMS_CHECKLIST.md +69 -0
  8. package/template/docs/REPORTS.md +255 -0
  9. package/template/docs/REPORTS_CHECKLIST.md +152 -0
  10. package/template/prisma/migrations/20260612000000_add_form_builder/migration.sql +147 -0
  11. package/template/prisma/migrations/20260613000000_add_report_builder/migration.sql +129 -0
  12. package/template/prisma/migrations/20260616000000_add_form_outbox_claimed_by/migration.sql +5 -0
  13. package/template/prisma/schema.prisma +289 -0
  14. package/template/scripts/export-openapi.ts +85 -0
  15. package/template/scripts/gen-form.mjs +149 -0
  16. package/template/scripts/push-form.ts +124 -0
  17. package/template/src/app.module.ts +30 -8
  18. package/template/src/common/dto/membership-response.dto.ts +1 -0
  19. package/template/src/common/dto/role-summary.dto.ts +3 -3
  20. package/template/src/common/dto/user-summary.dto.ts +3 -3
  21. package/template/src/config/env.validation.ts +28 -0
  22. package/template/src/config/forms.config.ts +13 -0
  23. package/template/src/config/index.ts +2 -0
  24. package/template/src/config/openapi.ts +12 -0
  25. package/template/src/config/reports-secret.ts +15 -0
  26. package/template/src/config/reports.config.ts +18 -0
  27. package/template/src/main.ts +3 -12
  28. package/template/src/modules/access-control/dto/access-control-response.dto.ts +3 -0
  29. package/template/src/modules/access-control/dto/current-access-control-response.dto.ts +5 -1
  30. package/template/src/modules/access-control/types/permission-key.ts +27 -0
  31. package/template/src/modules/access-control/types/route-permission-registry.ts +183 -0
  32. package/template/src/modules/audit/dto/audit-response.dto.ts +7 -3
  33. package/template/src/modules/auth/auth.module.ts +3 -1
  34. package/template/src/modules/auth/dto/auth-response.dto.ts +1 -1
  35. package/template/src/modules/forms/application/services/file-gc.service.ts +85 -0
  36. package/template/src/modules/forms/application/services/forms-definitions.service.ts +137 -0
  37. package/template/src/modules/forms/application/services/forms-error.mapper.ts +64 -0
  38. package/template/src/modules/forms/application/services/forms-export.service.ts +210 -0
  39. package/template/src/modules/forms/application/services/forms-files.service.ts +164 -0
  40. package/template/src/modules/forms/application/services/forms-public.service.ts +49 -0
  41. package/template/src/modules/forms/application/services/forms-settings-reader.service.ts +53 -0
  42. package/template/src/modules/forms/application/services/forms-submissions.service.ts +103 -0
  43. package/template/src/modules/forms/application/services/handlers/authenticate.action.ts +37 -0
  44. package/template/src/modules/forms/application/services/handlers/logging-email.handler.ts +22 -0
  45. package/template/src/modules/forms/application/services/handlers/send-confirmation-email.action.ts +40 -0
  46. package/template/src/modules/forms/application/services/handlers/webhook-delivery.transport.ts +319 -0
  47. package/template/src/modules/forms/application/services/handlers/webhook.handler.ts +89 -0
  48. package/template/src/modules/forms/application/services/outbox-dispatcher.service.ts +131 -0
  49. package/template/src/modules/forms/dto/create-form-definition.dto.ts +12 -0
  50. package/template/src/modules/forms/dto/data-source-response.dto.ts +19 -0
  51. package/template/src/modules/forms/dto/export-submissions-query.dto.ts +33 -0
  52. package/template/src/modules/forms/dto/file-upload-response.dto.ts +24 -0
  53. package/template/src/modules/forms/dto/form-definition-response.dto.ts +50 -0
  54. package/template/src/modules/forms/dto/form-render-response.dto.ts +17 -0
  55. package/template/src/modules/forms/dto/list-form-definitions-query.dto.ts +10 -0
  56. package/template/src/modules/forms/dto/list-submissions-query.dto.ts +10 -0
  57. package/template/src/modules/forms/dto/public-submit-form.dto.ts +24 -0
  58. package/template/src/modules/forms/dto/set-public-access.dto.ts +8 -0
  59. package/template/src/modules/forms/dto/submission-response.dto.ts +99 -0
  60. package/template/src/modules/forms/dto/submit-form.dto.ts +50 -0
  61. package/template/src/modules/forms/dto/update-form-definition.dto.ts +12 -0
  62. package/template/src/modules/forms/dto/upload-file-query.dto.ts +33 -0
  63. package/template/src/modules/forms/dto/validate-submission.dto.ts +22 -0
  64. package/template/src/modules/forms/examples/abstract-submission.form.json +80 -0
  65. package/template/src/modules/forms/examples/login.form.json +24 -0
  66. package/template/src/modules/forms/examples/registration.form.json +44 -0
  67. package/template/src/modules/forms/forms.module.ts +228 -0
  68. package/template/src/modules/forms/forms.tokens.ts +6 -0
  69. package/template/src/modules/forms/infrastructure/audit-sink.adapter.ts +30 -0
  70. package/template/src/modules/forms/infrastructure/casl-forms-authorization.ts +31 -0
  71. package/template/src/modules/forms/infrastructure/prisma-tx-runner.ts +17 -0
  72. package/template/src/modules/forms/infrastructure/registry/form-extension.decorators.ts +17 -0
  73. package/template/src/modules/forms/infrastructure/registry/registry-bootstrap.service.ts +82 -0
  74. package/template/src/modules/forms/infrastructure/request-forms-context.ts +60 -0
  75. package/template/src/modules/forms/infrastructure/schema-check/forms-schema-check.service.ts +76 -0
  76. package/template/src/modules/forms/infrastructure/storage/local-disk-storage.adapter.ts +43 -0
  77. package/template/src/modules/forms/infrastructure/stores/index.ts +5 -0
  78. package/template/src/modules/forms/infrastructure/stores/prisma-action-log.store.ts +37 -0
  79. package/template/src/modules/forms/infrastructure/stores/prisma-file.store.ts +108 -0
  80. package/template/src/modules/forms/infrastructure/stores/prisma-form-definition.store.ts +147 -0
  81. package/template/src/modules/forms/infrastructure/stores/prisma-outbox.store.ts +156 -0
  82. package/template/src/modules/forms/infrastructure/stores/prisma-submission.store.ts +164 -0
  83. package/template/src/modules/forms/presentation/forms-data-sources.controller.ts +58 -0
  84. package/template/src/modules/forms/presentation/forms-definitions.controller.ts +191 -0
  85. package/template/src/modules/forms/presentation/forms-files.controller.ts +79 -0
  86. package/template/src/modules/forms/presentation/forms-submissions.controller.ts +154 -0
  87. package/template/src/modules/forms/presentation/forms-upload.interceptor.ts +33 -0
  88. package/template/src/modules/forms/presentation/public-forms.controller.ts +51 -0
  89. package/template/src/modules/invitations/dto/invitation-response.dto.ts +4 -0
  90. package/template/src/modules/organisations/dto/organisation-response.dto.ts +1 -0
  91. package/template/src/modules/reports/application/services/reports-actions.service.ts +54 -0
  92. package/template/src/modules/reports/application/services/reports-definitions.service.ts +66 -0
  93. package/template/src/modules/reports/application/services/reports-error.mapper.ts +97 -0
  94. package/template/src/modules/reports/application/services/reports-export-dispatcher.service.ts +205 -0
  95. package/template/src/modules/reports/application/services/reports-exports.service.ts +78 -0
  96. package/template/src/modules/reports/application/services/reports-queries.service.ts +35 -0
  97. package/template/src/modules/reports/application/services/reports-settings-reader.service.ts +49 -0
  98. package/template/src/modules/reports/application/services/reports-views.service.ts +79 -0
  99. package/template/src/modules/reports/dto/action-result-response.dto.ts +21 -0
  100. package/template/src/modules/reports/dto/create-report-definition.dto.ts +86 -0
  101. package/template/src/modules/reports/dto/create-saved-view.dto.ts +26 -0
  102. package/template/src/modules/reports/dto/execute-action.dto.ts +71 -0
  103. package/template/src/modules/reports/dto/export-job-response.dto.ts +60 -0
  104. package/template/src/modules/reports/dto/export-request.dto.ts +34 -0
  105. package/template/src/modules/reports/dto/list-reports-query.dto.ts +10 -0
  106. package/template/src/modules/reports/dto/list-views-query.dto.ts +17 -0
  107. package/template/src/modules/reports/dto/prepare-action-response.dto.ts +14 -0
  108. package/template/src/modules/reports/dto/prepare-action.dto.ts +27 -0
  109. package/template/src/modules/reports/dto/query-response.dto.ts +64 -0
  110. package/template/src/modules/reports/dto/query-spec.dto.ts +120 -0
  111. package/template/src/modules/reports/dto/report-definition-response.dto.ts +64 -0
  112. package/template/src/modules/reports/dto/report-meta-query.dto.ts +16 -0
  113. package/template/src/modules/reports/dto/report-meta-response.dto.ts +113 -0
  114. package/template/src/modules/reports/dto/saved-view-response.dto.ts +66 -0
  115. package/template/src/modules/reports/dto/update-report-definition.dto.ts +9 -0
  116. package/template/src/modules/reports/dto/update-saved-view.dto.ts +27 -0
  117. package/template/src/modules/reports/examples/abstract-review-board.report.json +54 -0
  118. package/template/src/modules/reports/examples/org-members.report.json +55 -0
  119. package/template/src/modules/reports/infrastructure/audit-sink.adapter.ts +31 -0
  120. package/template/src/modules/reports/infrastructure/casl-reports-authorization.ts +39 -0
  121. package/template/src/modules/reports/infrastructure/forms-adapter/form-report-source.adapter.ts +292 -0
  122. package/template/src/modules/reports/infrastructure/forms-adapter/form-row-actions.ts +171 -0
  123. package/template/src/modules/reports/infrastructure/forms-adapter/forms-bridge-bootstrap.service.ts +32 -0
  124. package/template/src/modules/reports/infrastructure/prisma-catalog.adapter.ts +95 -0
  125. package/template/src/modules/reports/infrastructure/prisma-query-executor.ts +103 -0
  126. package/template/src/modules/reports/infrastructure/prisma-snapshot-runner.ts +47 -0
  127. package/template/src/modules/reports/infrastructure/prisma-tx-runner.ts +18 -0
  128. package/template/src/modules/reports/infrastructure/registry/registry-bootstrap.service.ts +61 -0
  129. package/template/src/modules/reports/infrastructure/registry/report-extension.decorators.ts +14 -0
  130. package/template/src/modules/reports/infrastructure/reports-job-queue.adapter.ts +28 -0
  131. package/template/src/modules/reports/infrastructure/request-reports-context.ts +42 -0
  132. package/template/src/modules/reports/infrastructure/schema-check/reports-schema-check.service.ts +116 -0
  133. package/template/src/modules/reports/infrastructure/storage/local-disk-export-storage.adapter.ts +92 -0
  134. package/template/src/modules/reports/infrastructure/stores/index.ts +5 -0
  135. package/template/src/modules/reports/infrastructure/stores/prisma-bulk-action-run.store.ts +89 -0
  136. package/template/src/modules/reports/infrastructure/stores/prisma-export-job.store.ts +93 -0
  137. package/template/src/modules/reports/infrastructure/stores/prisma-report-definition.store.ts +171 -0
  138. package/template/src/modules/reports/infrastructure/stores/prisma-row-tag.store.ts +110 -0
  139. package/template/src/modules/reports/infrastructure/stores/prisma-saved-view.store.ts +144 -0
  140. package/template/src/modules/reports/presentation/reports-actions.controller.ts +83 -0
  141. package/template/src/modules/reports/presentation/reports-definitions.controller.ts +156 -0
  142. package/template/src/modules/reports/presentation/reports-export-jobs.controller.ts +61 -0
  143. package/template/src/modules/reports/presentation/reports-export.controller.ts +76 -0
  144. package/template/src/modules/reports/presentation/reports-query.controller.ts +52 -0
  145. package/template/src/modules/reports/presentation/reports-views.controller.ts +140 -0
  146. package/template/src/modules/reports/reports-forms.module.ts +33 -0
  147. package/template/src/modules/reports/reports.module.ts +335 -0
  148. package/template/src/modules/reports/reports.tokens.ts +11 -0
  149. package/template/src/modules/reports/sources/org-members.source.ts +112 -0
  150. package/template/src/modules/settings/types/setting-definitions.ts +94 -0
  151. package/template/test/forms-captcha.e2e-spec.ts +163 -0
  152. package/template/test/forms-definitions.e2e-spec.ts +394 -0
  153. package/template/test/forms-export.e2e-spec.ts +390 -0
  154. package/template/test/forms-files.e2e-spec.ts +345 -0
  155. package/template/test/forms-outbox.e2e-spec.ts +570 -0
  156. package/template/test/forms-permission-sync.spec.ts +27 -0
  157. package/template/test/forms-public.e2e-spec.ts +293 -0
  158. package/template/test/forms-schema-check.e2e-spec.ts +65 -0
  159. package/template/test/forms-submissions.e2e-spec.ts +500 -0
  160. package/template/test/forms-throttling.e2e-spec.ts +146 -0
  161. package/template/test/forms-webhooks.e2e-spec.ts +403 -0
  162. package/template/test/jest-e2e.json +1 -0
  163. package/template/test/reports-advanced.e2e-spec.ts +381 -0
  164. package/template/test/reports-permission-sync.spec.ts +30 -0
  165. package/template/test/reports-query.e2e-spec.ts +402 -0
  166. package/template/test/reports-tiers.e2e-spec.ts +343 -0
  167. package/template/test/route-registry.validator.spec.ts +22 -0
@@ -0,0 +1,60 @@
1
+ import { ApiProperty } from '@nestjs/swagger';
2
+
3
+ export class ExportJobResponseDto {
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-review-board' })
17
+ reportKey!: string;
18
+
19
+ @ApiProperty({ example: 4 })
20
+ reportVersion!: number;
21
+
22
+ @ApiProperty({
23
+ type: 'object',
24
+ additionalProperties: true,
25
+ description: 'The FULL query spec, format, and resolved column list — replayable (finding #8).',
26
+ })
27
+ spec!: Record<string, unknown>;
28
+
29
+ @ApiProperty({
30
+ type: String,
31
+ format: 'date-time',
32
+ nullable: true,
33
+ description: 'Snapshot timestamp, set when the job runs (§9).',
34
+ })
35
+ asOf!: string | null;
36
+
37
+ @ApiProperty({ enum: ['PENDING', 'RUNNING', 'DONE', 'FAILED'], example: 'PENDING' })
38
+ status!: 'PENDING' | 'RUNNING' | 'DONE' | 'FAILED';
39
+
40
+ @ApiProperty({ type: String, format: 'uuid', nullable: true })
41
+ fileId!: string | null;
42
+
43
+ @ApiProperty({ type: Number, nullable: true, example: 52340 })
44
+ rowCount!: number | null;
45
+
46
+ @ApiProperty({ type: String, nullable: true })
47
+ error!: string | null;
48
+
49
+ @ApiProperty({
50
+ example: '4a4f0d8a-4bd2-469f-a6a9-3e1cb6a2b456',
51
+ format: 'uuid',
52
+ })
53
+ requestedBy!: string;
54
+
55
+ @ApiProperty({ example: '2026-06-01T10:30:00.000Z', format: 'date-time' })
56
+ createdAt!: string;
57
+
58
+ @ApiProperty({ example: '2026-06-01T10:30:00.000Z', format: 'date-time' })
59
+ updatedAt!: string;
60
+ }
@@ -0,0 +1,34 @@
1
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
+ import { Type } from 'class-transformer';
3
+ import {
4
+ IsArray,
5
+ IsIn,
6
+ IsObject,
7
+ IsOptional,
8
+ IsString,
9
+ ValidateNested,
10
+ } from 'class-validator';
11
+ import { QuerySpecDto } from './query-spec.dto';
12
+
13
+ export class ExportRequestDto {
14
+ @ApiProperty({ enum: ['csv', 'xlsx'], example: 'csv' })
15
+ @IsIn(['csv', 'xlsx'])
16
+ format!: 'csv' | 'xlsx';
17
+
18
+ @ApiPropertyOptional({ type: QuerySpecDto })
19
+ @IsOptional()
20
+ @IsObject()
21
+ @ValidateNested()
22
+ @Type(() => QuerySpecDto)
23
+ spec?: QuerySpecDto;
24
+
25
+ @ApiPropertyOptional({
26
+ type: [String],
27
+ description:
28
+ 'Explicit column-id projection. Columns marked exportable:false are stripped regardless (§9).',
29
+ })
30
+ @IsOptional()
31
+ @IsArray()
32
+ @IsString({ each: true })
33
+ columns?: string[];
34
+ }
@@ -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 ListReportsQueryDto extends PaginationQueryDto {
6
+ @ApiPropertyOptional({ enum: ['DRAFT', 'PUBLISHED', 'ARCHIVED'] })
7
+ @IsOptional()
8
+ @IsIn(['DRAFT', 'PUBLISHED', 'ARCHIVED'])
9
+ status?: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED';
10
+ }
@@ -0,0 +1,17 @@
1
+ import { ApiPropertyOptional } from '@nestjs/swagger';
2
+ import { Type } from 'class-transformer';
3
+ import { IsInt, IsOptional, Min } from 'class-validator';
4
+
5
+ export class ListViewsQueryDto {
6
+ @ApiPropertyOptional({
7
+ example: 3,
8
+ minimum: 1,
9
+ description:
10
+ 'Version the compatibility verdicts are computed against; defaults to the latest published one (§4).',
11
+ })
12
+ @IsOptional()
13
+ @Type(() => Number)
14
+ @IsInt()
15
+ @Min(1)
16
+ version?: number;
17
+ }
@@ -0,0 +1,14 @@
1
+ import { ApiProperty } from '@nestjs/swagger';
2
+
3
+ export class PrepareActionResponseDto {
4
+ @ApiProperty({ example: 240, description: 'Rows the byFilter selection resolves to right now.' })
5
+ expectedCount!: number;
6
+
7
+ @ApiProperty({ example: '2026-06-01T10:30:00.000Z', format: 'date-time' })
8
+ asOf!: string;
9
+
10
+ @ApiProperty({
11
+ description: 'HMAC-signed, short-TTL token binding report, version, action, and spec (§6.3).',
12
+ })
13
+ actionToken!: string;
14
+ }
@@ -0,0 +1,27 @@
1
+ import { ApiProperty } from '@nestjs/swagger';
2
+ import { Type } from 'class-transformer';
3
+ import { IsDefined, IsObject, ValidateNested } from 'class-validator';
4
+ import { QuerySpecDto } from './query-spec.dto';
5
+
6
+ /**
7
+ * §6.3 step 1: only byFilter selections prepare — the server resolves the
8
+ * spec NOW and returns a signed token binding what was seen. byIds selections
9
+ * skip prepare entirely (the ids ARE the snapshot) and go straight to execute.
10
+ */
11
+ export class PrepareActionSelectionDto {
12
+ @ApiProperty({ type: QuerySpecDto })
13
+ @IsDefined()
14
+ @IsObject()
15
+ @ValidateNested()
16
+ @Type(() => QuerySpecDto)
17
+ byFilter!: QuerySpecDto;
18
+ }
19
+
20
+ export class PrepareActionDto {
21
+ @ApiProperty({ type: PrepareActionSelectionDto })
22
+ @IsDefined()
23
+ @IsObject()
24
+ @ValidateNested()
25
+ @Type(() => PrepareActionSelectionDto)
26
+ selection!: PrepareActionSelectionDto;
27
+ }
@@ -0,0 +1,64 @@
1
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
+
3
+ export class CountResultDto {
4
+ @ApiProperty({
5
+ enum: ['estimated', 'exact', 'capped'],
6
+ description: '"capped" means the true count exceeds the cap — render "value+" (§5.4).',
7
+ })
8
+ mode!: 'estimated' | 'exact' | 'capped';
9
+
10
+ @ApiProperty({ example: 1240 })
11
+ value!: number;
12
+ }
13
+
14
+ export class PageInfoDto {
15
+ @ApiProperty({
16
+ type: String,
17
+ nullable: true,
18
+ description: 'Opaque, HMAC-signed keyset cursor (§5.3). null on the last page.',
19
+ })
20
+ nextCursor!: string | null;
21
+
22
+ @ApiProperty({ example: true })
23
+ hasMore!: boolean;
24
+
25
+ @ApiPropertyOptional({ type: CountResultDto })
26
+ count?: CountResultDto;
27
+ }
28
+
29
+ export class QueryMetaDto {
30
+ @ApiProperty({ example: 4 })
31
+ reportVersion!: number;
32
+
33
+ @ApiProperty({ type: 'array', items: { type: 'object', additionalProperties: true } })
34
+ appliedFilters!: Record<string, unknown>[];
35
+
36
+ @ApiProperty({ type: 'array', items: { type: 'object', additionalProperties: true } })
37
+ appliedSort!: Record<string, unknown>[];
38
+
39
+ @ApiProperty({
40
+ type: String,
41
+ nullable: true,
42
+ format: 'date-time',
43
+ description: 'Materialized-tier staleness marker; null on live/indexed tiers (§8).',
44
+ })
45
+ freshAsOf!: string | null;
46
+
47
+ @ApiProperty({ example: 14 })
48
+ durationMs!: number;
49
+ }
50
+
51
+ export class QueryResponseDto {
52
+ @ApiProperty({
53
+ type: 'array',
54
+ items: { type: 'object', additionalProperties: true },
55
+ description: 'Rows: declared column ids plus $id (stable row identity) and $tags (§7).',
56
+ })
57
+ rows!: Record<string, unknown>[];
58
+
59
+ @ApiProperty({ type: PageInfoDto })
60
+ pageInfo!: PageInfoDto;
61
+
62
+ @ApiProperty({ type: QueryMetaDto })
63
+ meta!: QueryMetaDto;
64
+ }
@@ -0,0 +1,120 @@
1
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
+ import { Type } from 'class-transformer';
3
+ import {
4
+ IsArray,
5
+ IsDefined,
6
+ IsIn,
7
+ IsInt,
8
+ IsOptional,
9
+ IsString,
10
+ Min,
11
+ ValidateNested,
12
+ } from 'class-validator';
13
+
14
+ /**
15
+ * Envelope-only validation of the QuerySpec (report design §4/§10): the DTO
16
+ * checks the shape; the engine's compiler validates the semantics — column
17
+ * ids against the definition's allowlist, operators against column types,
18
+ * cursors against (key, version, sortHash, filterHash). A `contains` on a
19
+ * number column is the engine's 400, not this DTO's.
20
+ */
21
+ export class FilterClauseDto {
22
+ @ApiProperty({
23
+ example: 'status',
24
+ description: 'A declared column id, or the virtual "$tags" column (§7).',
25
+ })
26
+ @IsString()
27
+ column!: string;
28
+
29
+ @ApiProperty({
30
+ example: 'eq',
31
+ description: 'Filter operator — validated against the column type by the engine.',
32
+ })
33
+ @IsString()
34
+ op!: string;
35
+
36
+ @ApiProperty({
37
+ description: 'Operand — any JSON value; the engine validates it against the column type.',
38
+ })
39
+ @IsDefined()
40
+ value!: unknown;
41
+ }
42
+
43
+ export class SortClauseDto {
44
+ @ApiProperty({ example: 'createdAt' })
45
+ @IsString()
46
+ column!: string;
47
+
48
+ @ApiProperty({ enum: ['asc', 'desc'], example: 'desc' })
49
+ @IsIn(['asc', 'desc'])
50
+ dir!: 'asc' | 'desc';
51
+ }
52
+
53
+ export class QuerySpecDto {
54
+ @ApiPropertyOptional({
55
+ type: Number,
56
+ nullable: true,
57
+ example: null,
58
+ description: 'null/omitted ⇒ latest published; or pin an explicit published version (§4).',
59
+ })
60
+ @IsOptional()
61
+ @IsInt()
62
+ @Min(1)
63
+ version?: number | null;
64
+
65
+ @ApiPropertyOptional({ type: [FilterClauseDto] })
66
+ @IsOptional()
67
+ @IsArray()
68
+ @ValidateNested({ each: true })
69
+ @Type(() => FilterClauseDto)
70
+ filters?: FilterClauseDto[];
71
+
72
+ @ApiPropertyOptional({
73
+ type: String,
74
+ nullable: true,
75
+ example: 'transformer',
76
+ description: "Free-text search across the definition's declared search columns (§5.5).",
77
+ })
78
+ @IsOptional()
79
+ @IsString()
80
+ search?: string | null;
81
+
82
+ @ApiPropertyOptional({ type: [SortClauseDto] })
83
+ @IsOptional()
84
+ @IsArray()
85
+ @ValidateNested({ each: true })
86
+ @Type(() => SortClauseDto)
87
+ sort?: SortClauseDto[];
88
+
89
+ @ApiPropertyOptional({
90
+ type: String,
91
+ nullable: true,
92
+ description: 'Keyset cursor from the previous page, or null for page one (§5.3).',
93
+ })
94
+ @IsOptional()
95
+ @IsString()
96
+ cursor?: string | null;
97
+
98
+ @ApiPropertyOptional({ example: 50, minimum: 1, description: 'Capped server-side by policy.' })
99
+ @IsOptional()
100
+ @IsInt()
101
+ @Min(1)
102
+ pageSize?: number;
103
+
104
+ @ApiPropertyOptional({
105
+ enum: ['none', 'estimated', 'exact-capped'],
106
+ description: 'Counts are a choice, not a tax (§5.4).',
107
+ })
108
+ @IsOptional()
109
+ @IsIn(['none', 'estimated', 'exact-capped'])
110
+ count?: 'none' | 'estimated' | 'exact-capped';
111
+
112
+ @ApiPropertyOptional({
113
+ type: [String],
114
+ description: 'Projection: declared column ids to return. Default: all definition columns.',
115
+ })
116
+ @IsOptional()
117
+ @IsArray()
118
+ @IsString({ each: true })
119
+ columns?: string[];
120
+ }
@@ -0,0 +1,64 @@
1
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
+
3
+ export class ReportDefinitionResponseDto {
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-review-board' })
17
+ key!: string;
18
+
19
+ @ApiProperty({ example: 1 })
20
+ version!: number;
21
+
22
+ @ApiProperty({ enum: ['DRAFT', 'PUBLISHED', 'ARCHIVED'], example: 'DRAFT' })
23
+ status!: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED';
24
+
25
+ @ApiProperty({
26
+ type: 'object',
27
+ additionalProperties: true,
28
+ description: 'The full ReportDefinition document.',
29
+ })
30
+ schema!: Record<string, unknown>;
31
+
32
+ @ApiPropertyOptional({
33
+ type: 'object',
34
+ additionalProperties: true,
35
+ nullable: true,
36
+ description:
37
+ 'Publish-time compiled metadata (logical→physical column map, plan hashes); null on drafts.',
38
+ })
39
+ compiled!: Record<string, unknown> | null;
40
+
41
+ @ApiProperty({ example: '2026-06-01T10:30:00.000Z', format: 'date-time' })
42
+ createdAt!: string;
43
+
44
+ @ApiProperty({ example: '2026-06-01T10:30:00.000Z', format: 'date-time' })
45
+ updatedAt!: string;
46
+ }
47
+
48
+ export class ReportDefinitionListResponseDto {
49
+ @ApiProperty({ type: [ReportDefinitionResponseDto] })
50
+ items!: ReportDefinitionResponseDto[];
51
+
52
+ @ApiPropertyOptional({
53
+ type: String,
54
+ example: '0a57fb4a-95c6-4f7e-bd5a-f96dbe0599e3',
55
+ format: 'uuid',
56
+ nullable: true,
57
+ })
58
+ nextCursor!: string | null;
59
+ }
60
+
61
+ export class ReportArchivedResponseDto {
62
+ @ApiProperty({ example: true })
63
+ archived!: boolean;
64
+ }
@@ -0,0 +1,16 @@
1
+ import { ApiPropertyOptional } from '@nestjs/swagger';
2
+ import { Type } from 'class-transformer';
3
+ import { IsInt, IsOptional, Min } from 'class-validator';
4
+
5
+ export class ReportMetaQueryDto {
6
+ @ApiPropertyOptional({
7
+ example: 3,
8
+ minimum: 1,
9
+ description: 'Specific published version to describe; defaults to the latest published one.',
10
+ })
11
+ @IsOptional()
12
+ @Type(() => Number)
13
+ @IsInt()
14
+ @Min(1)
15
+ version?: number;
16
+ }
@@ -0,0 +1,113 @@
1
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
+
3
+ /** Mirrors the engine's ReportMetaDescriptor (§4/§10) — the grid UI is generic. */
4
+ export class ReportColumnMetaDto {
5
+ @ApiProperty({ example: 'status' })
6
+ id!: string;
7
+
8
+ @ApiProperty({ example: 'Status' })
9
+ header!: string;
10
+
11
+ @ApiProperty({ example: 'enum' })
12
+ type!: string;
13
+
14
+ @ApiProperty({ example: true })
15
+ sortable!: boolean;
16
+
17
+ @ApiProperty({ example: true })
18
+ filterable!: boolean;
19
+
20
+ @ApiProperty({ example: false })
21
+ searchable!: boolean;
22
+
23
+ @ApiProperty({
24
+ type: [String],
25
+ example: ['eq', 'in'],
26
+ description: 'The exact operator set this column accepts.',
27
+ })
28
+ operators!: string[];
29
+
30
+ @ApiPropertyOptional({ type: [String] })
31
+ enum?: string[];
32
+
33
+ @ApiPropertyOptional({ type: 'object', additionalProperties: true })
34
+ lookup?: Record<string, unknown>;
35
+
36
+ @ApiProperty({ example: true })
37
+ exportable!: boolean;
38
+
39
+ @ApiPropertyOptional({
40
+ type: 'object',
41
+ additionalProperties: true,
42
+ description: 'Opaque UI hints — never interpreted by the backend.',
43
+ })
44
+ ui?: Record<string, unknown>;
45
+ }
46
+
47
+ export class ReportRowActionMetaDto {
48
+ @ApiProperty({ example: 'markReviewed' })
49
+ name!: string;
50
+
51
+ @ApiProperty({ enum: ['transactional', 'post-commit'], example: 'transactional' })
52
+ kind!: string;
53
+
54
+ @ApiPropertyOptional({
55
+ type: 'object',
56
+ additionalProperties: true,
57
+ description: 'JSON Schema for the action input, when the action declares one.',
58
+ })
59
+ inputSchema?: Record<string, unknown>;
60
+
61
+ @ApiProperty({ example: false })
62
+ destructive!: boolean;
63
+ }
64
+
65
+ export class ReportExportMetaDto {
66
+ @ApiProperty({ type: [String], example: ['csv', 'xlsx'] })
67
+ formats!: string[];
68
+
69
+ @ApiProperty({ example: 10000 })
70
+ maxRowsSync!: number;
71
+ }
72
+
73
+ export class ReportMetaResponseDto {
74
+ @ApiProperty({ example: 'abstract-review-board' })
75
+ reportKey!: string;
76
+
77
+ @ApiProperty({ example: 4 })
78
+ reportVersion!: number;
79
+
80
+ @ApiPropertyOptional({ example: 'Abstract review board' })
81
+ title?: string;
82
+
83
+ @ApiPropertyOptional({ example: 'All submitted abstracts with review status.' })
84
+ description?: string;
85
+
86
+ @ApiProperty({ type: [ReportColumnMetaDto] })
87
+ columns!: ReportColumnMetaDto[];
88
+
89
+ @ApiProperty({
90
+ type: 'array',
91
+ items: { type: 'object', additionalProperties: true },
92
+ example: [{ column: 'createdAt', dir: 'desc' }],
93
+ })
94
+ defaultSort!: Record<string, unknown>[];
95
+
96
+ @ApiProperty({ example: true })
97
+ searchEnabled!: boolean;
98
+
99
+ @ApiProperty({ type: [ReportRowActionMetaDto] })
100
+ rowActions!: ReportRowActionMetaDto[];
101
+
102
+ @ApiProperty({ type: ReportExportMetaDto })
103
+ export!: ReportExportMetaDto;
104
+
105
+ @ApiProperty({ example: 'indexed' })
106
+ performanceTier!: string;
107
+
108
+ @ApiPropertyOptional({
109
+ example: 900,
110
+ description: 'Declared staleness bound for materialized-tier reports, in seconds (§8).',
111
+ })
112
+ stalenessSeconds?: number;
113
+ }
@@ -0,0 +1,66 @@
1
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
+
3
+ export class ViewCompatibilityDto {
4
+ @ApiProperty({
5
+ enum: ['ok', 'degraded', 'incompatible'],
6
+ description: 'Verdict against the requested report version (§4, finding #1).',
7
+ })
8
+ status!: 'ok' | 'degraded' | 'incompatible';
9
+
10
+ @ApiProperty({ type: [String] })
11
+ missingColumns!: string[];
12
+
13
+ @ApiProperty({ type: [String] })
14
+ retypedColumns!: string[];
15
+ }
16
+
17
+ export class SavedViewResponseDto {
18
+ @ApiProperty({
19
+ example: '0a57fb4a-95c6-4f7e-bd5a-f96dbe0599e3',
20
+ format: 'uuid',
21
+ })
22
+ id!: string;
23
+
24
+ @ApiProperty({
25
+ example: '2c67399d-670c-4025-a5fd-1ea9a211891e',
26
+ format: 'uuid',
27
+ })
28
+ orgId!: string;
29
+
30
+ @ApiProperty({ example: 'abstract-review-board' })
31
+ reportKey!: string;
32
+
33
+ @ApiProperty({
34
+ example: 4,
35
+ description: 'The version the view was authored against (finding #1).',
36
+ })
37
+ reportVersion!: number;
38
+
39
+ @ApiProperty({ example: 'My ML shortlist' })
40
+ name!: string;
41
+
42
+ @ApiProperty({ type: 'object', additionalProperties: true })
43
+ spec!: Record<string, unknown>;
44
+
45
+ @ApiProperty({
46
+ type: String,
47
+ format: 'uuid',
48
+ nullable: true,
49
+ description: 'null ⇒ shared with the org (creation gated by reports.update, §10).',
50
+ })
51
+ ownerId!: string | null;
52
+
53
+ @ApiProperty({ example: '2026-06-01T10:30:00.000Z', format: 'date-time' })
54
+ createdAt!: string;
55
+
56
+ @ApiPropertyOptional({
57
+ type: ViewCompatibilityDto,
58
+ description: 'Present on list responses only.',
59
+ })
60
+ compatibility?: ViewCompatibilityDto;
61
+ }
62
+
63
+ export class SavedViewListResponseDto {
64
+ @ApiProperty({ type: [SavedViewResponseDto] })
65
+ items!: SavedViewResponseDto[];
66
+ }
@@ -0,0 +1,9 @@
1
+ import { CreateReportDefinitionDto } from './create-report-definition.dto';
2
+
3
+ /**
4
+ * Same envelope as create — the body IS the full ReportDefinition document.
5
+ * The engine enforces the rest: the document key must match the route's
6
+ * report, drafts mutate in place, and editing a published version opens a new
7
+ * draft (report design §2/§10).
8
+ */
9
+ export class UpdateReportDefinitionDto extends CreateReportDefinitionDto {}
@@ -0,0 +1,27 @@
1
+ import { ApiPropertyOptional } from '@nestjs/swagger';
2
+ import { Type } from 'class-transformer';
3
+ import {
4
+ IsObject,
5
+ IsOptional,
6
+ IsString,
7
+ MaxLength,
8
+ MinLength,
9
+ ValidateNested,
10
+ } from 'class-validator';
11
+ import { QuerySpecDto } from './query-spec.dto';
12
+
13
+ export class UpdateSavedViewDto {
14
+ @ApiPropertyOptional({ example: 'My ML shortlist', minLength: 1, maxLength: 120 })
15
+ @IsOptional()
16
+ @IsString()
17
+ @MinLength(1)
18
+ @MaxLength(120)
19
+ name?: string;
20
+
21
+ @ApiPropertyOptional({ type: QuerySpecDto })
22
+ @IsOptional()
23
+ @IsObject()
24
+ @ValidateNested()
25
+ @Type(() => QuerySpecDto)
26
+ spec?: QuerySpecDto;
27
+ }