payload-dynamic-forms 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +404 -2
- package/dist/access.d.ts +7 -0
- package/dist/access.js +10 -0
- package/dist/access.js.map +1 -0
- package/dist/collections/attachments.d.ts +6 -0
- package/dist/collections/attachments.js +67 -0
- package/dist/collections/attachments.js.map +1 -0
- package/dist/collections/blocks.d.ts +27 -0
- package/dist/collections/blocks.js +495 -0
- package/dist/collections/blocks.js.map +1 -0
- package/dist/collections/forms.d.ts +3 -0
- package/dist/collections/forms.js +553 -0
- package/dist/collections/forms.js.map +1 -0
- package/dist/collections/overrides.d.ts +7 -0
- package/dist/collections/overrides.js +49 -0
- package/dist/collections/overrides.js.map +1 -0
- package/dist/collections/ruleFields.d.ts +6 -0
- package/dist/collections/ruleFields.js +97 -0
- package/dist/collections/ruleFields.js.map +1 -0
- package/dist/collections/submissions.d.ts +7 -0
- package/dist/collections/submissions.js +225 -0
- package/dist/collections/submissions.js.map +1 -0
- package/dist/delivery/run.d.ts +36 -0
- package/dist/delivery/run.js +187 -0
- package/dist/delivery/run.js.map +1 -0
- package/dist/delivery/webhook.d.ts +26 -0
- package/dist/delivery/webhook.js +71 -0
- package/dist/delivery/webhook.js.map +1 -0
- package/dist/endpoints/public.d.ts +4 -0
- package/dist/endpoints/public.js +27 -0
- package/dist/endpoints/public.js.map +1 -0
- package/dist/endpoints/resend.d.ts +4 -0
- package/dist/endpoints/resend.js +58 -0
- package/dist/endpoints/resend.js.map +1 -0
- package/dist/endpoints/submit.d.ts +4 -0
- package/dist/endpoints/submit.js +320 -0
- package/dist/endpoints/submit.js.map +1 -0
- package/dist/exports/shared.d.ts +13 -0
- package/dist/exports/shared.js +13 -0
- package/dist/exports/shared.js.map +1 -0
- package/dist/formBlock.d.ts +13 -0
- package/dist/formBlock.js +25 -0
- package/dist/formBlock.js.map +1 -0
- package/dist/hooks/resendTick.d.ts +8 -0
- package/dist/hooks/resendTick.js +55 -0
- package/dist/hooks/resendTick.js.map +1 -0
- package/dist/hooks/validateForm.d.ts +8 -0
- package/dist/hooks/validateForm.js +251 -0
- package/dist/hooks/validateForm.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +124 -0
- package/dist/index.js.map +1 -0
- package/dist/security/crypto.d.ts +2 -0
- package/dist/security/crypto.js +17 -0
- package/dist/security/crypto.js.map +1 -0
- package/dist/security/rateLimit.d.ts +13 -0
- package/dist/security/rateLimit.js +28 -0
- package/dist/security/rateLimit.js.map +1 -0
- package/dist/security/request.d.ts +3 -0
- package/dist/security/request.js +13 -0
- package/dist/security/request.js.map +1 -0
- package/dist/security/turnstile.d.ts +28 -0
- package/dist/security/turnstile.js +56 -0
- package/dist/security/turnstile.js.map +1 -0
- package/dist/server/forms.d.ts +73 -0
- package/dist/server/forms.js +215 -0
- package/dist/server/forms.js.map +1 -0
- package/dist/server/resend.d.ts +27 -0
- package/dist/server/resend.js +84 -0
- package/dist/server/resend.js.map +1 -0
- package/dist/shared/answers.d.ts +47 -0
- package/dist/shared/answers.js +282 -0
- package/dist/shared/answers.js.map +1 -0
- package/dist/shared/operators.d.ts +41 -0
- package/dist/shared/operators.js +54 -0
- package/dist/shared/operators.js.map +1 -0
- package/dist/shared/rules.d.ts +58 -0
- package/dist/shared/rules.js +324 -0
- package/dist/shared/rules.js.map +1 -0
- package/dist/shared/steps.d.ts +28 -0
- package/dist/shared/steps.js +109 -0
- package/dist/shared/steps.js.map +1 -0
- package/dist/shared/tokens.d.ts +25 -0
- package/dist/shared/tokens.js +96 -0
- package/dist/shared/tokens.js.map +1 -0
- package/dist/shared/types.d.ts +181 -0
- package/dist/shared/types.js +5 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/shared/whatsapp.d.ts +20 -0
- package/dist/shared/whatsapp.js +26 -0
- package/dist/shared/whatsapp.js.map +1 -0
- package/dist/shared/words.d.ts +9 -0
- package/dist/shared/words.js +41 -0
- package/dist/shared/words.js.map +1 -0
- package/dist/types.d.ts +133 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +113 -4
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { MATCH_MODES, OPERATORS } from '../shared/operators.js';
|
|
2
|
+
/** One rule row: field key (or `key.attribute`), operator from the shared list, value. */ export function ruleRowFields() {
|
|
3
|
+
return [
|
|
4
|
+
{
|
|
5
|
+
type: 'row',
|
|
6
|
+
fields: [
|
|
7
|
+
{
|
|
8
|
+
name: 'fieldKey',
|
|
9
|
+
type: 'text',
|
|
10
|
+
admin: {
|
|
11
|
+
description: 'A field key, or key.attribute for an attribute of the chosen option.',
|
|
12
|
+
width: '40%'
|
|
13
|
+
},
|
|
14
|
+
required: true
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'operator',
|
|
18
|
+
type: 'select',
|
|
19
|
+
admin: {
|
|
20
|
+
width: '30%'
|
|
21
|
+
},
|
|
22
|
+
dbName: 'op',
|
|
23
|
+
defaultValue: 'equals',
|
|
24
|
+
// The one shared list: every rule field uses this same array.
|
|
25
|
+
options: OPERATORS,
|
|
26
|
+
required: true
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'value',
|
|
30
|
+
type: 'text',
|
|
31
|
+
admin: {
|
|
32
|
+
width: '30%'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
function matchField() {
|
|
40
|
+
return {
|
|
41
|
+
name: 'match',
|
|
42
|
+
type: 'radio',
|
|
43
|
+
dbName: 'mt',
|
|
44
|
+
defaultValue: 'all',
|
|
45
|
+
options: MATCH_MODES
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** A show/hide rule group (`all` or `any` of its rows; empty shows). */ export function ruleGroupField(name = 'visibilityRules', label = 'Show when') {
|
|
49
|
+
return {
|
|
50
|
+
name,
|
|
51
|
+
type: 'group',
|
|
52
|
+
admin: {
|
|
53
|
+
description: 'Leave empty to always show.'
|
|
54
|
+
},
|
|
55
|
+
fields: [
|
|
56
|
+
matchField(),
|
|
57
|
+
{
|
|
58
|
+
name: 'rows',
|
|
59
|
+
type: 'array',
|
|
60
|
+
admin: {
|
|
61
|
+
initCollapsed: true
|
|
62
|
+
},
|
|
63
|
+
fields: ruleRowFields()
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
label
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function nextStepRulesField() {
|
|
70
|
+
return {
|
|
71
|
+
name: 'nextStepRules',
|
|
72
|
+
type: 'array',
|
|
73
|
+
admin: {
|
|
74
|
+
condition: (data)=>data?.presentationMode === 'steps',
|
|
75
|
+
description: 'Step-by-step forms: the first matching rule picks the next section, else the next visible one.',
|
|
76
|
+
initCollapsed: true
|
|
77
|
+
},
|
|
78
|
+
fields: [
|
|
79
|
+
{
|
|
80
|
+
name: 'targetSectionKey',
|
|
81
|
+
type: 'text',
|
|
82
|
+
required: true
|
|
83
|
+
},
|
|
84
|
+
matchField(),
|
|
85
|
+
{
|
|
86
|
+
name: 'rows',
|
|
87
|
+
type: 'array',
|
|
88
|
+
admin: {
|
|
89
|
+
initCollapsed: true
|
|
90
|
+
},
|
|
91
|
+
fields: ruleRowFields()
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=ruleFields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/collections/ruleFields.ts"],"sourcesContent":["import type { Field } from 'payload'\n\nimport { MATCH_MODES, OPERATORS } from '../shared/operators.js'\n\n/** One rule row: field key (or `key.attribute`), operator from the shared list, value. */\nexport function ruleRowFields(): Field[] {\n return [\n {\n type: 'row',\n fields: [\n {\n name: 'fieldKey',\n type: 'text',\n admin: {\n description: 'A field key, or key.attribute for an attribute of the chosen option.',\n width: '40%',\n },\n required: true,\n },\n {\n name: 'operator',\n type: 'select',\n admin: { width: '30%' },\n dbName: 'op',\n defaultValue: 'equals',\n // The one shared list: every rule field uses this same array.\n options: OPERATORS as unknown as { label: string; value: string }[],\n required: true,\n },\n {\n name: 'value',\n type: 'text',\n admin: { width: '30%' },\n },\n ],\n },\n ]\n}\n\nfunction matchField(): Field {\n return {\n name: 'match',\n type: 'radio',\n dbName: 'mt',\n defaultValue: 'all',\n options: MATCH_MODES as unknown as { label: string; value: string }[],\n }\n}\n\n/** A show/hide rule group (`all` or `any` of its rows; empty shows). */\nexport function ruleGroupField(name = 'visibilityRules', label = 'Show when'): Field {\n return {\n name,\n type: 'group',\n admin: { description: 'Leave empty to always show.' },\n fields: [\n matchField(),\n {\n name: 'rows',\n type: 'array',\n admin: { initCollapsed: true },\n fields: ruleRowFields(),\n },\n ],\n label,\n }\n}\n\nexport function nextStepRulesField(): Field {\n return {\n name: 'nextStepRules',\n type: 'array',\n admin: {\n condition: (data) => data?.presentationMode === 'steps',\n description:\n 'Step-by-step forms: the first matching rule picks the next section, else the next visible one.',\n initCollapsed: true,\n },\n fields: [\n {\n name: 'targetSectionKey',\n type: 'text',\n required: true,\n },\n matchField(),\n {\n name: 'rows',\n type: 'array',\n admin: { initCollapsed: true },\n fields: ruleRowFields(),\n },\n ],\n }\n}\n"],"names":["MATCH_MODES","OPERATORS","ruleRowFields","type","fields","name","admin","description","width","required","dbName","defaultValue","options","matchField","ruleGroupField","label","initCollapsed","nextStepRulesField","condition","data","presentationMode"],"mappings":"AAEA,SAASA,WAAW,EAAEC,SAAS,QAAQ,yBAAwB;AAE/D,wFAAwF,GACxF,OAAO,SAASC;IACd,OAAO;QACL;YACEC,MAAM;YACNC,QAAQ;gBACN;oBACEC,MAAM;oBACNF,MAAM;oBACNG,OAAO;wBACLC,aAAa;wBACbC,OAAO;oBACT;oBACAC,UAAU;gBACZ;gBACA;oBACEJ,MAAM;oBACNF,MAAM;oBACNG,OAAO;wBAAEE,OAAO;oBAAM;oBACtBE,QAAQ;oBACRC,cAAc;oBACd,8DAA8D;oBAC9DC,SAASX;oBACTQ,UAAU;gBACZ;gBACA;oBACEJ,MAAM;oBACNF,MAAM;oBACNG,OAAO;wBAAEE,OAAO;oBAAM;gBACxB;aACD;QACH;KACD;AACH;AAEA,SAASK;IACP,OAAO;QACLR,MAAM;QACNF,MAAM;QACNO,QAAQ;QACRC,cAAc;QACdC,SAASZ;IACX;AACF;AAEA,sEAAsE,GACtE,OAAO,SAASc,eAAeT,OAAO,iBAAiB,EAAEU,QAAQ,WAAW;IAC1E,OAAO;QACLV;QACAF,MAAM;QACNG,OAAO;YAAEC,aAAa;QAA8B;QACpDH,QAAQ;YACNS;YACA;gBACER,MAAM;gBACNF,MAAM;gBACNG,OAAO;oBAAEU,eAAe;gBAAK;gBAC7BZ,QAAQF;YACV;SACD;QACDa;IACF;AACF;AAEA,OAAO,SAASE;IACd,OAAO;QACLZ,MAAM;QACNF,MAAM;QACNG,OAAO;YACLY,WAAW,CAACC,OAASA,MAAMC,qBAAqB;YAChDb,aACE;YACFS,eAAe;QACjB;QACAZ,QAAQ;YACN;gBACEC,MAAM;gBACNF,MAAM;gBACNM,UAAU;YACZ;YACAI;YACA;gBACER,MAAM;gBACNF,MAAM;gBACNG,OAAO;oBAAEU,eAAe;gBAAK;gBAC7BZ,QAAQF;YACV;SACD;IACH;AACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CollectionConfig, Endpoint } from 'payload';
|
|
2
|
+
import type { ResolvedOptions } from '../types.js';
|
|
3
|
+
export declare const SUBMISSION_STATUSES: {
|
|
4
|
+
label: string;
|
|
5
|
+
value: string;
|
|
6
|
+
}[];
|
|
7
|
+
export declare function submissionsCollection(options: ResolvedOptions, endpoints: Endpoint[]): CollectionConfig;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { adminAccess, loggedIn, nobody } from '../access.js';
|
|
2
|
+
import { resendTickHook } from '../hooks/resendTick.js';
|
|
3
|
+
export const SUBMISSION_STATUSES = [
|
|
4
|
+
{
|
|
5
|
+
label: 'Received',
|
|
6
|
+
value: 'received'
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
label: 'Processed',
|
|
10
|
+
value: 'processed'
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
label: 'Partly failed',
|
|
14
|
+
value: 'partlyFailed'
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
label: 'Failed',
|
|
18
|
+
value: 'failed'
|
|
19
|
+
}
|
|
20
|
+
];
|
|
21
|
+
export function submissionsCollection(options, endpoints) {
|
|
22
|
+
const admin = adminAccess(options.isAdmin);
|
|
23
|
+
return {
|
|
24
|
+
slug: options.slugs.submissions,
|
|
25
|
+
access: {
|
|
26
|
+
create: nobody,
|
|
27
|
+
delete: admin,
|
|
28
|
+
read: loggedIn,
|
|
29
|
+
update: admin
|
|
30
|
+
},
|
|
31
|
+
admin: {
|
|
32
|
+
defaultColumns: [
|
|
33
|
+
'id',
|
|
34
|
+
'form',
|
|
35
|
+
'status',
|
|
36
|
+
'createdAt'
|
|
37
|
+
],
|
|
38
|
+
group: 'Forms'
|
|
39
|
+
},
|
|
40
|
+
defaultSort: '-createdAt',
|
|
41
|
+
endpoints,
|
|
42
|
+
fields: [
|
|
43
|
+
{
|
|
44
|
+
name: 'form',
|
|
45
|
+
type: 'relationship',
|
|
46
|
+
relationTo: options.slugs.forms,
|
|
47
|
+
required: true
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'status',
|
|
51
|
+
type: 'select',
|
|
52
|
+
admin: {
|
|
53
|
+
position: 'sidebar'
|
|
54
|
+
},
|
|
55
|
+
dbName: 'sts',
|
|
56
|
+
defaultValue: 'received',
|
|
57
|
+
options: SUBMISSION_STATUSES,
|
|
58
|
+
required: true
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'resendFailed',
|
|
62
|
+
type: 'checkbox',
|
|
63
|
+
admin: {
|
|
64
|
+
description: 'Tick and save to run every failed email or webhook again.',
|
|
65
|
+
position: 'sidebar'
|
|
66
|
+
},
|
|
67
|
+
defaultValue: false,
|
|
68
|
+
label: 'Resend failed deliveries'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'answers',
|
|
72
|
+
type: 'json',
|
|
73
|
+
required: true
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'snapshot',
|
|
77
|
+
type: 'array',
|
|
78
|
+
admin: {
|
|
79
|
+
description: 'Each answer as worded when it was sent.',
|
|
80
|
+
readOnly: true
|
|
81
|
+
},
|
|
82
|
+
fields: [
|
|
83
|
+
{
|
|
84
|
+
name: 'sectionKey',
|
|
85
|
+
type: 'text'
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'sectionTitle',
|
|
89
|
+
type: 'text'
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'fieldKey',
|
|
93
|
+
type: 'text',
|
|
94
|
+
required: true
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'label',
|
|
98
|
+
type: 'text'
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'value',
|
|
102
|
+
type: 'textarea'
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
...options.attachments ? [
|
|
107
|
+
{
|
|
108
|
+
name: 'attachments',
|
|
109
|
+
type: 'relationship',
|
|
110
|
+
admin: {
|
|
111
|
+
readOnly: true
|
|
112
|
+
},
|
|
113
|
+
hasMany: true,
|
|
114
|
+
relationTo: options.attachments.collection
|
|
115
|
+
}
|
|
116
|
+
] : [],
|
|
117
|
+
{
|
|
118
|
+
name: 'deliveryLog',
|
|
119
|
+
type: 'array',
|
|
120
|
+
admin: {
|
|
121
|
+
initCollapsed: true,
|
|
122
|
+
readOnly: true
|
|
123
|
+
},
|
|
124
|
+
fields: [
|
|
125
|
+
{
|
|
126
|
+
name: 'kind',
|
|
127
|
+
type: 'select',
|
|
128
|
+
dbName: 'k',
|
|
129
|
+
options: [
|
|
130
|
+
{
|
|
131
|
+
label: 'Email',
|
|
132
|
+
value: 'email'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
label: 'Webhook',
|
|
136
|
+
value: 'webhook'
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
required: true
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'outputId',
|
|
143
|
+
type: 'text'
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'label',
|
|
147
|
+
type: 'text'
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'target',
|
|
151
|
+
type: 'text'
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: 'result',
|
|
155
|
+
type: 'select',
|
|
156
|
+
dbName: 'res',
|
|
157
|
+
options: [
|
|
158
|
+
{
|
|
159
|
+
label: 'Success',
|
|
160
|
+
value: 'success'
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
label: 'Failed',
|
|
164
|
+
value: 'failed'
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
label: 'Skipped',
|
|
168
|
+
value: 'skipped'
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
required: true
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'error',
|
|
175
|
+
type: 'textarea'
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'tries',
|
|
179
|
+
type: 'number'
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: 'at',
|
|
183
|
+
type: 'date',
|
|
184
|
+
admin: {
|
|
185
|
+
date: {
|
|
186
|
+
pickerAppearance: 'dayAndTime'
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: 'metadata',
|
|
194
|
+
type: 'group',
|
|
195
|
+
admin: {
|
|
196
|
+
readOnly: true
|
|
197
|
+
},
|
|
198
|
+
fields: [
|
|
199
|
+
{
|
|
200
|
+
name: 'ip',
|
|
201
|
+
type: 'text',
|
|
202
|
+
admin: {
|
|
203
|
+
description: 'Kept only when the plugin option metadata.ip is on.'
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'userAgent',
|
|
208
|
+
type: 'textarea'
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: 'locale',
|
|
212
|
+
type: 'text'
|
|
213
|
+
}
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
],
|
|
217
|
+
hooks: {
|
|
218
|
+
beforeChange: [
|
|
219
|
+
resendTickHook(options)
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
//# sourceMappingURL=submissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/collections/submissions.ts"],"sourcesContent":["import type { CollectionConfig, Endpoint } from 'payload'\n\nimport type { ResolvedOptions } from '../types.js'\n\nimport { adminAccess, loggedIn, nobody } from '../access.js'\nimport { resendTickHook } from '../hooks/resendTick.js'\n\nexport const SUBMISSION_STATUSES = [\n { label: 'Received', value: 'received' },\n { label: 'Processed', value: 'processed' },\n { label: 'Partly failed', value: 'partlyFailed' },\n { label: 'Failed', value: 'failed' },\n]\n\nexport function submissionsCollection(options: ResolvedOptions, endpoints: Endpoint[]): CollectionConfig {\n const admin = adminAccess(options.isAdmin)\n return {\n slug: options.slugs.submissions,\n access: {\n create: nobody,\n delete: admin,\n read: loggedIn,\n update: admin,\n },\n admin: {\n defaultColumns: ['id', 'form', 'status', 'createdAt'],\n group: 'Forms',\n },\n defaultSort: '-createdAt',\n endpoints,\n fields: [\n { name: 'form', type: 'relationship', relationTo: options.slugs.forms as never, required: true },\n {\n name: 'status',\n type: 'select',\n admin: { position: 'sidebar' },\n dbName: 'sts',\n defaultValue: 'received',\n options: SUBMISSION_STATUSES,\n required: true,\n },\n {\n name: 'resendFailed',\n type: 'checkbox',\n admin: { description: 'Tick and save to run every failed email or webhook again.', position: 'sidebar' },\n defaultValue: false,\n label: 'Resend failed deliveries',\n },\n { name: 'answers', type: 'json', required: true },\n {\n name: 'snapshot',\n type: 'array',\n admin: { description: 'Each answer as worded when it was sent.', readOnly: true },\n fields: [\n { name: 'sectionKey', type: 'text' },\n { name: 'sectionTitle', type: 'text' },\n { name: 'fieldKey', type: 'text', required: true },\n { name: 'label', type: 'text' },\n { name: 'value', type: 'textarea' },\n ],\n },\n ...(options.attachments\n ? [\n {\n name: 'attachments',\n type: 'relationship' as const,\n admin: { readOnly: true },\n hasMany: true,\n relationTo: options.attachments.collection as never,\n },\n ]\n : []),\n {\n name: 'deliveryLog',\n type: 'array',\n admin: { initCollapsed: true, readOnly: true },\n fields: [\n {\n name: 'kind',\n type: 'select',\n dbName: 'k',\n options: [\n { label: 'Email', value: 'email' },\n { label: 'Webhook', value: 'webhook' },\n ],\n required: true,\n },\n { name: 'outputId', type: 'text' },\n { name: 'label', type: 'text' },\n { name: 'target', type: 'text' },\n {\n name: 'result',\n type: 'select',\n dbName: 'res',\n options: [\n { label: 'Success', value: 'success' },\n { label: 'Failed', value: 'failed' },\n { label: 'Skipped', value: 'skipped' },\n ],\n required: true,\n },\n { name: 'error', type: 'textarea' },\n { name: 'tries', type: 'number' },\n { name: 'at', type: 'date', admin: { date: { pickerAppearance: 'dayAndTime' } } },\n ],\n },\n {\n name: 'metadata',\n type: 'group',\n admin: { readOnly: true },\n fields: [\n { name: 'ip', type: 'text', admin: { description: 'Kept only when the plugin option metadata.ip is on.' } },\n { name: 'userAgent', type: 'textarea' },\n { name: 'locale', type: 'text' },\n ],\n },\n ],\n hooks: {\n beforeChange: [resendTickHook(options)],\n },\n }\n}\n"],"names":["adminAccess","loggedIn","nobody","resendTickHook","SUBMISSION_STATUSES","label","value","submissionsCollection","options","endpoints","admin","isAdmin","slug","slugs","submissions","access","create","delete","read","update","defaultColumns","group","defaultSort","fields","name","type","relationTo","forms","required","position","dbName","defaultValue","description","readOnly","attachments","hasMany","collection","initCollapsed","date","pickerAppearance","hooks","beforeChange"],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Payload, PayloadRequest } from 'payload';
|
|
2
|
+
import type { FormDelivery } from '../server/forms.js';
|
|
3
|
+
import type { Answers, PublicForm, SnapshotRow, SubmissionStatus } from '../shared/types.js';
|
|
4
|
+
import type { ResolvedOptions } from '../types.js';
|
|
5
|
+
export type LogEntry = {
|
|
6
|
+
at: string;
|
|
7
|
+
error?: null | string;
|
|
8
|
+
kind: 'email' | 'webhook';
|
|
9
|
+
label?: null | string;
|
|
10
|
+
outputId?: null | string;
|
|
11
|
+
result: 'failed' | 'skipped' | 'success';
|
|
12
|
+
target?: null | string;
|
|
13
|
+
tries?: null | number;
|
|
14
|
+
};
|
|
15
|
+
export type DeliveryContext = {
|
|
16
|
+
answers: Answers;
|
|
17
|
+
attachments?: Array<{
|
|
18
|
+
fieldKey: string;
|
|
19
|
+
filename: string;
|
|
20
|
+
id: number | string;
|
|
21
|
+
}>;
|
|
22
|
+
delivery: FormDelivery;
|
|
23
|
+
form: PublicForm;
|
|
24
|
+
options: ResolvedOptions;
|
|
25
|
+
payload: Payload;
|
|
26
|
+
req: PayloadRequest;
|
|
27
|
+
snapshot: SnapshotRow[];
|
|
28
|
+
submissionId?: number | string;
|
|
29
|
+
};
|
|
30
|
+
/** Runs the enabled email and webhook outputs (or only `only`), each catching its own errors. */
|
|
31
|
+
export declare function runDeliveries(ctx: DeliveryContext, only?: Set<string>): Promise<LogEntry[]>;
|
|
32
|
+
export declare function hasDeliveries(delivery: FormDelivery): boolean;
|
|
33
|
+
/** The last entry of each output, ignoring outputs whose last entry is "skipped". */
|
|
34
|
+
export declare function lastResults(log: LogEntry[]): Map<string, LogEntry>;
|
|
35
|
+
/** processed: all succeeded or none ran; partlyFailed: some failed; failed: all failed. */
|
|
36
|
+
export declare function statusFromLog(log: LogEntry[]): SubmissionStatus;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { isValidEmail } from '../shared/answers.js';
|
|
2
|
+
import { buildMessageRows, buildTemplateValues, interpolate } from '../shared/tokens.js';
|
|
3
|
+
import { sendWebhook } from './webhook.js';
|
|
4
|
+
function addresses(value) {
|
|
5
|
+
const list = String(value || '').split(/[,;]/).map((v)=>v.trim()).filter(Boolean);
|
|
6
|
+
return list.length ? list.join(', ') : undefined;
|
|
7
|
+
}
|
|
8
|
+
function errorText(error) {
|
|
9
|
+
return error instanceof Error ? error.message : String(error);
|
|
10
|
+
}
|
|
11
|
+
async function sendOneEmail(ctx, output) {
|
|
12
|
+
const at = new Date().toISOString();
|
|
13
|
+
const base = {
|
|
14
|
+
at,
|
|
15
|
+
kind: 'email',
|
|
16
|
+
label: output.label || null,
|
|
17
|
+
outputId: output.id || null
|
|
18
|
+
};
|
|
19
|
+
let to;
|
|
20
|
+
if (output.kind === 'visitor') {
|
|
21
|
+
const answer = ctx.answers[String(output.visitorField || '')];
|
|
22
|
+
const address = typeof answer === 'string' ? answer.trim() : '';
|
|
23
|
+
if (!isValidEmail(address)) {
|
|
24
|
+
return {
|
|
25
|
+
...base,
|
|
26
|
+
error: `Visitor copy not sent: "${address}" is not one valid email address.`,
|
|
27
|
+
result: 'failed',
|
|
28
|
+
target: address || null,
|
|
29
|
+
tries: 0
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
to = address;
|
|
33
|
+
} else {
|
|
34
|
+
to = addresses(output.to);
|
|
35
|
+
}
|
|
36
|
+
if (!to) {
|
|
37
|
+
return {
|
|
38
|
+
...base,
|
|
39
|
+
error: 'No address to send to.',
|
|
40
|
+
result: 'failed',
|
|
41
|
+
target: null,
|
|
42
|
+
tries: 0
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const rows = buildMessageRows(ctx.form, ctx.answers, ctx.options.fieldTypes);
|
|
46
|
+
const values = buildTemplateValues(ctx.form, ctx.answers, {
|
|
47
|
+
formTitle: ctx.form.title,
|
|
48
|
+
submissionId: ctx.submissionId === undefined ? '' : String(ctx.submissionId)
|
|
49
|
+
}, ctx.options.fieldTypes);
|
|
50
|
+
const body = output.body || '{{*:table}}';
|
|
51
|
+
const replyKey = String(output.replyToField || '');
|
|
52
|
+
const replyTo = replyKey && isValidEmail(ctx.answers[replyKey]) ? String(ctx.answers[replyKey]).trim() : undefined;
|
|
53
|
+
let message = {
|
|
54
|
+
bcc: addresses(output.bcc),
|
|
55
|
+
cc: addresses(output.cc),
|
|
56
|
+
from: output.from?.trim() || undefined,
|
|
57
|
+
html: interpolate(body.split('\n').join('<br>\n'), values, {
|
|
58
|
+
html: true,
|
|
59
|
+
rows
|
|
60
|
+
}),
|
|
61
|
+
replyTo,
|
|
62
|
+
subject: interpolate(output.subject || 'New submission: {{formTitle}}', values, {
|
|
63
|
+
singleLine: true
|
|
64
|
+
}),
|
|
65
|
+
text: interpolate(body, values, {
|
|
66
|
+
rows
|
|
67
|
+
}),
|
|
68
|
+
to
|
|
69
|
+
};
|
|
70
|
+
for (const key of Object.keys(message)){
|
|
71
|
+
if (message[key] === undefined) {
|
|
72
|
+
delete message[key];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
if (ctx.options.beforeEmail) {
|
|
77
|
+
message = await ctx.options.beforeEmail(message, {
|
|
78
|
+
answers: ctx.answers,
|
|
79
|
+
form: ctx.form,
|
|
80
|
+
req: ctx.req
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
await ctx.payload.sendEmail(message);
|
|
84
|
+
return {
|
|
85
|
+
...base,
|
|
86
|
+
result: 'success',
|
|
87
|
+
target: to,
|
|
88
|
+
tries: 1
|
|
89
|
+
};
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return {
|
|
92
|
+
...base,
|
|
93
|
+
error: errorText(error),
|
|
94
|
+
result: 'failed',
|
|
95
|
+
target: to,
|
|
96
|
+
tries: 1
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function sendOneWebhook(ctx, output) {
|
|
101
|
+
const at = new Date().toISOString();
|
|
102
|
+
const body = output.payloadMode === 'answersOnly' ? {
|
|
103
|
+
answers: ctx.answers
|
|
104
|
+
} : {
|
|
105
|
+
answers: ctx.answers,
|
|
106
|
+
form: {
|
|
107
|
+
id: ctx.form.id,
|
|
108
|
+
slug: ctx.form.slug,
|
|
109
|
+
title: ctx.form.title
|
|
110
|
+
},
|
|
111
|
+
submission: {
|
|
112
|
+
id: ctx.submissionId ?? null,
|
|
113
|
+
attachments: ctx.attachments || [],
|
|
114
|
+
snapshot: ctx.snapshot
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const result = await sendWebhook(output, body);
|
|
118
|
+
return {
|
|
119
|
+
at,
|
|
120
|
+
error: result.ok ? null : result.error || 'Webhook failed.',
|
|
121
|
+
kind: 'webhook',
|
|
122
|
+
label: output.label || null,
|
|
123
|
+
outputId: output.id || null,
|
|
124
|
+
result: result.ok ? 'success' : 'failed',
|
|
125
|
+
target: output.url || null,
|
|
126
|
+
tries: result.tries
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** Runs the enabled email and webhook outputs (or only `only`), each catching its own errors. */ export async function runDeliveries(ctx, only) {
|
|
130
|
+
const entries = [];
|
|
131
|
+
for (const output of ctx.delivery.emails){
|
|
132
|
+
if (output.enabled === false || only && !only.has(String(output.id))) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
entries.push(await sendOneEmail(ctx, output));
|
|
136
|
+
}
|
|
137
|
+
for (const output of ctx.delivery.webhooks){
|
|
138
|
+
if (output.enabled === false || only && !only.has(String(output.id))) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
entries.push(await sendOneWebhook(ctx, output));
|
|
143
|
+
} catch (error) {
|
|
144
|
+
entries.push({
|
|
145
|
+
at: new Date().toISOString(),
|
|
146
|
+
error: errorText(error),
|
|
147
|
+
kind: 'webhook',
|
|
148
|
+
label: output.label || null,
|
|
149
|
+
outputId: output.id || null,
|
|
150
|
+
result: 'failed',
|
|
151
|
+
target: output.url || null,
|
|
152
|
+
tries: 0
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return entries;
|
|
157
|
+
}
|
|
158
|
+
export function hasDeliveries(delivery) {
|
|
159
|
+
return delivery.emails.some((e)=>e.enabled !== false) || delivery.webhooks.some((w)=>w.enabled !== false);
|
|
160
|
+
}
|
|
161
|
+
/** The last entry of each output, ignoring outputs whose last entry is "skipped". */ export function lastResults(log) {
|
|
162
|
+
const last = new Map();
|
|
163
|
+
log.forEach((entry, index)=>{
|
|
164
|
+
last.set(entry.outputId || `#${index}`, entry);
|
|
165
|
+
});
|
|
166
|
+
for (const [key, entry] of last){
|
|
167
|
+
if (entry.result === 'skipped') {
|
|
168
|
+
last.delete(key);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return last;
|
|
172
|
+
}
|
|
173
|
+
/** processed: all succeeded or none ran; partlyFailed: some failed; failed: all failed. */ export function statusFromLog(log) {
|
|
174
|
+
const results = [
|
|
175
|
+
...lastResults(log).values()
|
|
176
|
+
];
|
|
177
|
+
if (!results.length) {
|
|
178
|
+
return 'processed';
|
|
179
|
+
}
|
|
180
|
+
const ok = results.filter((r)=>r.result === 'success').length;
|
|
181
|
+
if (ok === results.length) {
|
|
182
|
+
return 'processed';
|
|
183
|
+
}
|
|
184
|
+
return ok === 0 ? 'failed' : 'partlyFailed';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/delivery/run.ts"],"sourcesContent":["import type { Payload, PayloadRequest, SendEmailOptions } from 'payload'\n\nimport type { EmailOutput, FormDelivery, WebhookOutput } from '../server/forms.js'\nimport type { Answers, PublicForm, SnapshotRow, SubmissionStatus } from '../shared/types.js'\nimport type { ResolvedOptions } from '../types.js'\n\nimport { isValidEmail } from '../shared/answers.js'\nimport { buildMessageRows, buildTemplateValues, interpolate } from '../shared/tokens.js'\nimport { sendWebhook } from './webhook.js'\n\nexport type LogEntry = {\n at: string\n error?: null | string\n kind: 'email' | 'webhook'\n label?: null | string\n outputId?: null | string\n result: 'failed' | 'skipped' | 'success'\n target?: null | string\n tries?: null | number\n}\n\nexport type DeliveryContext = {\n answers: Answers\n attachments?: Array<{ fieldKey: string; filename: string; id: number | string }>\n delivery: FormDelivery\n form: PublicForm\n options: ResolvedOptions\n payload: Payload\n req: PayloadRequest\n snapshot: SnapshotRow[]\n submissionId?: number | string\n}\n\nfunction addresses(value: null | string | undefined): string | undefined {\n const list = String(value || '')\n .split(/[,;]/)\n .map((v) => v.trim())\n .filter(Boolean)\n return list.length ? list.join(', ') : undefined\n}\n\nfunction errorText(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nasync function sendOneEmail(ctx: DeliveryContext, output: EmailOutput): Promise<LogEntry> {\n const at = new Date().toISOString()\n const base = { at, kind: 'email' as const, label: output.label || null, outputId: output.id || null }\n let to: string | undefined\n if (output.kind === 'visitor') {\n const answer = ctx.answers[String(output.visitorField || '')]\n const address = typeof answer === 'string' ? answer.trim() : ''\n if (!isValidEmail(address)) {\n return {\n ...base,\n error: `Visitor copy not sent: \"${address}\" is not one valid email address.`,\n result: 'failed',\n target: address || null,\n tries: 0,\n }\n }\n to = address\n } else {\n to = addresses(output.to)\n }\n if (!to) {\n return { ...base, error: 'No address to send to.', result: 'failed', target: null, tries: 0 }\n }\n const rows = buildMessageRows(ctx.form, ctx.answers, ctx.options.fieldTypes)\n const values = buildTemplateValues(\n ctx.form,\n ctx.answers,\n { formTitle: ctx.form.title, submissionId: ctx.submissionId === undefined ? '' : String(ctx.submissionId) },\n ctx.options.fieldTypes,\n )\n const body = output.body || '{{*:table}}'\n const replyKey = String(output.replyToField || '')\n const replyTo = replyKey && isValidEmail(ctx.answers[replyKey]) ? String(ctx.answers[replyKey]).trim() : undefined\n let message: SendEmailOptions = {\n bcc: addresses(output.bcc),\n cc: addresses(output.cc),\n from: output.from?.trim() || undefined,\n html: interpolate(body.split('\\n').join('<br>\\n'), values, { html: true, rows }),\n replyTo,\n subject: interpolate(output.subject || 'New submission: {{formTitle}}', values, { singleLine: true }),\n text: interpolate(body, values, { rows }),\n to,\n }\n for (const key of Object.keys(message) as Array<keyof SendEmailOptions>) {\n if (message[key] === undefined) {\n delete message[key]\n }\n }\n try {\n if (ctx.options.beforeEmail) {\n message = await ctx.options.beforeEmail(message, { answers: ctx.answers, form: ctx.form, req: ctx.req })\n }\n await ctx.payload.sendEmail(message)\n return { ...base, result: 'success', target: to, tries: 1 }\n } catch (error) {\n return { ...base, error: errorText(error), result: 'failed', target: to, tries: 1 }\n }\n}\n\nasync function sendOneWebhook(ctx: DeliveryContext, output: WebhookOutput): Promise<LogEntry> {\n const at = new Date().toISOString()\n const body =\n output.payloadMode === 'answersOnly'\n ? { answers: ctx.answers }\n : {\n answers: ctx.answers,\n form: { id: ctx.form.id, slug: ctx.form.slug, title: ctx.form.title },\n submission: {\n id: ctx.submissionId ?? null,\n attachments: ctx.attachments || [],\n snapshot: ctx.snapshot,\n },\n }\n const result = await sendWebhook(output, body)\n return {\n at,\n error: result.ok ? null : result.error || 'Webhook failed.',\n kind: 'webhook',\n label: output.label || null,\n outputId: output.id || null,\n result: result.ok ? 'success' : 'failed',\n target: output.url || null,\n tries: result.tries,\n }\n}\n\n/** Runs the enabled email and webhook outputs (or only `only`), each catching its own errors. */\nexport async function runDeliveries(ctx: DeliveryContext, only?: Set<string>): Promise<LogEntry[]> {\n const entries: LogEntry[] = []\n for (const output of ctx.delivery.emails) {\n if (output.enabled === false || (only && !only.has(String(output.id)))) {\n continue\n }\n entries.push(await sendOneEmail(ctx, output))\n }\n for (const output of ctx.delivery.webhooks) {\n if (output.enabled === false || (only && !only.has(String(output.id)))) {\n continue\n }\n try {\n entries.push(await sendOneWebhook(ctx, output))\n } catch (error) {\n entries.push({\n at: new Date().toISOString(),\n error: errorText(error),\n kind: 'webhook',\n label: output.label || null,\n outputId: output.id || null,\n result: 'failed',\n target: output.url || null,\n tries: 0,\n })\n }\n }\n return entries\n}\n\nexport function hasDeliveries(delivery: FormDelivery): boolean {\n return delivery.emails.some((e) => e.enabled !== false) || delivery.webhooks.some((w) => w.enabled !== false)\n}\n\n/** The last entry of each output, ignoring outputs whose last entry is \"skipped\". */\nexport function lastResults(log: LogEntry[]): Map<string, LogEntry> {\n const last = new Map<string, LogEntry>()\n log.forEach((entry, index) => {\n last.set(entry.outputId || `#${index}`, entry)\n })\n for (const [key, entry] of last) {\n if (entry.result === 'skipped') {\n last.delete(key)\n }\n }\n return last\n}\n\n/** processed: all succeeded or none ran; partlyFailed: some failed; failed: all failed. */\nexport function statusFromLog(log: LogEntry[]): SubmissionStatus {\n const results = [...lastResults(log).values()]\n if (!results.length) {\n return 'processed'\n }\n const ok = results.filter((r) => r.result === 'success').length\n if (ok === results.length) {\n return 'processed'\n }\n return ok === 0 ? 'failed' : 'partlyFailed'\n}\n"],"names":["isValidEmail","buildMessageRows","buildTemplateValues","interpolate","sendWebhook","addresses","value","list","String","split","map","v","trim","filter","Boolean","length","join","undefined","errorText","error","Error","message","sendOneEmail","ctx","output","at","Date","toISOString","base","kind","label","outputId","id","to","answer","answers","visitorField","address","result","target","tries","rows","form","options","fieldTypes","values","formTitle","title","submissionId","body","replyKey","replyToField","replyTo","bcc","cc","from","html","subject","singleLine","text","key","Object","keys","beforeEmail","req","payload","sendEmail","sendOneWebhook","payloadMode","slug","submission","attachments","snapshot","ok","url","runDeliveries","only","entries","delivery","emails","enabled","has","push","webhooks","hasDeliveries","some","e","w","lastResults","log","last","Map","forEach","entry","index","set","delete","statusFromLog","results","r"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type WebhookSettings = {
|
|
2
|
+
headers?: Array<{
|
|
3
|
+
key?: null | string;
|
|
4
|
+
value?: null | string;
|
|
5
|
+
}> | null;
|
|
6
|
+
method?: 'PATCH' | 'POST' | 'PUT' | null;
|
|
7
|
+
retries?: null | number;
|
|
8
|
+
signingSecret?: null | string;
|
|
9
|
+
timeoutMs?: null | number;
|
|
10
|
+
url?: null | string;
|
|
11
|
+
};
|
|
12
|
+
export type WebhookResult = {
|
|
13
|
+
error?: string;
|
|
14
|
+
ok: boolean;
|
|
15
|
+
status?: number;
|
|
16
|
+
tries: number;
|
|
17
|
+
};
|
|
18
|
+
/** Pauses between tries: 0.5 s, 1 s, 2 s. */
|
|
19
|
+
export declare const RETRY_PAUSES_MS: number[];
|
|
20
|
+
/** `X-Form-Signature` value: `t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">`. */
|
|
21
|
+
export declare function signWebhookBody(secret: string, body: string, now?: number): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Sends one webhook: JSON body, secret headers, optional signature, a timeout per try (default
|
|
24
|
+
* 8 s) and up to 3 retries with growing pauses. A non-2xx answer or a timeout is a failed try.
|
|
25
|
+
*/
|
|
26
|
+
export declare function sendWebhook(settings: WebhookSettings, body: unknown): Promise<WebhookResult>;
|