@ydtb/tk-scope-capture 0.22.0 → 0.23.1
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 +1855 -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 +151 -27
- 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 +849 -8
- 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 +875 -9
- package/dist/src/server/api/router.d.ts.map +1 -1
- package/dist/src/server/api/router.js +544 -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 +89 -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 +9 -6
|
@@ -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,7 +337,11 @@ 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,
|
|
104
347
|
publicationSlug: row.publicationSlug ?? undefined,
|
|
@@ -127,14 +370,19 @@ const listDefinitions = scopeAuthed.handler(async ({ context }) => {
|
|
|
127
370
|
name: captureDefinitions.name,
|
|
128
371
|
slug: captureDefinitions.slug,
|
|
129
372
|
status: captureDefinitions.status,
|
|
373
|
+
tags: captureDefinitions.tags,
|
|
130
374
|
fields: captureDefinitions.fields,
|
|
375
|
+
actionConfig: captureDefinitions.actionConfig,
|
|
131
376
|
createdAt: captureDefinitions.createdAt,
|
|
132
377
|
updatedAt: captureDefinitions.updatedAt,
|
|
133
378
|
publicationSlug: capturePublications.slug,
|
|
379
|
+
submissionCount: sql `count(${captureSubmissions.id})::int`,
|
|
134
380
|
})
|
|
135
381
|
.from(captureDefinitions)
|
|
136
382
|
.leftJoin(capturePublications, eq(capturePublications.definitionId, captureDefinitions.id))
|
|
383
|
+
.leftJoin(captureSubmissions, eq(captureSubmissions.definitionId, captureDefinitions.id))
|
|
137
384
|
.where(and(eq(captureDefinitions.scope, context.scopeType), eq(captureDefinitions.scopeId, context.scopeId)))
|
|
385
|
+
.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.slug)
|
|
138
386
|
.orderBy(desc(captureDefinitions.createdAt));
|
|
139
387
|
return rows.map(summarizeDefinition);
|
|
140
388
|
});
|
|
@@ -154,6 +402,7 @@ const createDefinition = scopeAuthed
|
|
|
154
402
|
slug,
|
|
155
403
|
status: "draft",
|
|
156
404
|
fields: [],
|
|
405
|
+
document: deriveCaptureDocumentFromFields([]),
|
|
157
406
|
actionConfig: {},
|
|
158
407
|
})
|
|
159
408
|
.returning();
|
|
@@ -162,6 +411,116 @@ const createDefinition = scopeAuthed
|
|
|
162
411
|
}
|
|
163
412
|
return row;
|
|
164
413
|
});
|
|
414
|
+
const resolveStorageImageUrl = scopeAuthed
|
|
415
|
+
.input(z.object({ itemId: z.string().min(1) }))
|
|
416
|
+
.handler(async ({ context, input }) => {
|
|
417
|
+
const result = await getHooks().doAction("storage:get-download-url", {
|
|
418
|
+
scope: context.scopeType,
|
|
419
|
+
scopeId: context.scopeId,
|
|
420
|
+
itemId: input.itemId,
|
|
421
|
+
});
|
|
422
|
+
if (!result?.url) {
|
|
423
|
+
throw new ORPCError("NOT_FOUND", { message: "Storage image not found" });
|
|
424
|
+
}
|
|
425
|
+
return { url: result.url };
|
|
426
|
+
});
|
|
427
|
+
const listItemViews = scopeAuthed.handler(async ({ context }) => {
|
|
428
|
+
const db = getDb();
|
|
429
|
+
return db
|
|
430
|
+
.select()
|
|
431
|
+
.from(captureItemViews)
|
|
432
|
+
.where(and(eq(captureItemViews.scope, context.scopeType), eq(captureItemViews.scopeId, context.scopeId)))
|
|
433
|
+
.orderBy(desc(captureItemViews.updatedAt));
|
|
434
|
+
});
|
|
435
|
+
const createItemView = scopeAuthed
|
|
436
|
+
.input(z.object({ name: z.string().min(1).max(80), icon: z.string().max(40).optional() }).merge(captureItemViewStateSchema))
|
|
437
|
+
.handler(async ({ context, input }) => {
|
|
438
|
+
const db = getDb();
|
|
439
|
+
const [row] = await db.insert(captureItemViews).values({
|
|
440
|
+
scope: context.scopeType,
|
|
441
|
+
scopeId: context.scopeId,
|
|
442
|
+
name: input.name,
|
|
443
|
+
icon: input.icon ?? "list",
|
|
444
|
+
filters: input.filters ?? null,
|
|
445
|
+
sorts: input.sorts ?? null,
|
|
446
|
+
columns: input.columns ?? null,
|
|
447
|
+
}).returning();
|
|
448
|
+
return row;
|
|
449
|
+
});
|
|
450
|
+
const updateItemView = scopeAuthed
|
|
451
|
+
.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()))
|
|
452
|
+
.handler(async ({ context, input }) => {
|
|
453
|
+
const db = getDb();
|
|
454
|
+
const [row] = await db.update(captureItemViews).set({
|
|
455
|
+
...(input.name !== undefined ? { name: input.name } : {}),
|
|
456
|
+
...(input.icon !== undefined ? { icon: input.icon } : {}),
|
|
457
|
+
...(input.filters !== undefined ? { filters: input.filters } : {}),
|
|
458
|
+
...(input.sorts !== undefined ? { sorts: input.sorts } : {}),
|
|
459
|
+
...(input.columns !== undefined ? { columns: input.columns } : {}),
|
|
460
|
+
updatedAt: new Date(),
|
|
461
|
+
}).where(and(eq(captureItemViews.id, input.id), eq(captureItemViews.scope, context.scopeType), eq(captureItemViews.scopeId, context.scopeId))).returning();
|
|
462
|
+
if (!row)
|
|
463
|
+
throw new ORPCError("NOT_FOUND", { message: "Capture item view not found" });
|
|
464
|
+
return row;
|
|
465
|
+
});
|
|
466
|
+
const deleteItemView = scopeAuthed
|
|
467
|
+
.input(z.object({ id: z.string().min(1) }))
|
|
468
|
+
.handler(async ({ context, input }) => {
|
|
469
|
+
const db = getDb();
|
|
470
|
+
await db.delete(captureItemViews).where(and(eq(captureItemViews.id, input.id), eq(captureItemViews.scope, context.scopeType), eq(captureItemViews.scopeId, context.scopeId)));
|
|
471
|
+
return { ok: true };
|
|
472
|
+
});
|
|
473
|
+
const listSubmissionViews = scopeAuthed
|
|
474
|
+
.input(z.object({ definitionId: z.string().min(1) }))
|
|
475
|
+
.handler(async ({ context, input }) => {
|
|
476
|
+
await ensureDefinitionInScope({ id: input.definitionId, scope: context.scopeType, scopeId: context.scopeId });
|
|
477
|
+
const db = getDb();
|
|
478
|
+
return db
|
|
479
|
+
.select()
|
|
480
|
+
.from(captureSubmissionViews)
|
|
481
|
+
.where(and(eq(captureSubmissionViews.scope, context.scopeType), eq(captureSubmissionViews.scopeId, context.scopeId), eq(captureSubmissionViews.definitionId, input.definitionId)))
|
|
482
|
+
.orderBy(desc(captureSubmissionViews.updatedAt));
|
|
483
|
+
});
|
|
484
|
+
const createSubmissionView = scopeAuthed
|
|
485
|
+
.input(z.object({ definitionId: z.string().min(1), name: z.string().min(1).max(80), icon: z.string().max(40).optional() }).merge(captureSubmissionViewStateSchema))
|
|
486
|
+
.handler(async ({ context, input }) => {
|
|
487
|
+
await ensureDefinitionInScope({ id: input.definitionId, scope: context.scopeType, scopeId: context.scopeId });
|
|
488
|
+
const db = getDb();
|
|
489
|
+
const [row] = await db.insert(captureSubmissionViews).values({
|
|
490
|
+
scope: context.scopeType,
|
|
491
|
+
scopeId: context.scopeId,
|
|
492
|
+
definitionId: input.definitionId,
|
|
493
|
+
name: input.name,
|
|
494
|
+
icon: input.icon ?? "list",
|
|
495
|
+
filters: input.filters ?? null,
|
|
496
|
+
sorts: input.sorts ?? null,
|
|
497
|
+
columns: input.columns ?? null,
|
|
498
|
+
}).returning();
|
|
499
|
+
return row;
|
|
500
|
+
});
|
|
501
|
+
const updateSubmissionView = scopeAuthed
|
|
502
|
+
.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()))
|
|
503
|
+
.handler(async ({ context, input }) => {
|
|
504
|
+
const db = getDb();
|
|
505
|
+
const [row] = await db.update(captureSubmissionViews).set({
|
|
506
|
+
...(input.name !== undefined ? { name: input.name } : {}),
|
|
507
|
+
...(input.icon !== undefined ? { icon: input.icon } : {}),
|
|
508
|
+
...(input.filters !== undefined ? { filters: input.filters } : {}),
|
|
509
|
+
...(input.sorts !== undefined ? { sorts: input.sorts } : {}),
|
|
510
|
+
...(input.columns !== undefined ? { columns: input.columns } : {}),
|
|
511
|
+
updatedAt: new Date(),
|
|
512
|
+
}).where(and(eq(captureSubmissionViews.id, input.id), eq(captureSubmissionViews.scope, context.scopeType), eq(captureSubmissionViews.scopeId, context.scopeId))).returning();
|
|
513
|
+
if (!row)
|
|
514
|
+
throw new ORPCError("NOT_FOUND", { message: "Capture submission view not found" });
|
|
515
|
+
return row;
|
|
516
|
+
});
|
|
517
|
+
const deleteSubmissionView = scopeAuthed
|
|
518
|
+
.input(z.object({ id: z.string().min(1) }))
|
|
519
|
+
.handler(async ({ context, input }) => {
|
|
520
|
+
const db = getDb();
|
|
521
|
+
await db.delete(captureSubmissionViews).where(and(eq(captureSubmissionViews.id, input.id), eq(captureSubmissionViews.scope, context.scopeType), eq(captureSubmissionViews.scopeId, context.scopeId)));
|
|
522
|
+
return { ok: true };
|
|
523
|
+
});
|
|
165
524
|
const getDefinition = scopeAuthed
|
|
166
525
|
.input(z.object({ id: z.string().min(1) }))
|
|
167
526
|
.handler(async ({ context, input }) => {
|
|
@@ -176,21 +535,67 @@ const getDefinition = scopeAuthed
|
|
|
176
535
|
.from(capturePublications)
|
|
177
536
|
.where(eq(capturePublications.definitionId, definition.id))
|
|
178
537
|
.limit(1);
|
|
179
|
-
|
|
538
|
+
const document = resolveCaptureDocument(definition.fields, definition.document);
|
|
539
|
+
return {
|
|
540
|
+
...definition,
|
|
541
|
+
document: await resolveCaptureStorageImageUrls({
|
|
542
|
+
scope: definition.scope,
|
|
543
|
+
scopeId: definition.scopeId,
|
|
544
|
+
document,
|
|
545
|
+
}),
|
|
546
|
+
publicationSlug: publication?.slug ?? null,
|
|
547
|
+
};
|
|
548
|
+
});
|
|
549
|
+
const updateDefinitionTags = scopeAuthed
|
|
550
|
+
.input(z.object({ id: z.string().min(1), tags: z.array(z.string().min(1).max(60)).max(30) }))
|
|
551
|
+
.handler(async ({ context, input }) => {
|
|
552
|
+
await ensureDefinitionInScope({ id: input.id, scope: context.scopeType, scopeId: context.scopeId });
|
|
553
|
+
const tags = normalizeDefinitionTags(input.tags);
|
|
554
|
+
const db = getDb();
|
|
555
|
+
const [row] = await db
|
|
556
|
+
.update(captureDefinitions)
|
|
557
|
+
.set({ tags, updatedAt: new Date() })
|
|
558
|
+
.where(eq(captureDefinitions.id, input.id))
|
|
559
|
+
.returning();
|
|
560
|
+
if (!row)
|
|
561
|
+
throw new ORPCError("INTERNAL_SERVER_ERROR", { message: "Failed to update Capture item tags" });
|
|
562
|
+
return summarizeDefinition(row);
|
|
563
|
+
});
|
|
564
|
+
const updateDefinitionActionConfig = scopeAuthed
|
|
565
|
+
.input(z.object({ id: z.string().min(1), actionConfig: actionConfigSchema.default({}) }))
|
|
566
|
+
.handler(async ({ context, input }) => {
|
|
567
|
+
await ensureDefinitionInScope({ id: input.id, scope: context.scopeType, scopeId: context.scopeId });
|
|
568
|
+
const db = getDb();
|
|
569
|
+
const [row] = await db
|
|
570
|
+
.update(captureDefinitions)
|
|
571
|
+
.set({ actionConfig: input.actionConfig, updatedAt: new Date() })
|
|
572
|
+
.where(eq(captureDefinitions.id, input.id))
|
|
573
|
+
.returning();
|
|
574
|
+
if (!row)
|
|
575
|
+
throw new ORPCError("INTERNAL_SERVER_ERROR", { message: "Failed to update Capture item actions" });
|
|
576
|
+
return summarizeDefinition(row);
|
|
180
577
|
});
|
|
181
578
|
const updateDefinition = scopeAuthed
|
|
182
579
|
.input(z.object({
|
|
183
580
|
id: z.string().min(1),
|
|
184
581
|
name: z.string().min(1).max(160),
|
|
185
582
|
fields: z.array(fieldSchema).max(50),
|
|
583
|
+
document: formDocumentSchema.optional(),
|
|
186
584
|
actionConfig: actionConfigSchema.default({}),
|
|
187
585
|
}))
|
|
188
586
|
.handler(async ({ context, input }) => {
|
|
189
587
|
await ensureDefinitionInScope({ id: input.id, scope: context.scopeType, scopeId: context.scopeId });
|
|
588
|
+
const persistedDocument = normalizeCaptureDocumentForPersistence(input.fields, input.document);
|
|
190
589
|
const db = getDb();
|
|
191
590
|
const [row] = await db
|
|
192
591
|
.update(captureDefinitions)
|
|
193
|
-
.set({
|
|
592
|
+
.set({
|
|
593
|
+
name: input.name,
|
|
594
|
+
fields: persistedDocument.fields,
|
|
595
|
+
document: persistedDocument.document,
|
|
596
|
+
actionConfig: input.actionConfig,
|
|
597
|
+
updatedAt: new Date(),
|
|
598
|
+
})
|
|
194
599
|
.where(eq(captureDefinitions.id, input.id))
|
|
195
600
|
.returning();
|
|
196
601
|
if (!row) {
|
|
@@ -337,6 +742,41 @@ const listWorkflowDefinitions = scopeAuthed
|
|
|
337
742
|
throw error;
|
|
338
743
|
}
|
|
339
744
|
});
|
|
745
|
+
function parseContactCustomFieldRows(rows) {
|
|
746
|
+
if (!Array.isArray(rows))
|
|
747
|
+
return [];
|
|
748
|
+
return rows.flatMap((row) => {
|
|
749
|
+
const record = asRecord(row);
|
|
750
|
+
if (!record)
|
|
751
|
+
return [];
|
|
752
|
+
const id = typeof record.id === "string" ? record.id : null;
|
|
753
|
+
const key = typeof record.slug === "string" ? record.slug : null;
|
|
754
|
+
const label = typeof record.name === "string" ? record.name : key;
|
|
755
|
+
const type = typeof record.type === "string" ? record.type : "text";
|
|
756
|
+
if (!id || !key || !label)
|
|
757
|
+
return [];
|
|
758
|
+
return [{ id, key, label, type }];
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
const listContactCustomFields = scopeAuthed
|
|
762
|
+
.input(z.object({}))
|
|
763
|
+
.handler(async ({ context }) => {
|
|
764
|
+
try {
|
|
765
|
+
const rows = await getDb().execute(sql `
|
|
766
|
+
SELECT id, slug, name, type
|
|
767
|
+
FROM contact_custom_fields
|
|
768
|
+
WHERE scope = ${context.scopeType} AND "scopeId" = ${context.scopeId} AND "builtIn" = false
|
|
769
|
+
ORDER BY position ASC, name ASC
|
|
770
|
+
`);
|
|
771
|
+
return parseContactCustomFieldRows(rows);
|
|
772
|
+
}
|
|
773
|
+
catch (error) {
|
|
774
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
775
|
+
if (message.includes("contact_custom_fields"))
|
|
776
|
+
return [];
|
|
777
|
+
throw error;
|
|
778
|
+
}
|
|
779
|
+
});
|
|
340
780
|
const listSubmissions = scopeAuthed.handler(async ({ context }) => {
|
|
341
781
|
const db = getDb();
|
|
342
782
|
const rows = await db
|
|
@@ -582,7 +1022,18 @@ async function loadPublicForm(input, headers) {
|
|
|
582
1022
|
throw new ORPCError("NOT_FOUND", { message: "Form not found" });
|
|
583
1023
|
}
|
|
584
1024
|
enforceOrigin(headers, row.publication.security);
|
|
585
|
-
|
|
1025
|
+
const document = resolveCaptureDocument(row.definition.fields, row.definition.document);
|
|
1026
|
+
return {
|
|
1027
|
+
publication: row.publication,
|
|
1028
|
+
definition: {
|
|
1029
|
+
...row.definition,
|
|
1030
|
+
document: await resolveCaptureStorageImageUrls({
|
|
1031
|
+
scope: row.definition.scope,
|
|
1032
|
+
scopeId: row.definition.scopeId,
|
|
1033
|
+
document,
|
|
1034
|
+
}),
|
|
1035
|
+
},
|
|
1036
|
+
};
|
|
586
1037
|
}
|
|
587
1038
|
const getPublicForm = base
|
|
588
1039
|
.input(publicFormLookupSchema)
|
|
@@ -593,7 +1044,8 @@ const getPublicForm = base
|
|
|
593
1044
|
publicationId: row.publication.id,
|
|
594
1045
|
title: row.definition.name,
|
|
595
1046
|
slug: row.publication.slug,
|
|
596
|
-
fields: row.definition.
|
|
1047
|
+
fields: extractCaptureAnswerFields(row.definition.document),
|
|
1048
|
+
document: row.definition.document,
|
|
597
1049
|
security: {
|
|
598
1050
|
minSubmitSeconds: row.publication.security.minSubmitSeconds ?? defaultPublicationSecurity.minSubmitSeconds,
|
|
599
1051
|
honeypotFieldName: row.publication.security.honeypotFieldName ?? defaultPublicationSecurity.honeypotFieldName,
|
|
@@ -714,12 +1166,47 @@ async function executeConfiguredActionByType(args) {
|
|
|
714
1166
|
}
|
|
715
1167
|
throw new Error(`Unsupported action type: ${args.actionType}`);
|
|
716
1168
|
}
|
|
1169
|
+
const CAPTURE_SUBMISSION_TRIGGER_TYPE = "capture-submission-trigger";
|
|
1170
|
+
async function dispatchCaptureSubmissionWorkflowTriggers(context) {
|
|
1171
|
+
const input = buildWorkflowSubmissionPayload(context);
|
|
1172
|
+
const result = await getHooks().tryAction("workflows:trigger.dispatch", {
|
|
1173
|
+
scope: context.scope,
|
|
1174
|
+
scopeId: context.scopeId,
|
|
1175
|
+
triggerType: CAPTURE_SUBMISSION_TRIGGER_TYPE,
|
|
1176
|
+
input,
|
|
1177
|
+
match: {
|
|
1178
|
+
definitionId: context.definitionId,
|
|
1179
|
+
surface: context.definitionSurface,
|
|
1180
|
+
},
|
|
1181
|
+
idempotencyKey: `capture:${context.submissionId}:trigger:${CAPTURE_SUBMISSION_TRIGGER_TYPE}`,
|
|
1182
|
+
callerId: "capture",
|
|
1183
|
+
runTags: {
|
|
1184
|
+
event: "capture.submission.completed",
|
|
1185
|
+
captureDefinitionId: context.definitionId,
|
|
1186
|
+
captureSubmissionId: context.submissionId,
|
|
1187
|
+
},
|
|
1188
|
+
runMetadata: {
|
|
1189
|
+
captureDefinitionName: context.definitionName,
|
|
1190
|
+
},
|
|
1191
|
+
execute: true,
|
|
1192
|
+
});
|
|
1193
|
+
const started = result?.started ?? [];
|
|
1194
|
+
return {
|
|
1195
|
+
message: result == null
|
|
1196
|
+
? "Workflows tool is not enabled"
|
|
1197
|
+
: started.length === 0
|
|
1198
|
+
? "No matching workflow trigger found"
|
|
1199
|
+
: `Started ${started.length} workflow run${started.length === 1 ? "" : "s"}`,
|
|
1200
|
+
workflowRuns: started,
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
717
1203
|
async function runConfiguredActions(args) {
|
|
718
1204
|
const context = {
|
|
719
1205
|
scope: args.definition.scope,
|
|
720
1206
|
scopeId: args.definition.scopeId,
|
|
721
1207
|
definitionId: args.definition.id,
|
|
722
1208
|
definitionName: args.definition.name,
|
|
1209
|
+
definitionSurface: args.definition.surface,
|
|
723
1210
|
submissionId: args.submissionId,
|
|
724
1211
|
fields: args.definition.fields,
|
|
725
1212
|
answers: withOutcomeAnswers(args.answers, args.outcome),
|
|
@@ -747,15 +1234,11 @@ async function runConfiguredActions(args) {
|
|
|
747
1234
|
execute: () => emailActionAdapter.execute({ config: parsedConfig, context }),
|
|
748
1235
|
});
|
|
749
1236
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
actionType: workflowActionAdapter.type,
|
|
756
|
-
execute: () => workflowActionAdapter.execute({ config: parsedConfig, context }),
|
|
757
|
-
});
|
|
758
|
-
}
|
|
1237
|
+
await recordActionRun({
|
|
1238
|
+
context,
|
|
1239
|
+
actionType: "workflows.trigger.dispatch",
|
|
1240
|
+
execute: () => dispatchCaptureSubmissionWorkflowTriggers(context),
|
|
1241
|
+
});
|
|
759
1242
|
}
|
|
760
1243
|
async function enforcePublicSubmitGuards(args) {
|
|
761
1244
|
const security = { ...defaultPublicationSecurity, ...args.publication.security };
|
|
@@ -799,8 +1282,24 @@ const submitPublicForm = base
|
|
|
799
1282
|
honeypot: input.honeypot,
|
|
800
1283
|
});
|
|
801
1284
|
const normalizedAnswers = {};
|
|
802
|
-
const
|
|
803
|
-
|
|
1285
|
+
const answerFields = extractCaptureAnswerFields(definition.document);
|
|
1286
|
+
const visiblePageIds = new Set(definition.document.pages
|
|
1287
|
+
.filter((page) => !page.hidden && evaluateCaptureCondition(page.visibleWhen, answerFields, input.answers))
|
|
1288
|
+
.map((page) => page.id));
|
|
1289
|
+
const itemPageById = new Map(definition.document.items.map((item) => [item.id, item.pageId]));
|
|
1290
|
+
const visibleFields = getVisibleCaptureFields(answerFields, input.answers)
|
|
1291
|
+
.filter((field) => !field.hidden)
|
|
1292
|
+
.filter((field) => !isHiddenCaptureField(field))
|
|
1293
|
+
.filter((field) => visiblePageIds.has(itemPageById.get(field.id) ?? ""));
|
|
1294
|
+
const hiddenFields = answerFields.filter((field) => !field.hidden && isHiddenCaptureField(field));
|
|
1295
|
+
const submittedFields = [...new Map([...visibleFields, ...hiddenFields].map((field) => [field.id, field])).values()];
|
|
1296
|
+
for (const field of hiddenFields.filter(isHoneypotCaptureField)) {
|
|
1297
|
+
const value = normalizeCaptureAnswer(field, input.answers[field.key], REFERENCE_CAPTURE_FIELD_TYPES);
|
|
1298
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
1299
|
+
throw new ORPCError("BAD_REQUEST", { message: "Unable to submit form" });
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
for (const field of submittedFields.filter((field) => !isHoneypotCaptureField(field))) {
|
|
804
1303
|
const value = normalizeCaptureAnswer(field, input.answers[field.key], REFERENCE_CAPTURE_FIELD_TYPES);
|
|
805
1304
|
const validationError = validateCaptureAnswer(field, value, REFERENCE_CAPTURE_FIELD_TYPES);
|
|
806
1305
|
if (validationError) {
|
|
@@ -808,12 +1307,19 @@ const submitPublicForm = base
|
|
|
808
1307
|
}
|
|
809
1308
|
normalizedAnswers[field.key] = value;
|
|
810
1309
|
}
|
|
1310
|
+
for (const field of submittedFields.filter((field) => !isHoneypotCaptureField(field))) {
|
|
1311
|
+
const ruleError = validateCaptureFieldRules(field, answerFields, normalizedAnswers);
|
|
1312
|
+
if (ruleError) {
|
|
1313
|
+
throw new ORPCError("BAD_REQUEST", { message: ruleError });
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
811
1316
|
const outcome = input.source === "quiz" ? evaluateQuizOutcome(definition.actionConfig.quiz, normalizedAnswers) : undefined;
|
|
812
1317
|
const db = getDb();
|
|
813
1318
|
const snapshot = {
|
|
814
1319
|
definitionId: definition.id,
|
|
815
1320
|
title: definition.name,
|
|
816
|
-
fields:
|
|
1321
|
+
fields: answerFields,
|
|
1322
|
+
document: definition.document,
|
|
817
1323
|
answers: normalizedAnswers,
|
|
818
1324
|
...(outcome ? { outcome } : {}),
|
|
819
1325
|
};
|
|
@@ -858,7 +1364,7 @@ const submitPublicForm = base
|
|
|
858
1364
|
if (existingSubmission && repeatPolicy === "update") {
|
|
859
1365
|
await db.delete(captureAnswers).where(eq(captureAnswers.submissionId, submission.id));
|
|
860
1366
|
}
|
|
861
|
-
const answerRows =
|
|
1367
|
+
const answerRows = submittedFields.filter((field) => !isHoneypotCaptureField(field)).map((field) => ({
|
|
862
1368
|
scope: definition.scope,
|
|
863
1369
|
scopeId: definition.scopeId,
|
|
864
1370
|
submissionId: submission.id,
|
|
@@ -882,15 +1388,31 @@ const submitPublicForm = base
|
|
|
882
1388
|
const captureRouter = {
|
|
883
1389
|
definitions: {
|
|
884
1390
|
list: listDefinitions,
|
|
1391
|
+
views: {
|
|
1392
|
+
list: listItemViews,
|
|
1393
|
+
create: createItemView,
|
|
1394
|
+
update: updateItemView,
|
|
1395
|
+
delete: deleteItemView,
|
|
1396
|
+
},
|
|
885
1397
|
create: createDefinition,
|
|
886
1398
|
get: getDefinition,
|
|
887
1399
|
update: updateDefinition,
|
|
1400
|
+
updateTags: updateDefinitionTags,
|
|
1401
|
+
updateActionConfig: updateDefinitionActionConfig,
|
|
888
1402
|
publish: publishDefinition,
|
|
1403
|
+
resolveStorageImageUrl,
|
|
889
1404
|
updatePublication,
|
|
890
1405
|
listWorkflows: listWorkflowDefinitions,
|
|
1406
|
+
listContactCustomFields,
|
|
891
1407
|
},
|
|
892
1408
|
submissions: {
|
|
893
1409
|
list: listSubmissions,
|
|
1410
|
+
views: {
|
|
1411
|
+
list: listSubmissionViews,
|
|
1412
|
+
create: createSubmissionView,
|
|
1413
|
+
update: updateSubmissionView,
|
|
1414
|
+
delete: deleteSubmissionView,
|
|
1415
|
+
},
|
|
894
1416
|
get: getSubmission,
|
|
895
1417
|
retryAction: retryActionRun,
|
|
896
1418
|
},
|