@ftisindia/create-app 0.1.5 → 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 +25 -0
- package/template/README.md +51 -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/env.validation.ts +25 -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 +3 -12
- package/template/src/modules/access-control/dto/access-control-response.dto.ts +3 -0
- package/template/src/modules/access-control/dto/current-access-control-response.dto.ts +5 -1
- package/template/src/modules/access-control/types/permission-key.ts +27 -0
- package/template/src/modules/access-control/types/route-permission-registry.ts +183 -0
- package/template/src/modules/audit/dto/audit-response.dto.ts +7 -3
- package/template/src/modules/auth/auth.module.ts +3 -1
- package/template/src/modules/auth/dto/auth-response.dto.ts +1 -1
- package/template/src/modules/forms/application/services/file-gc.service.ts +85 -0
- package/template/src/modules/forms/application/services/forms-definitions.service.ts +137 -0
- package/template/src/modules/forms/application/services/forms-error.mapper.ts +64 -0
- package/template/src/modules/forms/application/services/forms-export.service.ts +210 -0
- package/template/src/modules/forms/application/services/forms-files.service.ts +164 -0
- package/template/src/modules/forms/application/services/forms-public.service.ts +49 -0
- package/template/src/modules/forms/application/services/forms-settings-reader.service.ts +53 -0
- package/template/src/modules/forms/application/services/forms-submissions.service.ts +103 -0
- package/template/src/modules/forms/application/services/handlers/authenticate.action.ts +37 -0
- package/template/src/modules/forms/application/services/handlers/logging-email.handler.ts +22 -0
- package/template/src/modules/forms/application/services/handlers/send-confirmation-email.action.ts +40 -0
- package/template/src/modules/forms/application/services/handlers/webhook.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/dto/organisation-response.dto.ts +1 -0
- package/template/src/modules/reports/application/services/reports-actions.service.ts +54 -0
- package/template/src/modules/reports/application/services/reports-definitions.service.ts +66 -0
- package/template/src/modules/reports/application/services/reports-error.mapper.ts +97 -0
- package/template/src/modules/reports/application/services/reports-export-dispatcher.service.ts +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/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 +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
|
+
}
|