@ydtb/tk-scope-capture 0.22.0 → 0.23.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/dist/src/client/components/CaptureFormSurface.d.ts +58 -0
- package/dist/src/client/components/CaptureFormSurface.d.ts.map +1 -0
- package/dist/src/client/components/CaptureFormSurface.js +301 -0
- package/dist/src/client/components/CaptureFormSurface.js.map +1 -0
- package/dist/src/client/components/CaptureSidebar.d.ts +43 -0
- package/dist/src/client/components/CaptureSidebar.d.ts.map +1 -0
- package/dist/src/client/components/CaptureSidebar.js +38 -0
- package/dist/src/client/components/CaptureSidebar.js.map +1 -0
- package/dist/src/client/pages/CaptureBuilderPage.d.ts.map +1 -1
- package/dist/src/client/pages/CaptureBuilderPage.js +1869 -129
- package/dist/src/client/pages/CaptureBuilderPage.js.map +1 -1
- package/dist/src/client/pages/CaptureSubmissionDetailPage.d.ts.map +1 -1
- package/dist/src/client/pages/CaptureSubmissionDetailPage.js +61 -10
- package/dist/src/client/pages/CaptureSubmissionDetailPage.js.map +1 -1
- package/dist/src/client/pages/CaptureSubmissionsPage.d.ts.map +1 -1
- package/dist/src/client/pages/CaptureSubmissionsPage.js +14 -2
- package/dist/src/client/pages/CaptureSubmissionsPage.js.map +1 -1
- package/dist/src/client/pages/PublicFormPage.d.ts.map +1 -1
- package/dist/src/client/pages/PublicFormPage.js +13 -56
- package/dist/src/client/pages/PublicFormPage.js.map +1 -1
- package/dist/src/client/pages/PublicQuizPage.d.ts.map +1 -1
- package/dist/src/client/pages/PublicQuizPage.js +23 -11
- package/dist/src/client/pages/PublicQuizPage.js.map +1 -1
- package/dist/src/client/pages/QuizBuilderPage.d.ts.map +1 -1
- package/dist/src/client/pages/QuizBuilderPage.js +6 -2
- package/dist/src/client/pages/QuizBuilderPage.js.map +1 -1
- package/dist/src/client.d.ts +858 -15
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +2 -0
- package/dist/src/client.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/server/api/action-adapters.d.ts +2 -0
- package/dist/src/server/api/action-adapters.d.ts.map +1 -1
- package/dist/src/server/api/action-adapters.js +1 -0
- package/dist/src/server/api/action-adapters.js.map +1 -1
- package/dist/src/server/api/router.d.ts +884 -16
- package/dist/src/server/api/router.d.ts.map +1 -1
- package/dist/src/server/api/router.js +547 -22
- package/dist/src/server/api/router.js.map +1 -1
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +51 -1
- package/dist/src/server.js.map +1 -1
- package/dist/src/shared/conditions.d.ts +2 -2
- package/dist/src/shared/conditions.d.ts.map +1 -1
- package/dist/src/shared/conditions.js +47 -0
- package/dist/src/shared/conditions.js.map +1 -1
- package/dist/src/shared/db/schema.d.ts +414 -0
- package/dist/src/shared/db/schema.d.ts.map +1 -1
- package/dist/src/shared/db/schema.js +40 -0
- package/dist/src/shared/db/schema.js.map +1 -1
- package/dist/src/shared/document.d.ts +120 -0
- package/dist/src/shared/document.d.ts.map +1 -0
- package/dist/src/shared/document.js +111 -0
- package/dist/src/shared/document.js.map +1 -0
- package/dist/src/shared/field-types.d.ts +2 -0
- package/dist/src/shared/field-types.d.ts.map +1 -1
- package/dist/src/shared/field-types.js +43 -10
- package/dist/src/shared/field-types.js.map +1 -1
- package/dist/src/shared/types.d.ts +90 -3
- package/dist/src/shared/types.d.ts.map +1 -1
- package/dist/src/shared/types.js.map +1 -1
- package/dist/src/shared/validation.d.ts +6 -0
- package/dist/src/shared/validation.d.ts.map +1 -0
- package/dist/src/shared/validation.js +87 -0
- package/dist/src/shared/validation.js.map +1 -0
- package/package.json +11 -7
|
@@ -4,14 +4,53 @@ import "@ydtb/core-layer-postgres";
|
|
|
4
4
|
import { and, desc, eq, gte, isNull, sql } from "@ydtb/tk-scope-db/schema";
|
|
5
5
|
import { base, requirePermission, scopeAuthed } from "@ydtb/tk-scope-extension/orpc";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import { captureActionRuns, captureAnswers, captureDefinitions, capturePublications, captureSubmissions, } from "../../shared/db/schema.js";
|
|
8
|
-
import { getVisibleCaptureFields } from "../../shared/conditions.js";
|
|
9
|
-
import {
|
|
7
|
+
import { captureActionRuns, captureAnswers, captureDefinitions, captureItemViews, captureSubmissionViews, capturePublications, captureSubmissions, } from "../../shared/db/schema.js";
|
|
8
|
+
import { evaluateCaptureCondition, getVisibleCaptureFields } from "../../shared/conditions.js";
|
|
9
|
+
import { deriveCaptureDocumentFromFields, extractCaptureAnswerFields, normalizeCaptureDocumentForPersistence, resolveCaptureDocument, } from "../../shared/document.js";
|
|
10
|
+
import { REFERENCE_CAPTURE_FIELD_TYPES, isHiddenCaptureField, isHoneypotCaptureField, normalizeCaptureAnswer, validateCaptureAnswer } from "../../shared/field-types.js";
|
|
11
|
+
import { validateCaptureFieldRules } from "../../shared/validation.js";
|
|
10
12
|
import { evaluateQuizOutcome } from "../../shared/scoring.js";
|
|
11
|
-
import { emailActionAdapter, emailActionConfigSchema, executeWorkflowAction, workflowActionAdapter, workflowActionConfigSchema } from "./action-adapters.js";
|
|
13
|
+
import { buildWorkflowSubmissionPayload, emailActionAdapter, emailActionConfigSchema, executeWorkflowAction, workflowActionAdapter, workflowActionConfigSchema } from "./action-adapters.js";
|
|
12
14
|
function getDb() {
|
|
13
15
|
return getLayer("database").db;
|
|
14
16
|
}
|
|
17
|
+
async function resolveCaptureStorageImageUrls(args) {
|
|
18
|
+
const storageItems = args.document.items.filter((item) => {
|
|
19
|
+
if (item.kind !== "content.image")
|
|
20
|
+
return false;
|
|
21
|
+
return item.source?.type === "storage" && !!item.source.storageItemId;
|
|
22
|
+
});
|
|
23
|
+
if (storageItems.length === 0)
|
|
24
|
+
return args.document;
|
|
25
|
+
const resolvedUrls = new Map();
|
|
26
|
+
await Promise.all(storageItems.map(async (item) => {
|
|
27
|
+
const storageItemId = item.source?.storageItemId;
|
|
28
|
+
if (!storageItemId)
|
|
29
|
+
return;
|
|
30
|
+
try {
|
|
31
|
+
const result = await getHooks().doAction("storage:get-download-url", {
|
|
32
|
+
scope: args.scope,
|
|
33
|
+
scopeId: args.scopeId,
|
|
34
|
+
itemId: storageItemId,
|
|
35
|
+
});
|
|
36
|
+
if (result?.url)
|
|
37
|
+
resolvedUrls.set(item.id, result.url);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Missing/unavailable storage should not make the whole public
|
|
41
|
+
// form unavailable. The renderer falls back to the image
|
|
42
|
+
// placeholder for unresolved decorative content.
|
|
43
|
+
}
|
|
44
|
+
}));
|
|
45
|
+
if (resolvedUrls.size === 0)
|
|
46
|
+
return args.document;
|
|
47
|
+
return {
|
|
48
|
+
...args.document,
|
|
49
|
+
items: args.document.items.map((item) => item.kind === "content.image" && resolvedUrls.has(item.id)
|
|
50
|
+
? { ...item, imageUrl: resolvedUrls.get(item.id) }
|
|
51
|
+
: item),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
15
54
|
const fieldTypeSchema = z.string().min(1).max(80);
|
|
16
55
|
const answerValueSchema = z.union([
|
|
17
56
|
z.string(),
|
|
@@ -20,12 +59,71 @@ const answerValueSchema = z.union([
|
|
|
20
59
|
z.null(),
|
|
21
60
|
z.array(z.string()),
|
|
22
61
|
]);
|
|
23
|
-
const
|
|
62
|
+
const legacyConditionSchema = z.object({
|
|
24
63
|
fieldId: z.string().min(1).max(80),
|
|
25
64
|
fieldKey: z.string().min(1).max(80),
|
|
26
65
|
operator: z.enum(["equals", "notEquals", "isEmpty", "isNotEmpty"]),
|
|
27
66
|
value: answerValueSchema.optional(),
|
|
28
67
|
});
|
|
68
|
+
const filterConditionSchema = z.object({
|
|
69
|
+
id: z.string().min(1).max(80),
|
|
70
|
+
fieldKey: z.string().min(1).max(80),
|
|
71
|
+
operator: z.enum([
|
|
72
|
+
"is",
|
|
73
|
+
"is_not",
|
|
74
|
+
"contains",
|
|
75
|
+
"does_not_contain",
|
|
76
|
+
"starts_with",
|
|
77
|
+
"ends_with",
|
|
78
|
+
"is_empty",
|
|
79
|
+
"is_not_empty",
|
|
80
|
+
"greater_than",
|
|
81
|
+
"less_than",
|
|
82
|
+
"greater_than_or_equal",
|
|
83
|
+
"less_than_or_equal",
|
|
84
|
+
]),
|
|
85
|
+
value: z.string().optional(),
|
|
86
|
+
});
|
|
87
|
+
const conditionGroupSchema = z.lazy(() => z.object({
|
|
88
|
+
id: z.string().min(1).max(80),
|
|
89
|
+
logic: z.enum(["and", "or"]),
|
|
90
|
+
conditions: z.array(z.union([filterConditionSchema, conditionGroupSchema])),
|
|
91
|
+
}));
|
|
92
|
+
const conditionSchema = z.union([legacyConditionSchema, conditionGroupSchema]);
|
|
93
|
+
const validationOperatorSchema = z.enum([
|
|
94
|
+
"is",
|
|
95
|
+
"is_not",
|
|
96
|
+
"contains",
|
|
97
|
+
"does_not_contain",
|
|
98
|
+
"starts_with",
|
|
99
|
+
"ends_with",
|
|
100
|
+
"regex",
|
|
101
|
+
"is_empty",
|
|
102
|
+
"is_not_empty",
|
|
103
|
+
"greater_than",
|
|
104
|
+
"less_than",
|
|
105
|
+
"greater_than_or_equal",
|
|
106
|
+
"less_than_or_equal",
|
|
107
|
+
]);
|
|
108
|
+
const validationExpressionSchema = z.lazy(() => z.union([
|
|
109
|
+
z.object({
|
|
110
|
+
id: z.string().min(1).max(80),
|
|
111
|
+
fieldKey: z.string().min(1).max(80),
|
|
112
|
+
operator: validationOperatorSchema,
|
|
113
|
+
value: z.string().max(500).optional(),
|
|
114
|
+
valueFieldKey: z.string().min(1).max(80).optional(),
|
|
115
|
+
}),
|
|
116
|
+
z.object({
|
|
117
|
+
id: z.string().min(1).max(80),
|
|
118
|
+
logic: z.enum(["and", "or"]),
|
|
119
|
+
conditions: z.array(validationExpressionSchema),
|
|
120
|
+
}),
|
|
121
|
+
]));
|
|
122
|
+
const validationRuleSchema = z.object({
|
|
123
|
+
id: z.string().min(1).max(80),
|
|
124
|
+
message: z.string().min(1).max(300),
|
|
125
|
+
expression: validationExpressionSchema,
|
|
126
|
+
});
|
|
29
127
|
const fieldSchema = z.object({
|
|
30
128
|
id: z.string().min(1).max(80),
|
|
31
129
|
key: z.string().min(1).max(80),
|
|
@@ -34,7 +132,116 @@ const fieldSchema = z.object({
|
|
|
34
132
|
required: z.boolean().default(false),
|
|
35
133
|
options: z.array(z.string().min(1).max(120)).optional(),
|
|
36
134
|
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
135
|
+
hidden: z.boolean().optional(),
|
|
136
|
+
visibleWhen: conditionSchema.optional(),
|
|
137
|
+
validationRules: z.array(validationRuleSchema).max(1).optional(),
|
|
138
|
+
});
|
|
139
|
+
const documentPageSchema = z.object({
|
|
140
|
+
id: z.string().min(1).max(80),
|
|
141
|
+
title: z.string().min(1).max(160),
|
|
142
|
+
preHeading: z.string().max(80).optional(),
|
|
143
|
+
description: z.string().max(500).optional(),
|
|
144
|
+
headerVisible: z.boolean().optional(),
|
|
145
|
+
headerContentInitialized: z.boolean().optional(),
|
|
146
|
+
hidden: z.boolean().optional(),
|
|
37
147
|
visibleWhen: conditionSchema.optional(),
|
|
148
|
+
order: z.number().int().min(0),
|
|
149
|
+
});
|
|
150
|
+
const inputItemSchema = fieldSchema.extend({
|
|
151
|
+
kind: z.literal("input"),
|
|
152
|
+
pageId: z.string().min(1).max(80),
|
|
153
|
+
order: z.number().int().min(0),
|
|
154
|
+
});
|
|
155
|
+
const textBlockItemSchema = z.object({
|
|
156
|
+
id: z.string().min(1).max(80),
|
|
157
|
+
kind: z.literal("content.text"),
|
|
158
|
+
pageId: z.string().min(1).max(80),
|
|
159
|
+
order: z.number().int().min(0),
|
|
160
|
+
title: z.string().max(160).optional(),
|
|
161
|
+
body: z.string().max(5000),
|
|
162
|
+
hidden: z.boolean().optional(),
|
|
163
|
+
visibleWhen: conditionSchema.optional(),
|
|
164
|
+
});
|
|
165
|
+
const imageBlockItemSchema = z.object({
|
|
166
|
+
id: z.string().min(1).max(80),
|
|
167
|
+
kind: z.literal("content.image"),
|
|
168
|
+
pageId: z.string().min(1).max(80),
|
|
169
|
+
order: z.number().int().min(0),
|
|
170
|
+
imageUrl: z.string().max(500_000),
|
|
171
|
+
altText: z.string().max(300).optional(),
|
|
172
|
+
caption: z.string().max(500).optional(),
|
|
173
|
+
shape: z.enum(["square", "circle"]).optional(),
|
|
174
|
+
align: z.enum(["left", "center", "right"]).optional(),
|
|
175
|
+
size: z.enum(["small", "medium", "large", "full"]).optional(),
|
|
176
|
+
fit: z.enum(["cover", "contain"]).optional(),
|
|
177
|
+
source: z.object({
|
|
178
|
+
type: z.enum(["url", "upload", "storage"]),
|
|
179
|
+
storageItemId: z.string().min(1).optional(),
|
|
180
|
+
}).optional(),
|
|
181
|
+
hidden: z.boolean().optional(),
|
|
182
|
+
visibleWhen: conditionSchema.optional(),
|
|
183
|
+
});
|
|
184
|
+
const iconBlockItemSchema = z.object({
|
|
185
|
+
id: z.string().min(1).max(80),
|
|
186
|
+
kind: z.literal("content.icon"),
|
|
187
|
+
pageId: z.string().min(1).max(80),
|
|
188
|
+
order: z.number().int().min(0),
|
|
189
|
+
icon: z.string().min(1).max(120),
|
|
190
|
+
iconColor: z.string().max(40).optional(),
|
|
191
|
+
align: z.enum(["left", "center", "right"]),
|
|
192
|
+
hidden: z.boolean().optional(),
|
|
193
|
+
visibleWhen: conditionSchema.optional(),
|
|
194
|
+
});
|
|
195
|
+
const dividerBlockItemSchema = z.object({
|
|
196
|
+
id: z.string().min(1).max(80),
|
|
197
|
+
kind: z.literal("content.divider"),
|
|
198
|
+
pageId: z.string().min(1).max(80),
|
|
199
|
+
order: z.number().int().min(0),
|
|
200
|
+
hidden: z.boolean().optional(),
|
|
201
|
+
visibleWhen: conditionSchema.optional(),
|
|
202
|
+
});
|
|
203
|
+
const pageHeaderBlockItemSchema = z.object({
|
|
204
|
+
id: z.string().min(1).max(80),
|
|
205
|
+
kind: z.literal("content.page-header"),
|
|
206
|
+
pageId: z.string().min(1).max(80),
|
|
207
|
+
order: z.number().int().min(0),
|
|
208
|
+
preHeading: z.string().max(80).optional(),
|
|
209
|
+
title: z.string().min(1).max(160),
|
|
210
|
+
description: z.string().max(500).optional(),
|
|
211
|
+
hidden: z.boolean().optional(),
|
|
212
|
+
visibleWhen: conditionSchema.optional(),
|
|
213
|
+
});
|
|
214
|
+
const pageProgressBlockItemSchema = z.object({
|
|
215
|
+
id: z.string().min(1).max(80),
|
|
216
|
+
kind: z.literal("content.page-progress"),
|
|
217
|
+
pageId: z.string().min(1).max(80),
|
|
218
|
+
order: z.number().int().min(0),
|
|
219
|
+
hidden: z.boolean().optional(),
|
|
220
|
+
visibleWhen: conditionSchema.optional(),
|
|
221
|
+
});
|
|
222
|
+
const formDocumentSchema = z.object({
|
|
223
|
+
version: z.literal(1),
|
|
224
|
+
pages: z.array(documentPageSchema).min(1).max(50),
|
|
225
|
+
items: z.array(z.discriminatedUnion("kind", [
|
|
226
|
+
inputItemSchema,
|
|
227
|
+
textBlockItemSchema,
|
|
228
|
+
imageBlockItemSchema,
|
|
229
|
+
iconBlockItemSchema,
|
|
230
|
+
dividerBlockItemSchema,
|
|
231
|
+
pageHeaderBlockItemSchema,
|
|
232
|
+
pageProgressBlockItemSchema,
|
|
233
|
+
])).max(200),
|
|
234
|
+
}).superRefine((document, context) => {
|
|
235
|
+
const pageIds = new Set(document.pages.map((page) => page.id));
|
|
236
|
+
const inputCount = document.items.filter((item) => item.kind === "input").length;
|
|
237
|
+
if (inputCount > 50) {
|
|
238
|
+
context.addIssue({ code: "custom", message: "Capture forms can have at most 50 answer fields", path: ["items"] });
|
|
239
|
+
}
|
|
240
|
+
for (const [index, item] of document.items.entries()) {
|
|
241
|
+
if (!pageIds.has(item.pageId)) {
|
|
242
|
+
context.addIssue({ code: "custom", message: "Document item references an unknown page", path: ["items", index, "pageId"] });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
38
245
|
});
|
|
39
246
|
const contactsActionConfigSchema = z.object({
|
|
40
247
|
enabled: z.boolean().default(false),
|
|
@@ -62,6 +269,25 @@ const quizScoringConfigSchema = z.object({
|
|
|
62
269
|
outcomeKey: z.string().optional(),
|
|
63
270
|
})).default([]),
|
|
64
271
|
});
|
|
272
|
+
const captureItemViewColumnSchema = z.object({
|
|
273
|
+
fieldKey: z.string().min(1).max(80),
|
|
274
|
+
visible: z.boolean(),
|
|
275
|
+
position: z.number().int().min(0),
|
|
276
|
+
});
|
|
277
|
+
const captureItemViewSortSchema = z.object({
|
|
278
|
+
field: z.string().min(1).max(80),
|
|
279
|
+
direction: z.enum(["asc", "desc"]),
|
|
280
|
+
});
|
|
281
|
+
const captureItemViewStateSchema = z.object({
|
|
282
|
+
filters: conditionGroupSchema.optional().nullable(),
|
|
283
|
+
sorts: z.array(captureItemViewSortSchema).max(3).optional().nullable(),
|
|
284
|
+
columns: z.array(captureItemViewColumnSchema).max(24).optional().nullable(),
|
|
285
|
+
});
|
|
286
|
+
const captureSubmissionViewStateSchema = z.object({
|
|
287
|
+
filters: conditionGroupSchema.optional().nullable(),
|
|
288
|
+
sorts: z.array(captureItemViewSortSchema).max(3).optional().nullable(),
|
|
289
|
+
columns: z.array(captureItemViewColumnSchema).max(80).optional().nullable(),
|
|
290
|
+
});
|
|
65
291
|
const actionConfigSchema = z.object({
|
|
66
292
|
contacts: contactsActionConfigSchema.optional(),
|
|
67
293
|
email: emailActionConfigSchema.optional(),
|
|
@@ -82,6 +308,19 @@ const defaultPublicationSecurity = {
|
|
|
82
308
|
repeatPolicy: "allow",
|
|
83
309
|
};
|
|
84
310
|
const answersSchema = z.record(z.string().min(1), answerValueSchema);
|
|
311
|
+
function normalizeDefinitionTags(tags) {
|
|
312
|
+
const seen = new Set();
|
|
313
|
+
const normalized = [];
|
|
314
|
+
for (const tag of tags) {
|
|
315
|
+
const value = tag.trim().replace(/\s+/g, " ").slice(0, 60);
|
|
316
|
+
const key = value.toLowerCase();
|
|
317
|
+
if (!value || seen.has(key))
|
|
318
|
+
continue;
|
|
319
|
+
seen.add(key);
|
|
320
|
+
normalized.push(value);
|
|
321
|
+
}
|
|
322
|
+
return normalized;
|
|
323
|
+
}
|
|
85
324
|
function slugify(value) {
|
|
86
325
|
const slug = value
|
|
87
326
|
.toLowerCase()
|
|
@@ -98,9 +337,14 @@ function summarizeDefinition(row) {
|
|
|
98
337
|
name: row.name,
|
|
99
338
|
slug: row.slug,
|
|
100
339
|
status: row.status,
|
|
340
|
+
tags: row.tags ?? [],
|
|
341
|
+
fields: row.fields,
|
|
342
|
+
actionConfig: row.actionConfig ?? {},
|
|
101
343
|
fieldCount: row.fields.length,
|
|
344
|
+
submissionCount: row.submissionCount ?? 0,
|
|
102
345
|
createdAt: row.createdAt,
|
|
103
346
|
updatedAt: row.updatedAt,
|
|
347
|
+
publicationId: row.publicationId ?? undefined,
|
|
104
348
|
publicationSlug: row.publicationSlug ?? undefined,
|
|
105
349
|
};
|
|
106
350
|
}
|
|
@@ -127,14 +371,20 @@ const listDefinitions = scopeAuthed.handler(async ({ context }) => {
|
|
|
127
371
|
name: captureDefinitions.name,
|
|
128
372
|
slug: captureDefinitions.slug,
|
|
129
373
|
status: captureDefinitions.status,
|
|
374
|
+
tags: captureDefinitions.tags,
|
|
130
375
|
fields: captureDefinitions.fields,
|
|
376
|
+
actionConfig: captureDefinitions.actionConfig,
|
|
131
377
|
createdAt: captureDefinitions.createdAt,
|
|
132
378
|
updatedAt: captureDefinitions.updatedAt,
|
|
379
|
+
publicationId: capturePublications.id,
|
|
133
380
|
publicationSlug: capturePublications.slug,
|
|
381
|
+
submissionCount: sql `count(${captureSubmissions.id})::int`,
|
|
134
382
|
})
|
|
135
383
|
.from(captureDefinitions)
|
|
136
384
|
.leftJoin(capturePublications, eq(capturePublications.definitionId, captureDefinitions.id))
|
|
385
|
+
.leftJoin(captureSubmissions, eq(captureSubmissions.definitionId, captureDefinitions.id))
|
|
137
386
|
.where(and(eq(captureDefinitions.scope, context.scopeType), eq(captureDefinitions.scopeId, context.scopeId)))
|
|
387
|
+
.groupBy(captureDefinitions.id, captureDefinitions.scope, captureDefinitions.scopeId, captureDefinitions.surface, captureDefinitions.name, captureDefinitions.slug, captureDefinitions.status, captureDefinitions.tags, captureDefinitions.fields, captureDefinitions.actionConfig, captureDefinitions.createdAt, captureDefinitions.updatedAt, capturePublications.id, capturePublications.slug)
|
|
138
388
|
.orderBy(desc(captureDefinitions.createdAt));
|
|
139
389
|
return rows.map(summarizeDefinition);
|
|
140
390
|
});
|
|
@@ -154,6 +404,7 @@ const createDefinition = scopeAuthed
|
|
|
154
404
|
slug,
|
|
155
405
|
status: "draft",
|
|
156
406
|
fields: [],
|
|
407
|
+
document: deriveCaptureDocumentFromFields([]),
|
|
157
408
|
actionConfig: {},
|
|
158
409
|
})
|
|
159
410
|
.returning();
|
|
@@ -162,6 +413,116 @@ const createDefinition = scopeAuthed
|
|
|
162
413
|
}
|
|
163
414
|
return row;
|
|
164
415
|
});
|
|
416
|
+
const resolveStorageImageUrl = scopeAuthed
|
|
417
|
+
.input(z.object({ itemId: z.string().min(1) }))
|
|
418
|
+
.handler(async ({ context, input }) => {
|
|
419
|
+
const result = await getHooks().doAction("storage:get-download-url", {
|
|
420
|
+
scope: context.scopeType,
|
|
421
|
+
scopeId: context.scopeId,
|
|
422
|
+
itemId: input.itemId,
|
|
423
|
+
});
|
|
424
|
+
if (!result?.url) {
|
|
425
|
+
throw new ORPCError("NOT_FOUND", { message: "Storage image not found" });
|
|
426
|
+
}
|
|
427
|
+
return { url: result.url };
|
|
428
|
+
});
|
|
429
|
+
const listItemViews = scopeAuthed.handler(async ({ context }) => {
|
|
430
|
+
const db = getDb();
|
|
431
|
+
return db
|
|
432
|
+
.select()
|
|
433
|
+
.from(captureItemViews)
|
|
434
|
+
.where(and(eq(captureItemViews.scope, context.scopeType), eq(captureItemViews.scopeId, context.scopeId)))
|
|
435
|
+
.orderBy(desc(captureItemViews.updatedAt));
|
|
436
|
+
});
|
|
437
|
+
const createItemView = scopeAuthed
|
|
438
|
+
.input(z.object({ name: z.string().min(1).max(80), icon: z.string().max(40).optional() }).merge(captureItemViewStateSchema))
|
|
439
|
+
.handler(async ({ context, input }) => {
|
|
440
|
+
const db = getDb();
|
|
441
|
+
const [row] = await db.insert(captureItemViews).values({
|
|
442
|
+
scope: context.scopeType,
|
|
443
|
+
scopeId: context.scopeId,
|
|
444
|
+
name: input.name,
|
|
445
|
+
icon: input.icon ?? "list",
|
|
446
|
+
filters: input.filters ?? null,
|
|
447
|
+
sorts: input.sorts ?? null,
|
|
448
|
+
columns: input.columns ?? null,
|
|
449
|
+
}).returning();
|
|
450
|
+
return row;
|
|
451
|
+
});
|
|
452
|
+
const updateItemView = scopeAuthed
|
|
453
|
+
.input(z.object({ id: z.string().min(1), name: z.string().min(1).max(80).optional(), icon: z.string().max(40).optional() }).merge(captureItemViewStateSchema.partial()))
|
|
454
|
+
.handler(async ({ context, input }) => {
|
|
455
|
+
const db = getDb();
|
|
456
|
+
const [row] = await db.update(captureItemViews).set({
|
|
457
|
+
...(input.name !== undefined ? { name: input.name } : {}),
|
|
458
|
+
...(input.icon !== undefined ? { icon: input.icon } : {}),
|
|
459
|
+
...(input.filters !== undefined ? { filters: input.filters } : {}),
|
|
460
|
+
...(input.sorts !== undefined ? { sorts: input.sorts } : {}),
|
|
461
|
+
...(input.columns !== undefined ? { columns: input.columns } : {}),
|
|
462
|
+
updatedAt: new Date(),
|
|
463
|
+
}).where(and(eq(captureItemViews.id, input.id), eq(captureItemViews.scope, context.scopeType), eq(captureItemViews.scopeId, context.scopeId))).returning();
|
|
464
|
+
if (!row)
|
|
465
|
+
throw new ORPCError("NOT_FOUND", { message: "Capture item view not found" });
|
|
466
|
+
return row;
|
|
467
|
+
});
|
|
468
|
+
const deleteItemView = scopeAuthed
|
|
469
|
+
.input(z.object({ id: z.string().min(1) }))
|
|
470
|
+
.handler(async ({ context, input }) => {
|
|
471
|
+
const db = getDb();
|
|
472
|
+
await db.delete(captureItemViews).where(and(eq(captureItemViews.id, input.id), eq(captureItemViews.scope, context.scopeType), eq(captureItemViews.scopeId, context.scopeId)));
|
|
473
|
+
return { ok: true };
|
|
474
|
+
});
|
|
475
|
+
const listSubmissionViews = scopeAuthed
|
|
476
|
+
.input(z.object({ definitionId: z.string().min(1) }))
|
|
477
|
+
.handler(async ({ context, input }) => {
|
|
478
|
+
await ensureDefinitionInScope({ id: input.definitionId, scope: context.scopeType, scopeId: context.scopeId });
|
|
479
|
+
const db = getDb();
|
|
480
|
+
return db
|
|
481
|
+
.select()
|
|
482
|
+
.from(captureSubmissionViews)
|
|
483
|
+
.where(and(eq(captureSubmissionViews.scope, context.scopeType), eq(captureSubmissionViews.scopeId, context.scopeId), eq(captureSubmissionViews.definitionId, input.definitionId)))
|
|
484
|
+
.orderBy(desc(captureSubmissionViews.updatedAt));
|
|
485
|
+
});
|
|
486
|
+
const createSubmissionView = scopeAuthed
|
|
487
|
+
.input(z.object({ definitionId: z.string().min(1), name: z.string().min(1).max(80), icon: z.string().max(40).optional() }).merge(captureSubmissionViewStateSchema))
|
|
488
|
+
.handler(async ({ context, input }) => {
|
|
489
|
+
await ensureDefinitionInScope({ id: input.definitionId, scope: context.scopeType, scopeId: context.scopeId });
|
|
490
|
+
const db = getDb();
|
|
491
|
+
const [row] = await db.insert(captureSubmissionViews).values({
|
|
492
|
+
scope: context.scopeType,
|
|
493
|
+
scopeId: context.scopeId,
|
|
494
|
+
definitionId: input.definitionId,
|
|
495
|
+
name: input.name,
|
|
496
|
+
icon: input.icon ?? "list",
|
|
497
|
+
filters: input.filters ?? null,
|
|
498
|
+
sorts: input.sorts ?? null,
|
|
499
|
+
columns: input.columns ?? null,
|
|
500
|
+
}).returning();
|
|
501
|
+
return row;
|
|
502
|
+
});
|
|
503
|
+
const updateSubmissionView = scopeAuthed
|
|
504
|
+
.input(z.object({ id: z.string().min(1), name: z.string().min(1).max(80).optional(), icon: z.string().max(40).optional() }).merge(captureSubmissionViewStateSchema.partial()))
|
|
505
|
+
.handler(async ({ context, input }) => {
|
|
506
|
+
const db = getDb();
|
|
507
|
+
const [row] = await db.update(captureSubmissionViews).set({
|
|
508
|
+
...(input.name !== undefined ? { name: input.name } : {}),
|
|
509
|
+
...(input.icon !== undefined ? { icon: input.icon } : {}),
|
|
510
|
+
...(input.filters !== undefined ? { filters: input.filters } : {}),
|
|
511
|
+
...(input.sorts !== undefined ? { sorts: input.sorts } : {}),
|
|
512
|
+
...(input.columns !== undefined ? { columns: input.columns } : {}),
|
|
513
|
+
updatedAt: new Date(),
|
|
514
|
+
}).where(and(eq(captureSubmissionViews.id, input.id), eq(captureSubmissionViews.scope, context.scopeType), eq(captureSubmissionViews.scopeId, context.scopeId))).returning();
|
|
515
|
+
if (!row)
|
|
516
|
+
throw new ORPCError("NOT_FOUND", { message: "Capture submission view not found" });
|
|
517
|
+
return row;
|
|
518
|
+
});
|
|
519
|
+
const deleteSubmissionView = scopeAuthed
|
|
520
|
+
.input(z.object({ id: z.string().min(1) }))
|
|
521
|
+
.handler(async ({ context, input }) => {
|
|
522
|
+
const db = getDb();
|
|
523
|
+
await db.delete(captureSubmissionViews).where(and(eq(captureSubmissionViews.id, input.id), eq(captureSubmissionViews.scope, context.scopeType), eq(captureSubmissionViews.scopeId, context.scopeId)));
|
|
524
|
+
return { ok: true };
|
|
525
|
+
});
|
|
165
526
|
const getDefinition = scopeAuthed
|
|
166
527
|
.input(z.object({ id: z.string().min(1) }))
|
|
167
528
|
.handler(async ({ context, input }) => {
|
|
@@ -176,21 +537,68 @@ const getDefinition = scopeAuthed
|
|
|
176
537
|
.from(capturePublications)
|
|
177
538
|
.where(eq(capturePublications.definitionId, definition.id))
|
|
178
539
|
.limit(1);
|
|
179
|
-
|
|
540
|
+
const document = resolveCaptureDocument(definition.fields, definition.document);
|
|
541
|
+
return {
|
|
542
|
+
...definition,
|
|
543
|
+
document: await resolveCaptureStorageImageUrls({
|
|
544
|
+
scope: definition.scope,
|
|
545
|
+
scopeId: definition.scopeId,
|
|
546
|
+
document,
|
|
547
|
+
}),
|
|
548
|
+
publicationId: publication?.id ?? null,
|
|
549
|
+
publicationSlug: publication?.slug ?? null,
|
|
550
|
+
};
|
|
551
|
+
});
|
|
552
|
+
const updateDefinitionTags = scopeAuthed
|
|
553
|
+
.input(z.object({ id: z.string().min(1), tags: z.array(z.string().min(1).max(60)).max(30) }))
|
|
554
|
+
.handler(async ({ context, input }) => {
|
|
555
|
+
await ensureDefinitionInScope({ id: input.id, scope: context.scopeType, scopeId: context.scopeId });
|
|
556
|
+
const tags = normalizeDefinitionTags(input.tags);
|
|
557
|
+
const db = getDb();
|
|
558
|
+
const [row] = await db
|
|
559
|
+
.update(captureDefinitions)
|
|
560
|
+
.set({ tags, updatedAt: new Date() })
|
|
561
|
+
.where(eq(captureDefinitions.id, input.id))
|
|
562
|
+
.returning();
|
|
563
|
+
if (!row)
|
|
564
|
+
throw new ORPCError("INTERNAL_SERVER_ERROR", { message: "Failed to update Capture item tags" });
|
|
565
|
+
return summarizeDefinition(row);
|
|
566
|
+
});
|
|
567
|
+
const updateDefinitionActionConfig = scopeAuthed
|
|
568
|
+
.input(z.object({ id: z.string().min(1), actionConfig: actionConfigSchema.default({}) }))
|
|
569
|
+
.handler(async ({ context, input }) => {
|
|
570
|
+
await ensureDefinitionInScope({ id: input.id, scope: context.scopeType, scopeId: context.scopeId });
|
|
571
|
+
const db = getDb();
|
|
572
|
+
const [row] = await db
|
|
573
|
+
.update(captureDefinitions)
|
|
574
|
+
.set({ actionConfig: input.actionConfig, updatedAt: new Date() })
|
|
575
|
+
.where(eq(captureDefinitions.id, input.id))
|
|
576
|
+
.returning();
|
|
577
|
+
if (!row)
|
|
578
|
+
throw new ORPCError("INTERNAL_SERVER_ERROR", { message: "Failed to update Capture item actions" });
|
|
579
|
+
return summarizeDefinition(row);
|
|
180
580
|
});
|
|
181
581
|
const updateDefinition = scopeAuthed
|
|
182
582
|
.input(z.object({
|
|
183
583
|
id: z.string().min(1),
|
|
184
584
|
name: z.string().min(1).max(160),
|
|
185
585
|
fields: z.array(fieldSchema).max(50),
|
|
586
|
+
document: formDocumentSchema.optional(),
|
|
186
587
|
actionConfig: actionConfigSchema.default({}),
|
|
187
588
|
}))
|
|
188
589
|
.handler(async ({ context, input }) => {
|
|
189
590
|
await ensureDefinitionInScope({ id: input.id, scope: context.scopeType, scopeId: context.scopeId });
|
|
591
|
+
const persistedDocument = normalizeCaptureDocumentForPersistence(input.fields, input.document);
|
|
190
592
|
const db = getDb();
|
|
191
593
|
const [row] = await db
|
|
192
594
|
.update(captureDefinitions)
|
|
193
|
-
.set({
|
|
595
|
+
.set({
|
|
596
|
+
name: input.name,
|
|
597
|
+
fields: persistedDocument.fields,
|
|
598
|
+
document: persistedDocument.document,
|
|
599
|
+
actionConfig: input.actionConfig,
|
|
600
|
+
updatedAt: new Date(),
|
|
601
|
+
})
|
|
194
602
|
.where(eq(captureDefinitions.id, input.id))
|
|
195
603
|
.returning();
|
|
196
604
|
if (!row) {
|
|
@@ -337,6 +745,41 @@ const listWorkflowDefinitions = scopeAuthed
|
|
|
337
745
|
throw error;
|
|
338
746
|
}
|
|
339
747
|
});
|
|
748
|
+
function parseContactCustomFieldRows(rows) {
|
|
749
|
+
if (!Array.isArray(rows))
|
|
750
|
+
return [];
|
|
751
|
+
return rows.flatMap((row) => {
|
|
752
|
+
const record = asRecord(row);
|
|
753
|
+
if (!record)
|
|
754
|
+
return [];
|
|
755
|
+
const id = typeof record.id === "string" ? record.id : null;
|
|
756
|
+
const key = typeof record.slug === "string" ? record.slug : null;
|
|
757
|
+
const label = typeof record.name === "string" ? record.name : key;
|
|
758
|
+
const type = typeof record.type === "string" ? record.type : "text";
|
|
759
|
+
if (!id || !key || !label)
|
|
760
|
+
return [];
|
|
761
|
+
return [{ id, key, label, type }];
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
const listContactCustomFields = scopeAuthed
|
|
765
|
+
.input(z.object({}))
|
|
766
|
+
.handler(async ({ context }) => {
|
|
767
|
+
try {
|
|
768
|
+
const rows = await getDb().execute(sql `
|
|
769
|
+
SELECT id, slug, name, type
|
|
770
|
+
FROM contact_custom_fields
|
|
771
|
+
WHERE scope = ${context.scopeType} AND "scopeId" = ${context.scopeId} AND "builtIn" = false
|
|
772
|
+
ORDER BY position ASC, name ASC
|
|
773
|
+
`);
|
|
774
|
+
return parseContactCustomFieldRows(rows);
|
|
775
|
+
}
|
|
776
|
+
catch (error) {
|
|
777
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
778
|
+
if (message.includes("contact_custom_fields"))
|
|
779
|
+
return [];
|
|
780
|
+
throw error;
|
|
781
|
+
}
|
|
782
|
+
});
|
|
340
783
|
const listSubmissions = scopeAuthed.handler(async ({ context }) => {
|
|
341
784
|
const db = getDb();
|
|
342
785
|
const rows = await db
|
|
@@ -582,7 +1025,18 @@ async function loadPublicForm(input, headers) {
|
|
|
582
1025
|
throw new ORPCError("NOT_FOUND", { message: "Form not found" });
|
|
583
1026
|
}
|
|
584
1027
|
enforceOrigin(headers, row.publication.security);
|
|
585
|
-
|
|
1028
|
+
const document = resolveCaptureDocument(row.definition.fields, row.definition.document);
|
|
1029
|
+
return {
|
|
1030
|
+
publication: row.publication,
|
|
1031
|
+
definition: {
|
|
1032
|
+
...row.definition,
|
|
1033
|
+
document: await resolveCaptureStorageImageUrls({
|
|
1034
|
+
scope: row.definition.scope,
|
|
1035
|
+
scopeId: row.definition.scopeId,
|
|
1036
|
+
document,
|
|
1037
|
+
}),
|
|
1038
|
+
},
|
|
1039
|
+
};
|
|
586
1040
|
}
|
|
587
1041
|
const getPublicForm = base
|
|
588
1042
|
.input(publicFormLookupSchema)
|
|
@@ -593,7 +1047,8 @@ const getPublicForm = base
|
|
|
593
1047
|
publicationId: row.publication.id,
|
|
594
1048
|
title: row.definition.name,
|
|
595
1049
|
slug: row.publication.slug,
|
|
596
|
-
fields: row.definition.
|
|
1050
|
+
fields: extractCaptureAnswerFields(row.definition.document),
|
|
1051
|
+
document: row.definition.document,
|
|
597
1052
|
security: {
|
|
598
1053
|
minSubmitSeconds: row.publication.security.minSubmitSeconds ?? defaultPublicationSecurity.minSubmitSeconds,
|
|
599
1054
|
honeypotFieldName: row.publication.security.honeypotFieldName ?? defaultPublicationSecurity.honeypotFieldName,
|
|
@@ -714,12 +1169,47 @@ async function executeConfiguredActionByType(args) {
|
|
|
714
1169
|
}
|
|
715
1170
|
throw new Error(`Unsupported action type: ${args.actionType}`);
|
|
716
1171
|
}
|
|
1172
|
+
const CAPTURE_SUBMISSION_TRIGGER_TYPE = "capture-submission-trigger";
|
|
1173
|
+
async function dispatchCaptureSubmissionWorkflowTriggers(context) {
|
|
1174
|
+
const input = buildWorkflowSubmissionPayload(context);
|
|
1175
|
+
const result = await getHooks().tryAction("workflows:trigger.dispatch", {
|
|
1176
|
+
scope: context.scope,
|
|
1177
|
+
scopeId: context.scopeId,
|
|
1178
|
+
triggerType: CAPTURE_SUBMISSION_TRIGGER_TYPE,
|
|
1179
|
+
input,
|
|
1180
|
+
match: {
|
|
1181
|
+
definitionId: context.definitionId,
|
|
1182
|
+
surface: context.definitionSurface,
|
|
1183
|
+
},
|
|
1184
|
+
idempotencyKey: `capture:${context.submissionId}:trigger:${CAPTURE_SUBMISSION_TRIGGER_TYPE}`,
|
|
1185
|
+
callerId: "capture",
|
|
1186
|
+
runTags: {
|
|
1187
|
+
event: "capture.submission.completed",
|
|
1188
|
+
captureDefinitionId: context.definitionId,
|
|
1189
|
+
captureSubmissionId: context.submissionId,
|
|
1190
|
+
},
|
|
1191
|
+
runMetadata: {
|
|
1192
|
+
captureDefinitionName: context.definitionName,
|
|
1193
|
+
},
|
|
1194
|
+
execute: true,
|
|
1195
|
+
});
|
|
1196
|
+
const started = result?.started ?? [];
|
|
1197
|
+
return {
|
|
1198
|
+
message: result == null
|
|
1199
|
+
? "Workflows tool is not enabled"
|
|
1200
|
+
: started.length === 0
|
|
1201
|
+
? "No matching workflow trigger found"
|
|
1202
|
+
: `Started ${started.length} workflow run${started.length === 1 ? "" : "s"}`,
|
|
1203
|
+
workflowRuns: started,
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
717
1206
|
async function runConfiguredActions(args) {
|
|
718
1207
|
const context = {
|
|
719
1208
|
scope: args.definition.scope,
|
|
720
1209
|
scopeId: args.definition.scopeId,
|
|
721
1210
|
definitionId: args.definition.id,
|
|
722
1211
|
definitionName: args.definition.name,
|
|
1212
|
+
definitionSurface: args.definition.surface,
|
|
723
1213
|
submissionId: args.submissionId,
|
|
724
1214
|
fields: args.definition.fields,
|
|
725
1215
|
answers: withOutcomeAnswers(args.answers, args.outcome),
|
|
@@ -747,15 +1237,11 @@ async function runConfiguredActions(args) {
|
|
|
747
1237
|
execute: () => emailActionAdapter.execute({ config: parsedConfig, context }),
|
|
748
1238
|
});
|
|
749
1239
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
actionType: workflowActionAdapter.type,
|
|
756
|
-
execute: () => workflowActionAdapter.execute({ config: parsedConfig, context }),
|
|
757
|
-
});
|
|
758
|
-
}
|
|
1240
|
+
await recordActionRun({
|
|
1241
|
+
context,
|
|
1242
|
+
actionType: "workflows.trigger.dispatch",
|
|
1243
|
+
execute: () => dispatchCaptureSubmissionWorkflowTriggers(context),
|
|
1244
|
+
});
|
|
759
1245
|
}
|
|
760
1246
|
async function enforcePublicSubmitGuards(args) {
|
|
761
1247
|
const security = { ...defaultPublicationSecurity, ...args.publication.security };
|
|
@@ -799,8 +1285,24 @@ const submitPublicForm = base
|
|
|
799
1285
|
honeypot: input.honeypot,
|
|
800
1286
|
});
|
|
801
1287
|
const normalizedAnswers = {};
|
|
802
|
-
const
|
|
803
|
-
|
|
1288
|
+
const answerFields = extractCaptureAnswerFields(definition.document);
|
|
1289
|
+
const visiblePageIds = new Set(definition.document.pages
|
|
1290
|
+
.filter((page) => !page.hidden && evaluateCaptureCondition(page.visibleWhen, answerFields, input.answers))
|
|
1291
|
+
.map((page) => page.id));
|
|
1292
|
+
const itemPageById = new Map(definition.document.items.map((item) => [item.id, item.pageId]));
|
|
1293
|
+
const visibleFields = getVisibleCaptureFields(answerFields, input.answers)
|
|
1294
|
+
.filter((field) => !field.hidden)
|
|
1295
|
+
.filter((field) => !isHiddenCaptureField(field))
|
|
1296
|
+
.filter((field) => visiblePageIds.has(itemPageById.get(field.id) ?? ""));
|
|
1297
|
+
const hiddenFields = answerFields.filter((field) => !field.hidden && isHiddenCaptureField(field));
|
|
1298
|
+
const submittedFields = [...new Map([...visibleFields, ...hiddenFields].map((field) => [field.id, field])).values()];
|
|
1299
|
+
for (const field of hiddenFields.filter(isHoneypotCaptureField)) {
|
|
1300
|
+
const value = normalizeCaptureAnswer(field, input.answers[field.key], REFERENCE_CAPTURE_FIELD_TYPES);
|
|
1301
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
1302
|
+
throw new ORPCError("BAD_REQUEST", { message: "Unable to submit form" });
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
for (const field of submittedFields.filter((field) => !isHoneypotCaptureField(field))) {
|
|
804
1306
|
const value = normalizeCaptureAnswer(field, input.answers[field.key], REFERENCE_CAPTURE_FIELD_TYPES);
|
|
805
1307
|
const validationError = validateCaptureAnswer(field, value, REFERENCE_CAPTURE_FIELD_TYPES);
|
|
806
1308
|
if (validationError) {
|
|
@@ -808,12 +1310,19 @@ const submitPublicForm = base
|
|
|
808
1310
|
}
|
|
809
1311
|
normalizedAnswers[field.key] = value;
|
|
810
1312
|
}
|
|
1313
|
+
for (const field of submittedFields.filter((field) => !isHoneypotCaptureField(field))) {
|
|
1314
|
+
const ruleError = validateCaptureFieldRules(field, answerFields, normalizedAnswers);
|
|
1315
|
+
if (ruleError) {
|
|
1316
|
+
throw new ORPCError("BAD_REQUEST", { message: ruleError });
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
811
1319
|
const outcome = input.source === "quiz" ? evaluateQuizOutcome(definition.actionConfig.quiz, normalizedAnswers) : undefined;
|
|
812
1320
|
const db = getDb();
|
|
813
1321
|
const snapshot = {
|
|
814
1322
|
definitionId: definition.id,
|
|
815
1323
|
title: definition.name,
|
|
816
|
-
fields:
|
|
1324
|
+
fields: answerFields,
|
|
1325
|
+
document: definition.document,
|
|
817
1326
|
answers: normalizedAnswers,
|
|
818
1327
|
...(outcome ? { outcome } : {}),
|
|
819
1328
|
};
|
|
@@ -858,7 +1367,7 @@ const submitPublicForm = base
|
|
|
858
1367
|
if (existingSubmission && repeatPolicy === "update") {
|
|
859
1368
|
await db.delete(captureAnswers).where(eq(captureAnswers.submissionId, submission.id));
|
|
860
1369
|
}
|
|
861
|
-
const answerRows =
|
|
1370
|
+
const answerRows = submittedFields.filter((field) => !isHoneypotCaptureField(field)).map((field) => ({
|
|
862
1371
|
scope: definition.scope,
|
|
863
1372
|
scopeId: definition.scopeId,
|
|
864
1373
|
submissionId: submission.id,
|
|
@@ -882,15 +1391,31 @@ const submitPublicForm = base
|
|
|
882
1391
|
const captureRouter = {
|
|
883
1392
|
definitions: {
|
|
884
1393
|
list: listDefinitions,
|
|
1394
|
+
views: {
|
|
1395
|
+
list: listItemViews,
|
|
1396
|
+
create: createItemView,
|
|
1397
|
+
update: updateItemView,
|
|
1398
|
+
delete: deleteItemView,
|
|
1399
|
+
},
|
|
885
1400
|
create: createDefinition,
|
|
886
1401
|
get: getDefinition,
|
|
887
1402
|
update: updateDefinition,
|
|
1403
|
+
updateTags: updateDefinitionTags,
|
|
1404
|
+
updateActionConfig: updateDefinitionActionConfig,
|
|
888
1405
|
publish: publishDefinition,
|
|
1406
|
+
resolveStorageImageUrl,
|
|
889
1407
|
updatePublication,
|
|
890
1408
|
listWorkflows: listWorkflowDefinitions,
|
|
1409
|
+
listContactCustomFields,
|
|
891
1410
|
},
|
|
892
1411
|
submissions: {
|
|
893
1412
|
list: listSubmissions,
|
|
1413
|
+
views: {
|
|
1414
|
+
list: listSubmissionViews,
|
|
1415
|
+
create: createSubmissionView,
|
|
1416
|
+
update: updateSubmissionView,
|
|
1417
|
+
delete: deleteSubmissionView,
|
|
1418
|
+
},
|
|
894
1419
|
get: getSubmission,
|
|
895
1420
|
retryAction: retryActionRun,
|
|
896
1421
|
},
|