@ftisindia/create-app 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/.env.example +28 -0
- package/template/README.md +51 -0
- package/template/_gitignore +6 -0
- package/template/_package.json +10 -1
- package/template/docs/FORMS.md +188 -0
- package/template/docs/FORMS_CHECKLIST.md +69 -0
- package/template/docs/REPORTS.md +255 -0
- package/template/docs/REPORTS_CHECKLIST.md +152 -0
- package/template/prisma/migrations/20260612000000_add_form_builder/migration.sql +147 -0
- package/template/prisma/migrations/20260613000000_add_report_builder/migration.sql +129 -0
- package/template/prisma/migrations/20260616000000_add_form_outbox_claimed_by/migration.sql +5 -0
- package/template/prisma/schema.prisma +289 -0
- package/template/scripts/export-openapi.ts +85 -0
- package/template/scripts/gen-form.mjs +149 -0
- package/template/scripts/push-form.ts +124 -0
- package/template/src/app.module.ts +30 -8
- package/template/src/common/dto/membership-response.dto.ts +1 -0
- package/template/src/common/dto/role-summary.dto.ts +3 -3
- package/template/src/common/dto/user-summary.dto.ts +3 -3
- package/template/src/config/env.validation.ts +28 -0
- package/template/src/config/forms.config.ts +13 -0
- package/template/src/config/index.ts +2 -0
- package/template/src/config/openapi.ts +12 -0
- package/template/src/config/reports-secret.ts +15 -0
- package/template/src/config/reports.config.ts +18 -0
- package/template/src/main.ts +3 -12
- package/template/src/modules/access-control/dto/access-control-response.dto.ts +3 -0
- package/template/src/modules/access-control/dto/current-access-control-response.dto.ts +5 -1
- package/template/src/modules/access-control/types/permission-key.ts +27 -0
- package/template/src/modules/access-control/types/route-permission-registry.ts +183 -0
- package/template/src/modules/audit/dto/audit-response.dto.ts +7 -3
- package/template/src/modules/auth/auth.module.ts +3 -1
- package/template/src/modules/auth/dto/auth-response.dto.ts +1 -1
- package/template/src/modules/forms/application/services/file-gc.service.ts +85 -0
- package/template/src/modules/forms/application/services/forms-definitions.service.ts +137 -0
- package/template/src/modules/forms/application/services/forms-error.mapper.ts +64 -0
- package/template/src/modules/forms/application/services/forms-export.service.ts +210 -0
- package/template/src/modules/forms/application/services/forms-files.service.ts +164 -0
- package/template/src/modules/forms/application/services/forms-public.service.ts +49 -0
- package/template/src/modules/forms/application/services/forms-settings-reader.service.ts +53 -0
- package/template/src/modules/forms/application/services/forms-submissions.service.ts +103 -0
- package/template/src/modules/forms/application/services/handlers/authenticate.action.ts +37 -0
- package/template/src/modules/forms/application/services/handlers/logging-email.handler.ts +22 -0
- package/template/src/modules/forms/application/services/handlers/send-confirmation-email.action.ts +40 -0
- package/template/src/modules/forms/application/services/handlers/webhook-delivery.transport.ts +319 -0
- package/template/src/modules/forms/application/services/handlers/webhook.handler.ts +89 -0
- package/template/src/modules/forms/application/services/outbox-dispatcher.service.ts +131 -0
- package/template/src/modules/forms/dto/create-form-definition.dto.ts +12 -0
- package/template/src/modules/forms/dto/data-source-response.dto.ts +19 -0
- package/template/src/modules/forms/dto/export-submissions-query.dto.ts +33 -0
- package/template/src/modules/forms/dto/file-upload-response.dto.ts +24 -0
- package/template/src/modules/forms/dto/form-definition-response.dto.ts +50 -0
- package/template/src/modules/forms/dto/form-render-response.dto.ts +17 -0
- package/template/src/modules/forms/dto/list-form-definitions-query.dto.ts +10 -0
- package/template/src/modules/forms/dto/list-submissions-query.dto.ts +10 -0
- package/template/src/modules/forms/dto/public-submit-form.dto.ts +24 -0
- package/template/src/modules/forms/dto/set-public-access.dto.ts +8 -0
- package/template/src/modules/forms/dto/submission-response.dto.ts +99 -0
- package/template/src/modules/forms/dto/submit-form.dto.ts +50 -0
- package/template/src/modules/forms/dto/update-form-definition.dto.ts +12 -0
- package/template/src/modules/forms/dto/upload-file-query.dto.ts +33 -0
- package/template/src/modules/forms/dto/validate-submission.dto.ts +22 -0
- package/template/src/modules/forms/examples/abstract-submission.form.json +80 -0
- package/template/src/modules/forms/examples/login.form.json +24 -0
- package/template/src/modules/forms/examples/registration.form.json +44 -0
- package/template/src/modules/forms/forms.module.ts +228 -0
- package/template/src/modules/forms/forms.tokens.ts +6 -0
- package/template/src/modules/forms/infrastructure/audit-sink.adapter.ts +30 -0
- package/template/src/modules/forms/infrastructure/casl-forms-authorization.ts +31 -0
- package/template/src/modules/forms/infrastructure/prisma-tx-runner.ts +17 -0
- package/template/src/modules/forms/infrastructure/registry/form-extension.decorators.ts +17 -0
- package/template/src/modules/forms/infrastructure/registry/registry-bootstrap.service.ts +82 -0
- package/template/src/modules/forms/infrastructure/request-forms-context.ts +60 -0
- package/template/src/modules/forms/infrastructure/schema-check/forms-schema-check.service.ts +76 -0
- package/template/src/modules/forms/infrastructure/storage/local-disk-storage.adapter.ts +43 -0
- package/template/src/modules/forms/infrastructure/stores/index.ts +5 -0
- package/template/src/modules/forms/infrastructure/stores/prisma-action-log.store.ts +37 -0
- package/template/src/modules/forms/infrastructure/stores/prisma-file.store.ts +108 -0
- package/template/src/modules/forms/infrastructure/stores/prisma-form-definition.store.ts +147 -0
- package/template/src/modules/forms/infrastructure/stores/prisma-outbox.store.ts +156 -0
- package/template/src/modules/forms/infrastructure/stores/prisma-submission.store.ts +164 -0
- package/template/src/modules/forms/presentation/forms-data-sources.controller.ts +58 -0
- package/template/src/modules/forms/presentation/forms-definitions.controller.ts +191 -0
- package/template/src/modules/forms/presentation/forms-files.controller.ts +79 -0
- package/template/src/modules/forms/presentation/forms-submissions.controller.ts +154 -0
- package/template/src/modules/forms/presentation/forms-upload.interceptor.ts +33 -0
- package/template/src/modules/forms/presentation/public-forms.controller.ts +51 -0
- package/template/src/modules/invitations/dto/invitation-response.dto.ts +4 -0
- package/template/src/modules/organisations/dto/organisation-response.dto.ts +1 -0
- package/template/src/modules/reports/application/services/reports-actions.service.ts +54 -0
- package/template/src/modules/reports/application/services/reports-definitions.service.ts +66 -0
- package/template/src/modules/reports/application/services/reports-error.mapper.ts +97 -0
- package/template/src/modules/reports/application/services/reports-export-dispatcher.service.ts +205 -0
- package/template/src/modules/reports/application/services/reports-exports.service.ts +78 -0
- package/template/src/modules/reports/application/services/reports-queries.service.ts +35 -0
- package/template/src/modules/reports/application/services/reports-settings-reader.service.ts +49 -0
- package/template/src/modules/reports/application/services/reports-views.service.ts +79 -0
- package/template/src/modules/reports/dto/action-result-response.dto.ts +21 -0
- package/template/src/modules/reports/dto/create-report-definition.dto.ts +86 -0
- package/template/src/modules/reports/dto/create-saved-view.dto.ts +26 -0
- package/template/src/modules/reports/dto/execute-action.dto.ts +71 -0
- package/template/src/modules/reports/dto/export-job-response.dto.ts +60 -0
- package/template/src/modules/reports/dto/export-request.dto.ts +34 -0
- package/template/src/modules/reports/dto/list-reports-query.dto.ts +10 -0
- package/template/src/modules/reports/dto/list-views-query.dto.ts +17 -0
- package/template/src/modules/reports/dto/prepare-action-response.dto.ts +14 -0
- package/template/src/modules/reports/dto/prepare-action.dto.ts +27 -0
- package/template/src/modules/reports/dto/query-response.dto.ts +64 -0
- package/template/src/modules/reports/dto/query-spec.dto.ts +120 -0
- package/template/src/modules/reports/dto/report-definition-response.dto.ts +64 -0
- package/template/src/modules/reports/dto/report-meta-query.dto.ts +16 -0
- package/template/src/modules/reports/dto/report-meta-response.dto.ts +113 -0
- package/template/src/modules/reports/dto/saved-view-response.dto.ts +66 -0
- package/template/src/modules/reports/dto/update-report-definition.dto.ts +9 -0
- package/template/src/modules/reports/dto/update-saved-view.dto.ts +27 -0
- package/template/src/modules/reports/examples/abstract-review-board.report.json +54 -0
- package/template/src/modules/reports/examples/org-members.report.json +55 -0
- package/template/src/modules/reports/infrastructure/audit-sink.adapter.ts +31 -0
- package/template/src/modules/reports/infrastructure/casl-reports-authorization.ts +39 -0
- package/template/src/modules/reports/infrastructure/forms-adapter/form-report-source.adapter.ts +292 -0
- package/template/src/modules/reports/infrastructure/forms-adapter/form-row-actions.ts +171 -0
- package/template/src/modules/reports/infrastructure/forms-adapter/forms-bridge-bootstrap.service.ts +32 -0
- package/template/src/modules/reports/infrastructure/prisma-catalog.adapter.ts +95 -0
- package/template/src/modules/reports/infrastructure/prisma-query-executor.ts +103 -0
- package/template/src/modules/reports/infrastructure/prisma-snapshot-runner.ts +47 -0
- package/template/src/modules/reports/infrastructure/prisma-tx-runner.ts +18 -0
- package/template/src/modules/reports/infrastructure/registry/registry-bootstrap.service.ts +61 -0
- package/template/src/modules/reports/infrastructure/registry/report-extension.decorators.ts +14 -0
- package/template/src/modules/reports/infrastructure/reports-job-queue.adapter.ts +28 -0
- package/template/src/modules/reports/infrastructure/request-reports-context.ts +42 -0
- package/template/src/modules/reports/infrastructure/schema-check/reports-schema-check.service.ts +116 -0
- package/template/src/modules/reports/infrastructure/storage/local-disk-export-storage.adapter.ts +92 -0
- package/template/src/modules/reports/infrastructure/stores/index.ts +5 -0
- package/template/src/modules/reports/infrastructure/stores/prisma-bulk-action-run.store.ts +89 -0
- package/template/src/modules/reports/infrastructure/stores/prisma-export-job.store.ts +93 -0
- package/template/src/modules/reports/infrastructure/stores/prisma-report-definition.store.ts +171 -0
- package/template/src/modules/reports/infrastructure/stores/prisma-row-tag.store.ts +110 -0
- package/template/src/modules/reports/infrastructure/stores/prisma-saved-view.store.ts +144 -0
- package/template/src/modules/reports/presentation/reports-actions.controller.ts +83 -0
- package/template/src/modules/reports/presentation/reports-definitions.controller.ts +156 -0
- package/template/src/modules/reports/presentation/reports-export-jobs.controller.ts +61 -0
- package/template/src/modules/reports/presentation/reports-export.controller.ts +76 -0
- package/template/src/modules/reports/presentation/reports-query.controller.ts +52 -0
- package/template/src/modules/reports/presentation/reports-views.controller.ts +140 -0
- package/template/src/modules/reports/reports-forms.module.ts +33 -0
- package/template/src/modules/reports/reports.module.ts +335 -0
- package/template/src/modules/reports/reports.tokens.ts +11 -0
- package/template/src/modules/reports/sources/org-members.source.ts +112 -0
- package/template/src/modules/settings/types/setting-definitions.ts +94 -0
- package/template/test/forms-captcha.e2e-spec.ts +163 -0
- package/template/test/forms-definitions.e2e-spec.ts +394 -0
- package/template/test/forms-export.e2e-spec.ts +390 -0
- package/template/test/forms-files.e2e-spec.ts +345 -0
- package/template/test/forms-outbox.e2e-spec.ts +570 -0
- package/template/test/forms-permission-sync.spec.ts +27 -0
- package/template/test/forms-public.e2e-spec.ts +293 -0
- package/template/test/forms-schema-check.e2e-spec.ts +65 -0
- package/template/test/forms-submissions.e2e-spec.ts +500 -0
- package/template/test/forms-throttling.e2e-spec.ts +146 -0
- package/template/test/forms-webhooks.e2e-spec.ts +403 -0
- package/template/test/jest-e2e.json +1 -0
- package/template/test/reports-advanced.e2e-spec.ts +381 -0
- package/template/test/reports-permission-sync.spec.ts +30 -0
- package/template/test/reports-query.e2e-spec.ts +402 -0
- package/template/test/reports-tiers.e2e-spec.ts +343 -0
- package/template/test/route-registry.validator.spec.ts +22 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# Reports Module (`@ftisindia/report-builder`)
|
|
2
|
+
|
|
3
|
+
This app ships with a definition-driven report engine. A report is **a
|
|
4
|
+
declared, versioned view over one data source, plus the verbs users may apply
|
|
5
|
+
to it** — filter, sort, search, paginate, export, and act on rows. Report
|
|
6
|
+
definitions are versioned JSON documents in the database; the behaviors they
|
|
7
|
+
reference — sources, row actions — are code this app registers. New reports
|
|
8
|
+
over existing sources are **data**: most ship with zero backend code.
|
|
9
|
+
|
|
10
|
+
- Engine architecture: `report-builder/report-builder-design.md` in the
|
|
11
|
+
starter repository (Rev 2 — the binding spec).
|
|
12
|
+
- Completion standard: [`REPORTS_CHECKLIST.md`](./REPORTS_CHECKLIST.md) on top
|
|
13
|
+
of [`MODULE_COMPLETION_CHECKLIST.md`](./MODULE_COMPLETION_CHECKLIST.md).
|
|
14
|
+
- The engine is **fully usable without the form builder** — the form adapter
|
|
15
|
+
is one optional source provider among many (the standalone guarantee).
|
|
16
|
+
|
|
17
|
+
**Module structure (the standalone guarantee, made structural):**
|
|
18
|
+
`ReportsModule` is self-contained — definitions, queries, views, row actions
|
|
19
|
+
over custom sources, tags, and CSV/XLSX exports (sync + a reports-owned async
|
|
20
|
+
worker and file storage). It imports **no** FormsModule. Form-backed reports
|
|
21
|
+
(`source.kind: "form"`) and the delegated `editSubmission`/`updateStatus` verbs
|
|
22
|
+
live in the **optional** `ReportsFormsModule`, the single artifact aware of
|
|
23
|
+
both engines. Remove its import from `app.module.ts` and reports keeps working
|
|
24
|
+
over custom sources alone.
|
|
25
|
+
|
|
26
|
+
## The 60-second tour
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
GET /organisations/:orgId/reports reports.read list definitions
|
|
30
|
+
POST /organisations/:orgId/reports reports.create create draft
|
|
31
|
+
GET /organisations/:orgId/reports/:key reports.read latest definition
|
|
32
|
+
PATCH /organisations/:orgId/reports/:key reports.update edit draft (published ⇒ opens v+1)
|
|
33
|
+
POST /organisations/:orgId/reports/:key/publish reports.publish lint + freeze a new version
|
|
34
|
+
POST /organisations/:orgId/reports/:key/archive reports.archive archive the definition
|
|
35
|
+
GET /organisations/:orgId/reports/:key/meta?version= reports.read column/operator/action metadata
|
|
36
|
+
POST /organisations/:orgId/reports/:key/query reports.read THE grid endpoint (QuerySpec)
|
|
37
|
+
GET /organisations/:orgId/reports/:key/views reports.read saved views + compatibility
|
|
38
|
+
POST /organisations/:orgId/reports/:key/views reports.read create PERSONAL view
|
|
39
|
+
POST /organisations/:orgId/reports/:key/views/shared reports.update create SHARED view
|
|
40
|
+
POST /organisations/:orgId/reports/:key/actions/:n/prepare reports.read* byFilter token (drift-safe bulk)
|
|
41
|
+
POST /organisations/:orgId/reports/:key/actions/:n reports.read* execute row/bulk action
|
|
42
|
+
POST /organisations/:orgId/reports/:key/export reports.export sync stream or 202 job
|
|
43
|
+
GET /organisations/:orgId/reports/exports/:jobId reports.export async export status
|
|
44
|
+
GET /organisations/:orgId/reports/exports/:jobId/download reports.export download a finished export
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`*` Row actions are additionally gated by their own declared permission keys
|
|
48
|
+
(e.g. `formSubmissions.update`), checked when the action is wired into a
|
|
49
|
+
definition AND again at execute time.
|
|
50
|
+
|
|
51
|
+
Example definitions live in `src/modules/reports/examples/` — `org-members`
|
|
52
|
+
(custom source, no form builder anywhere) and `abstract-review-board` (the
|
|
53
|
+
form-backed quick path, indexed tier).
|
|
54
|
+
|
|
55
|
+
## One contract: the QuerySpec
|
|
56
|
+
|
|
57
|
+
The frontend never builds queries. Every grid interaction posts a declarative
|
|
58
|
+
spec naming declared column ids and verbs; the backend validates, compiles,
|
|
59
|
+
and applies them:
|
|
60
|
+
|
|
61
|
+
```jsonc
|
|
62
|
+
POST /organisations/:orgId/reports/abstract-review-board/query
|
|
63
|
+
{
|
|
64
|
+
"filters": [{ "column": "track", "op": "eq", "value": "trk_ml" }],
|
|
65
|
+
"search": "transformer",
|
|
66
|
+
"sort": [{ "column": "createdAt", "dir": "desc" }],
|
|
67
|
+
"cursor": null, // keyset cursor from the previous page
|
|
68
|
+
"pageSize": 50,
|
|
69
|
+
"count": "none", // "none" | "estimated" | "exact-capped"
|
|
70
|
+
"columns": ["title", "track", "status", "createdAt"]
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Operators are typed per column (`contains` on a number column is a 400, not a
|
|
75
|
+
seq scan). `GET /reports/:key/meta` describes every column, its operators, the
|
|
76
|
+
wired actions, and export formats — a generic grid UI configures itself from
|
|
77
|
+
it.
|
|
78
|
+
|
|
79
|
+
## How a query executes (the performance contract)
|
|
80
|
+
|
|
81
|
+
Slow reports come from four sins; the engine makes them structurally hard:
|
|
82
|
+
|
|
83
|
+
- **No OFFSET, ever.** Pagination is keyset-only; cursors are opaque,
|
|
84
|
+
HMAC-signed, and bound to (report, version, sort, filters) — replay against
|
|
85
|
+
anything else is a 400 telling the client to restart.
|
|
86
|
+
- **Unindexed shapes fail publish.** On the `indexed` tier, every
|
|
87
|
+
sortable/filterable/searchable column must name its backing index; publish
|
|
88
|
+
verifies the index against the catalog (expression, opclass, collation,
|
|
89
|
+
org-leading) AND runs `EXPLAIN` on representative shapes. The failure is
|
|
90
|
+
constructive: the 422 carries the exact migration SQL to apply.
|
|
91
|
+
- **Counts are a choice.** Default `none` (hasMore comes free), `estimated`
|
|
92
|
+
(planner estimate, milliseconds), or `exact-capped` (`10000+` beyond cap).
|
|
93
|
+
- **A statement budget backstops everything** — every report query carries
|
|
94
|
+
`statement_timeout` (`reports.statementTimeoutMs`, default 5s).
|
|
95
|
+
|
|
96
|
+
The compiler builds every statement from code-owned manifests; user input
|
|
97
|
+
never becomes an identifier and values travel only as bind parameters.
|
|
98
|
+
**SQL expressions can exist only in code** — a definition referencing
|
|
99
|
+
`"path": "title' || (SELECT ...)"` is rejected at save.
|
|
100
|
+
|
|
101
|
+
## Custom sources (no form builder required)
|
|
102
|
+
|
|
103
|
+
Register a source in code — one class, one decorator:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { ReportSource } from './modules/reports/infrastructure/registry/report-extension.decorators';
|
|
107
|
+
import type { ReportSourceDef, SourceManifest, SourceQuery } from '@ftisindia/report-builder';
|
|
108
|
+
|
|
109
|
+
@Injectable()
|
|
110
|
+
@ReportSource()
|
|
111
|
+
export class OrgMembersReportSource implements ReportSourceDef {
|
|
112
|
+
key = 'org-members';
|
|
113
|
+
manifest(): SourceManifest { /* columns + types + capabilities + indexes */ }
|
|
114
|
+
baseQuery(): SourceQuery {
|
|
115
|
+
return {
|
|
116
|
+
from: '"Membership" m JOIN "User" u ON u."id" = m."userId"',
|
|
117
|
+
orgColumn: 'm."orgId"',
|
|
118
|
+
primaryTable: 'Membership',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The source declares structure; the compiler owns query construction — a
|
|
125
|
+
source never sees the QuerySpec, so it cannot be injected and cannot bypass
|
|
126
|
+
the allowlist. The engine injects the org predicate first on every statement,
|
|
127
|
+
plus the source's optional `rowScope` (row-level security in code).
|
|
128
|
+
|
|
129
|
+
## Reports from forms (the quick path)
|
|
130
|
+
|
|
131
|
+
Requires the optional `ReportsFormsModule` bridge (imported in `app.module.ts`
|
|
132
|
+
by default in this template). Point a definition at a form and the adapter does
|
|
133
|
+
the thinking:
|
|
134
|
+
|
|
135
|
+
```jsonc
|
|
136
|
+
{ "source": { "kind": "form", "key": "abstract-submission" }, ... }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- Fields with `reportable: true` become report columns (labels, types, lookup
|
|
140
|
+
data sources carried over); `indexHint: true` fields are sortable/filterable.
|
|
141
|
+
- **Column-id convention:** a definition column over a form field must use the
|
|
142
|
+
field path (dots → `_`) as its column id; physical submission columns are
|
|
143
|
+
referenced as `$row.status`, `$row.createdAt`, `$row.updatedAt`,
|
|
144
|
+
`$row.createdBy`.
|
|
145
|
+
- On the `indexed` tier the publish lint demands the generated columns +
|
|
146
|
+
indexes (hashed `rb_*` names, immutable expressions, datetimes kept as
|
|
147
|
+
ISO-8601 UTC text + `COLLATE "C"`); the 422's `suggestionSql` is the exact
|
|
148
|
+
migration to apply — run it, publish again.
|
|
149
|
+
- Grid verbs delegate to the form engine: `editSubmission` re-validates
|
|
150
|
+
against the submission's stamped `formVersion`; `updateStatus` runs the same
|
|
151
|
+
pipeline as the form. Both require `formSubmissions.update`. There is no
|
|
152
|
+
generic UPDATE — the report engine never mutates source rows itself.
|
|
153
|
+
- Known limitation: columns resolve against the latest **published** form
|
|
154
|
+
version. Renamed-field mapping across versions needs rename metadata the form
|
|
155
|
+
definition format does not yet carry.
|
|
156
|
+
|
|
157
|
+
## Performance tiers
|
|
158
|
+
|
|
159
|
+
| Tier | Meaning | Publish gate |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| `live` | Query raw source/JSONB directly | Source row estimate under `reports.liveTierMaxRows` |
|
|
162
|
+
| `indexed` | Hot columns physical + index-declared | Catalog + EXPLAIN lint pass (suggested SQL on failure) |
|
|
163
|
+
| `materialized` | Reads hit a declared materialized relation | Relation exists; staleness bound declared; `meta.freshAsOf` always set |
|
|
164
|
+
|
|
165
|
+
Tier upgrades are ordinary new versions: v5 `materialized` publishes after its
|
|
166
|
+
migration lands while v4 keeps serving.
|
|
167
|
+
|
|
168
|
+
## Row actions & drift-safe bulk
|
|
169
|
+
|
|
170
|
+
Single rows / explicit lists go through `byIds` (capped at 1,000). Filter-based
|
|
171
|
+
bulk (`byFilter`) is a two-step protocol because rows matching at confirmation
|
|
172
|
+
may differ at execution:
|
|
173
|
+
|
|
174
|
+
1. `POST .../actions/:name/prepare { selection: { byFilter } }` →
|
|
175
|
+
`{ expectedCount, asOf, actionToken }` (HMAC-signed, 5-minute TTL).
|
|
176
|
+
2. `POST .../actions/:name { selection, actionToken, input, idempotencyKey }` —
|
|
177
|
+
the server re-resolves the selection; drift beyond tolerance (0 for
|
|
178
|
+
destructive actions) is a 409 with the current count. The
|
|
179
|
+
`idempotencyKey` is persisted: a retried execute returns the recorded
|
|
180
|
+
outcome instead of re-running.
|
|
181
|
+
|
|
182
|
+
Execution walks rows in keyset order in transactional batches; every batch
|
|
183
|
+
re-applies org + rowScope. Every bulk run writes an audit row with report key,
|
|
184
|
+
version, action, row count, and the filter snapshot.
|
|
185
|
+
|
|
186
|
+
## Tags & labels
|
|
187
|
+
|
|
188
|
+
`manageTags` (built-in action, `reportTags.manage`) annotates rows with
|
|
189
|
+
org-scoped tags without touching the source schema. Rows hydrate `$tags`; the
|
|
190
|
+
virtual `$tags` column filters with `hasTag` / `hasAnyTag` (indexed EXISTS).
|
|
191
|
+
Labels are single-valued tags by convention (`label:approved` replaces the
|
|
192
|
+
row family's previous `label:*`). Curate the vocabulary with the
|
|
193
|
+
`reports.tagVocabulary` setting (empty = free-form).
|
|
194
|
+
|
|
195
|
+
## Exports
|
|
196
|
+
|
|
197
|
+
`POST /reports/:key/export { format: "csv" | "xlsx", spec?, columns? }`:
|
|
198
|
+
|
|
199
|
+
- Up to `reports.maxRowsSync` (default 10k): direct stream with
|
|
200
|
+
`Content-Disposition`. Larger: `202 { job }` — the job is picked up by the
|
|
201
|
+
reports-owned export worker (polling `ReportExportJob`, no forms outbox),
|
|
202
|
+
streamed into reports-owned org-scoped file storage
|
|
203
|
+
(`REPORTS_EXPORT_STORAGE_DIR`), polled at `GET /reports/exports/:jobId`, and
|
|
204
|
+
downloaded from `GET /reports/exports/:jobId/download`.
|
|
205
|
+
- The default local-disk storage is for a single app instance or for multiple
|
|
206
|
+
instances sharing the same mounted volume. For horizontal scaling without a
|
|
207
|
+
shared volume, bind an S3/GCS-backed `ExportFileSink` so downloads can land on
|
|
208
|
+
any node.
|
|
209
|
+
- Finished async export files are retained for `REPORTS_EXPORT_RETENTION_DAYS`
|
|
210
|
+
(default 7) and swept by the reports export worker. After expiry, the job row
|
|
211
|
+
remains for audit/status history but the file is no longer downloadable.
|
|
212
|
+
- Every export — sync or async — runs inside ONE `REPEATABLE READ` snapshot
|
|
213
|
+
(duration-bounded by `reports.exportMaxSnapshotSeconds`); an export that
|
|
214
|
+
cannot finish in time is rejected with guidance to tighten filters or move
|
|
215
|
+
to the materialized tier.
|
|
216
|
+
- `reports.export` is never implied by read; **every export writes an audit
|
|
217
|
+
row** with report key, version, filter snapshot, row count, and the exact
|
|
218
|
+
column list. Columns marked `exportable: false` are stripped regardless of
|
|
219
|
+
the requested projection.
|
|
220
|
+
|
|
221
|
+
## Saved views
|
|
222
|
+
|
|
223
|
+
Views store the QuerySpec **and the version they were authored against**.
|
|
224
|
+
Listing views returns a `compatibility` verdict per view against the requested
|
|
225
|
+
version — `ok` | `degraded` (names missing/retyped columns) | `incompatible` —
|
|
226
|
+
so a new publish turns stale views into a visible, fixable state instead of a
|
|
227
|
+
silent 500. Personal views need `reports.read`; shared views (no owner) need
|
|
228
|
+
`reports.update`.
|
|
229
|
+
|
|
230
|
+
## Org settings & env
|
|
231
|
+
|
|
232
|
+
Typed settings (audited like all settings): `reports.statementTimeoutMs`
|
|
233
|
+
(5000), `reports.exportMaxSnapshotSeconds` (120), `reports.maxRowsSync`
|
|
234
|
+
(10000), `reports.countCap` (10000), `reports.liveTierMaxRows` (50000),
|
|
235
|
+
`reports.resultCacheTtlMs` (0 = cache off), `reports.tagVocabulary` ([]).
|
|
236
|
+
|
|
237
|
+
Env: `REPORTS_SCHEMA_CHECK` (`on`/`off`); `REPORTS_TOKEN_SECRET` (optional, min
|
|
238
|
+
32 chars — cursor/token HMAC key; when empty a dedicated key is derived from
|
|
239
|
+
`JWT_SECRET` via HKDF; rotating it invalidates outstanding cursors/tokens —
|
|
240
|
+
clients restart from page one); and the reports-owned async-export worker:
|
|
241
|
+
`REPORTS_EXPORT_WORKER_ENABLED` (default true), `REPORTS_EXPORT_POLL_MS`
|
|
242
|
+
(default 5000), `REPORTS_EXPORT_STORAGE_DIR` (default `./var/report-exports`),
|
|
243
|
+
`REPORTS_EXPORT_RETENTION_DAYS` (default 7; 0 disables cleanup), and
|
|
244
|
+
`REPORTS_EXPORT_RETENTION_SWEEP_MS` (default 3600000).
|
|
245
|
+
|
|
246
|
+
## Engine upgrades (schema ownership)
|
|
247
|
+
|
|
248
|
+
The app owns `schema.prisma` and the migration history; the engine ships the
|
|
249
|
+
canonical snippet at `@ftisindia/report-builder/prisma/reports.prisma` plus
|
|
250
|
+
`REPORTS_ENGINE_SCHEMA_VERSION`. The boot check verifies tables/columns AND
|
|
251
|
+
the two **partial unique indexes** on `ReportSavedView`
|
|
252
|
+
(`report_view_shared_uq`, `report_view_personal_uq`) — Prisma cannot express
|
|
253
|
+
partial indexes, so they live as raw SQL in the `add_report_builder`
|
|
254
|
+
migration. If a later `prisma migrate dev` proposes `DROP INDEX` for them,
|
|
255
|
+
delete those lines; the boot check fails fast if they ever go missing.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Reports Module Completion Checklist
|
|
2
|
+
|
|
3
|
+
This is the current go/no-go record for the reports module on top of the generic
|
|
4
|
+
[`MODULE_COMPLETION_CHECKLIST.md`](./MODULE_COMPLETION_CHECKLIST.md). Section
|
|
5
|
+
references are to `report-builder/report-builder-design.md` Rev 2.
|
|
6
|
+
|
|
7
|
+
## Boundary and Standalone Guarantee
|
|
8
|
+
|
|
9
|
+
- [x] The engine package (`@ftisindia/report-builder`) compiles and unit-tests
|
|
10
|
+
without the template and without `@ftisindia/form-builder` installed. Proof:
|
|
11
|
+
`.github/workflows/ci.yml` `report-builder-engine`.
|
|
12
|
+
- [x] The engine boundary lint blocks `@nestjs/*`, `@prisma/*`, `@casl/*`,
|
|
13
|
+
`@ftisindia/form-builder`, and template imports from the core; engine errors
|
|
14
|
+
are mapped to HTTP only in `reports-error.mapper.ts`.
|
|
15
|
+
- [x] `ReportsModule` imports no `FormsModule`. Custom-source reports, tags,
|
|
16
|
+
query, row actions, and async exports run from reports-owned glue. Form-backed
|
|
17
|
+
sources and delegated form verbs live only in `ReportsFormsModule`.
|
|
18
|
+
|
|
19
|
+
## Schema Ownership
|
|
20
|
+
|
|
21
|
+
- [x] Boot schema check passes on a migrated app and fails loudly on stale
|
|
22
|
+
schema. Proof: `test/reports-tiers.e2e-spec.ts`.
|
|
23
|
+
- [x] The check verifies `report_view_shared_uq` and
|
|
24
|
+
`report_view_personal_uq`, and prints the raw SQL when missing.
|
|
25
|
+
- [x] `packages/report-builder/prisma/reports.prisma` matches the template
|
|
26
|
+
Prisma models; app-owned migrations carry the raw partial indexes.
|
|
27
|
+
|
|
28
|
+
## Permissions and Registry
|
|
29
|
+
|
|
30
|
+
- [x] All report permission keys plus `formSubmissions.update` exist in the
|
|
31
|
+
template and are synced with the engine list. Proof:
|
|
32
|
+
`test/reports-permission-sync.spec.ts`.
|
|
33
|
+
- [x] Every report route is listed in `routePermissionRegistry`; the registry
|
|
34
|
+
validator covers the report controllers.
|
|
35
|
+
- [x] `reports.export` is enforced independently of `reports.read`. Proof:
|
|
36
|
+
`test/reports-query.e2e-spec.ts`.
|
|
37
|
+
- [x] Row actions are checked at attach time and execute time. Proof:
|
|
38
|
+
engine linter/action-service unit tests plus route-level e2e for `manageTags`.
|
|
39
|
+
|
|
40
|
+
## SQL Boundary
|
|
41
|
+
|
|
42
|
+
- [x] Definition-side SQL is rejected by meta-schema/lint; SQL-shaped paths are
|
|
43
|
+
rejected before SQL compilation. Proof: engine linter unit tests and
|
|
44
|
+
`test/reports-query.e2e-spec.ts`.
|
|
45
|
+
- [x] `$row.*` paths resolve only against manifest-exposed physical columns.
|
|
46
|
+
- [x] User values reach SQL only through bind parameters. Proof:
|
|
47
|
+
`ParamSink`, compiler snapshot tests, and Postgres e2e.
|
|
48
|
+
|
|
49
|
+
## Query Semantics
|
|
50
|
+
|
|
51
|
+
- [x] Keyset pagination is proven at depth; `OFFSET` is not used; the row-id
|
|
52
|
+
tiebreaker is always appended. Proof: compiler tests and
|
|
53
|
+
`test/reports-query.e2e-spec.ts`.
|
|
54
|
+
- [x] Cursor tamper and replay against changed sort/filter/version are rejected.
|
|
55
|
+
- [x] Typed operators reject invalid combinations before SQL.
|
|
56
|
+
- [x] Counts cover `none`, `estimated`, and the capped subquery `exact-capped`
|
|
57
|
+
shape.
|
|
58
|
+
- [x] Org predicates compile first; cross-org query/export/action attempts are
|
|
59
|
+
denied, and cross-org `byIds` resolve to zero rows. Proof:
|
|
60
|
+
`test/reports-query.e2e-spec.ts`.
|
|
61
|
+
- [x] Statement budgets are applied by `PrismaQueryExecutor` and mapped as typed
|
|
62
|
+
report errors.
|
|
63
|
+
|
|
64
|
+
## Tiers and Publish Lint
|
|
65
|
+
|
|
66
|
+
- [x] `live` tier publish fails above `reports.liveTierMaxRows`. Proof:
|
|
67
|
+
engine definition/tier-lint tests.
|
|
68
|
+
- [x] `indexed` tier publish fails with migration SQL, and the same draft
|
|
69
|
+
publishes after applying the required indexes. Proof:
|
|
70
|
+
`test/reports-tiers.e2e-spec.ts`.
|
|
71
|
+
- [x] Plan lint records compiled metadata at publish; queries use compiled
|
|
72
|
+
physical columns when present. Proof: engine tier-lint and definition-service
|
|
73
|
+
tests.
|
|
74
|
+
- [x] `materialized` tier requires the declared relation and returns
|
|
75
|
+
`meta.freshAsOf`. Proof: `test/reports-tiers.e2e-spec.ts`.
|
|
76
|
+
|
|
77
|
+
## Row Actions and Bulk
|
|
78
|
+
|
|
79
|
+
- [x] Reports do not raw-update/delete source tables; mutations delegate to
|
|
80
|
+
registered action handlers.
|
|
81
|
+
- [x] `byFilter` requires a token; prepare/execute works; drift returns 409;
|
|
82
|
+
expired tokens are rejected. Proof: engine action-service tests and
|
|
83
|
+
`test/reports-advanced.e2e-spec.ts`.
|
|
84
|
+
- [x] Idempotency replays recorded outcomes without rerunning the handler.
|
|
85
|
+
- [x] `byIds` is capped and cross-org ids resolve to zero rows. Proof:
|
|
86
|
+
engine action-service tests and `test/reports-query.e2e-spec.ts`.
|
|
87
|
+
|
|
88
|
+
## Tags
|
|
89
|
+
|
|
90
|
+
- [x] `manageTags` is gated by `reportTags.manage`; tags normalize; curated
|
|
91
|
+
vocabulary and `label:*` replacement are covered by engine tests.
|
|
92
|
+
- [x] `$tags` hydration and `hasTag` filters are covered against the
|
|
93
|
+
`ReportRowTag` table. Proof: `test/reports-query.e2e-spec.ts`.
|
|
94
|
+
|
|
95
|
+
## Exports
|
|
96
|
+
|
|
97
|
+
- [x] Sync export streams with `Content-Disposition`; async export creates a
|
|
98
|
+
`ReportExportJob`, runs on the reports-owned worker, writes a storage fileId,
|
|
99
|
+
and is pollable/downloadable.
|
|
100
|
+
- [x] Async export storage streams chunks to disk instead of buffering the whole
|
|
101
|
+
export in memory.
|
|
102
|
+
- [x] Local async export files have retention cleanup:
|
|
103
|
+
`REPORTS_EXPORT_RETENTION_DAYS` plus worker sweep.
|
|
104
|
+
- [x] Sync and async exports run in a `REPEATABLE READ` snapshot; over-long
|
|
105
|
+
snapshots map to the tier-guidance export error.
|
|
106
|
+
- [x] Every export writes a PII-egress audit row; `exportable: false` columns are
|
|
107
|
+
stripped even when requested.
|
|
108
|
+
- [x] XLSX output is structurally valid OOXML; CSV output has formula-injection
|
|
109
|
+
guarding.
|
|
110
|
+
|
|
111
|
+
## Saved Views
|
|
112
|
+
|
|
113
|
+
- [x] Shared-view create/update requires `reports.update`; duplicate shared
|
|
114
|
+
names are rejected by the partial unique index. Proof:
|
|
115
|
+
`test/reports-advanced.e2e-spec.ts`.
|
|
116
|
+
- [x] Personal/shared compatibility reports `ok`, `degraded`, or
|
|
117
|
+
`incompatible` instead of throwing on stale views. Proof: engine view-service
|
|
118
|
+
tests and template e2e.
|
|
119
|
+
|
|
120
|
+
## Envelope and Docs
|
|
121
|
+
|
|
122
|
+
- [x] Engine error codes map through `reports-error.mapper.ts`; Swagger
|
|
123
|
+
decorators document the report routes.
|
|
124
|
+
- [x] `docs/REPORTS.md` matches current storage topology, retention behavior,
|
|
125
|
+
tier coverage, and the form-field rename limitation.
|
|
126
|
+
|
|
127
|
+
## Current E2E Coverage
|
|
128
|
+
|
|
129
|
+
Template e2e coverage now includes the basic report lifecycle, keyset paging,
|
|
130
|
+
typed operator rejection, meta, `reports.read` vs `reports.export`, saved views,
|
|
131
|
+
tags, sync CSV export, SQL-boundary rejection, cross-org isolation for query /
|
|
132
|
+
export / `byIds`, byFilter token protocol, drift, idempotent replay, async
|
|
133
|
+
export worker + download + retention cleanup, XLSX validity, PII-egress audit,
|
|
134
|
+
shared-view partial unique enforcement, indexed-tier failure, indexed-tier
|
|
135
|
+
success after applying indexes, materialized tier `freshAsOf`, and boot
|
|
136
|
+
schema-check failure.
|
|
137
|
+
|
|
138
|
+
## Release and Operations Sign-Off
|
|
139
|
+
|
|
140
|
+
- [x] OPS-2: async local-disk exports no longer buffer the whole file in memory.
|
|
141
|
+
- [x] OPS-3: local async export files have retention cleanup and documented
|
|
142
|
+
retention env vars.
|
|
143
|
+
- [x] LIM-1: form-backed renamed fields are documented as a known limitation
|
|
144
|
+
until form definitions carry rename metadata.
|
|
145
|
+
- [x] OPS-1: deployment topology decision recorded. Single-node/shared-volume is
|
|
146
|
+
supported by the default local adapter; horizontal scaling without shared
|
|
147
|
+
storage requires rebinding `ExportFileSink` to object storage.
|
|
148
|
+
- [ ] PKG-1: deliberate `@ftisindia/report-builder` version/publish decision
|
|
149
|
+
made for go-live, following engine-before-CLI publish order.
|
|
150
|
+
- [ ] PROC-1: release commit/tag remains pending. `npm run sync-template` and
|
|
151
|
+
`npm run dogfood` have been run locally so the scaffolded CLI output carries
|
|
152
|
+
the same coverage.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
-- CreateEnum
|
|
2
|
+
CREATE TYPE "FormDefinitionStatus" AS ENUM ('DRAFT', 'PUBLISHED', 'ARCHIVED');
|
|
3
|
+
|
|
4
|
+
-- CreateEnum
|
|
5
|
+
CREATE TYPE "SubmissionStatus" AS ENUM ('DRAFT', 'SUBMITTED');
|
|
6
|
+
|
|
7
|
+
-- CreateEnum
|
|
8
|
+
CREATE TYPE "FormActionStatus" AS ENUM ('OK', 'ERROR');
|
|
9
|
+
|
|
10
|
+
-- CreateEnum
|
|
11
|
+
CREATE TYPE "OutboxStatus" AS ENUM ('PENDING', 'PROCESSING', 'DONE', 'FAILED');
|
|
12
|
+
|
|
13
|
+
-- CreateEnum
|
|
14
|
+
CREATE TYPE "FileStatus" AS ENUM ('TEMPORARY', 'SCANNING', 'CLEAN', 'INFECTED', 'LINKED');
|
|
15
|
+
|
|
16
|
+
-- CreateTable
|
|
17
|
+
CREATE TABLE "FormDefinition" (
|
|
18
|
+
"id" TEXT NOT NULL,
|
|
19
|
+
"orgId" TEXT NOT NULL,
|
|
20
|
+
"key" TEXT NOT NULL,
|
|
21
|
+
"version" INTEGER NOT NULL,
|
|
22
|
+
"status" "FormDefinitionStatus" NOT NULL DEFAULT 'DRAFT',
|
|
23
|
+
"schema" JSONB NOT NULL,
|
|
24
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
25
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
26
|
+
|
|
27
|
+
CONSTRAINT "FormDefinition_pkey" PRIMARY KEY ("id")
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
-- CreateTable
|
|
31
|
+
CREATE TABLE "FormSubmission" (
|
|
32
|
+
"id" TEXT NOT NULL,
|
|
33
|
+
"orgId" TEXT NOT NULL,
|
|
34
|
+
"formKey" TEXT NOT NULL,
|
|
35
|
+
"formVersion" INTEGER NOT NULL,
|
|
36
|
+
"data" JSONB NOT NULL,
|
|
37
|
+
"status" "SubmissionStatus" NOT NULL DEFAULT 'DRAFT',
|
|
38
|
+
"locked" BOOLEAN NOT NULL DEFAULT false,
|
|
39
|
+
"createdBy" TEXT,
|
|
40
|
+
"ipAddress" TEXT,
|
|
41
|
+
"userAgent" TEXT,
|
|
42
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
43
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
44
|
+
|
|
45
|
+
CONSTRAINT "FormSubmission_pkey" PRIMARY KEY ("id")
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
-- CreateTable
|
|
49
|
+
CREATE TABLE "FormActionLog" (
|
|
50
|
+
"id" TEXT NOT NULL,
|
|
51
|
+
"orgId" TEXT NOT NULL,
|
|
52
|
+
"submissionId" TEXT,
|
|
53
|
+
"formKey" TEXT NOT NULL,
|
|
54
|
+
"formVersion" INTEGER,
|
|
55
|
+
"action" TEXT NOT NULL,
|
|
56
|
+
"status" "FormActionStatus" NOT NULL,
|
|
57
|
+
"input" JSONB,
|
|
58
|
+
"output" JSONB,
|
|
59
|
+
"errorCode" TEXT,
|
|
60
|
+
"errorMessage" TEXT,
|
|
61
|
+
"actorId" TEXT,
|
|
62
|
+
"requestId" TEXT,
|
|
63
|
+
"durationMs" INTEGER,
|
|
64
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
65
|
+
|
|
66
|
+
CONSTRAINT "FormActionLog_pkey" PRIMARY KEY ("id")
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
-- CreateTable
|
|
70
|
+
CREATE TABLE "FormOutboxJob" (
|
|
71
|
+
"id" TEXT NOT NULL,
|
|
72
|
+
"type" TEXT NOT NULL,
|
|
73
|
+
"payload" JSONB NOT NULL,
|
|
74
|
+
"status" "OutboxStatus" NOT NULL DEFAULT 'PENDING',
|
|
75
|
+
"attempts" INTEGER NOT NULL DEFAULT 0,
|
|
76
|
+
"maxAttempts" INTEGER NOT NULL DEFAULT 8,
|
|
77
|
+
"idempotencyKey" TEXT,
|
|
78
|
+
"lastError" TEXT,
|
|
79
|
+
"runAfter" TIMESTAMP(3),
|
|
80
|
+
"orgId" TEXT,
|
|
81
|
+
"actorUserId" TEXT,
|
|
82
|
+
"originRequestId" TEXT,
|
|
83
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
84
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
85
|
+
|
|
86
|
+
CONSTRAINT "FormOutboxJob_pkey" PRIMARY KEY ("id")
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
-- CreateTable
|
|
90
|
+
CREATE TABLE "UploadedFile" (
|
|
91
|
+
"id" TEXT NOT NULL,
|
|
92
|
+
"storageKey" TEXT NOT NULL,
|
|
93
|
+
"originalName" TEXT NOT NULL,
|
|
94
|
+
"mimeType" TEXT NOT NULL,
|
|
95
|
+
"size" INTEGER NOT NULL,
|
|
96
|
+
"checksum" TEXT NOT NULL,
|
|
97
|
+
"ownerId" TEXT NOT NULL,
|
|
98
|
+
"orgId" TEXT NOT NULL,
|
|
99
|
+
"status" "FileStatus" NOT NULL DEFAULT 'TEMPORARY',
|
|
100
|
+
"submissionId" TEXT,
|
|
101
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
102
|
+
|
|
103
|
+
CONSTRAINT "UploadedFile_pkey" PRIMARY KEY ("id")
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
-- CreateIndex
|
|
107
|
+
CREATE INDEX "FormDefinition_orgId_idx" ON "FormDefinition"("orgId");
|
|
108
|
+
|
|
109
|
+
-- CreateIndex
|
|
110
|
+
CREATE INDEX "FormDefinition_orgId_status_idx" ON "FormDefinition"("orgId", "status");
|
|
111
|
+
|
|
112
|
+
-- CreateIndex
|
|
113
|
+
CREATE UNIQUE INDEX "FormDefinition_orgId_key_version_key" ON "FormDefinition"("orgId", "key", "version");
|
|
114
|
+
|
|
115
|
+
-- CreateIndex
|
|
116
|
+
CREATE INDEX "FormSubmission_orgId_formKey_status_idx" ON "FormSubmission"("orgId", "formKey", "status");
|
|
117
|
+
|
|
118
|
+
-- CreateIndex
|
|
119
|
+
CREATE INDEX "FormSubmission_orgId_createdAt_idx" ON "FormSubmission"("orgId", "createdAt");
|
|
120
|
+
|
|
121
|
+
-- CreateIndex
|
|
122
|
+
CREATE INDEX "FormSubmission_orgId_formKey_ipAddress_createdAt_idx" ON "FormSubmission"("orgId", "formKey", "ipAddress", "createdAt");
|
|
123
|
+
|
|
124
|
+
-- CreateIndex
|
|
125
|
+
CREATE INDEX "FormActionLog_orgId_createdAt_idx" ON "FormActionLog"("orgId", "createdAt");
|
|
126
|
+
|
|
127
|
+
-- CreateIndex
|
|
128
|
+
CREATE INDEX "FormActionLog_orgId_formKey_action_status_idx" ON "FormActionLog"("orgId", "formKey", "action", "status");
|
|
129
|
+
|
|
130
|
+
-- CreateIndex
|
|
131
|
+
CREATE UNIQUE INDEX "FormOutboxJob_idempotencyKey_key" ON "FormOutboxJob"("idempotencyKey");
|
|
132
|
+
|
|
133
|
+
-- CreateIndex
|
|
134
|
+
CREATE INDEX "FormOutboxJob_status_runAfter_idx" ON "FormOutboxJob"("status", "runAfter");
|
|
135
|
+
|
|
136
|
+
-- CreateIndex
|
|
137
|
+
CREATE UNIQUE INDEX "UploadedFile_storageKey_key" ON "UploadedFile"("storageKey");
|
|
138
|
+
|
|
139
|
+
-- CreateIndex
|
|
140
|
+
CREATE INDEX "UploadedFile_orgId_status_idx" ON "UploadedFile"("orgId", "status");
|
|
141
|
+
|
|
142
|
+
-- CreateIndex
|
|
143
|
+
CREATE INDEX "UploadedFile_ownerId_status_idx" ON "UploadedFile"("ownerId", "status");
|
|
144
|
+
|
|
145
|
+
-- CreateIndex
|
|
146
|
+
CREATE INDEX "UploadedFile_status_createdAt_idx" ON "UploadedFile"("status", "createdAt");
|
|
147
|
+
|