payload-dynamic-forms 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +461 -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 +558 -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 +37 -0
- package/dist/delivery/run.js +234 -0
- package/dist/delivery/run.js.map +1 -0
- package/dist/delivery/webhook.d.ts +41 -0
- package/dist/delivery/webhook.js +120 -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 +130 -0
- package/dist/index.js.map +1 -0
- package/dist/security/crypto.d.ts +9 -0
- package/dist/security/crypto.js +54 -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 +182 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +114 -4
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type WebhookSettings = {
|
|
2
|
+
/**
|
|
3
|
+
* Headers applied under the admin `headers` (e.g. resolved from the plugin config). A later
|
|
4
|
+
* header replaces an earlier one of the same name, in any case.
|
|
5
|
+
*/
|
|
6
|
+
configHeaders?: null | Record<string, string>;
|
|
7
|
+
/** The webhook's own (admin) secret headers. */
|
|
8
|
+
headers?: Array<{
|
|
9
|
+
key?: null | string;
|
|
10
|
+
value?: null | string;
|
|
11
|
+
}> | null;
|
|
12
|
+
method?: 'PATCH' | 'POST' | 'PUT' | null;
|
|
13
|
+
retries?: null | number;
|
|
14
|
+
/** The secret to sign with, already resolved. Empty: the request is sent unsigned. */
|
|
15
|
+
signingSecret?: null | string;
|
|
16
|
+
timeoutMs?: null | number;
|
|
17
|
+
url?: null | string;
|
|
18
|
+
/**
|
|
19
|
+
* The Standard Webhooks `webhook-id`, the same on every try. Missing: `msg_` plus a random id
|
|
20
|
+
* made once per call. Characters outside `[A-Za-z0-9_]` become `_`.
|
|
21
|
+
*/
|
|
22
|
+
webhookId?: null | string;
|
|
23
|
+
};
|
|
24
|
+
export type WebhookResult = {
|
|
25
|
+
error?: string;
|
|
26
|
+
ok: boolean;
|
|
27
|
+
status?: number;
|
|
28
|
+
tries: number;
|
|
29
|
+
};
|
|
30
|
+
/** Pauses between tries: 0.5 s, 1 s, 2 s. */
|
|
31
|
+
export declare const RETRY_PAUSES_MS: number[];
|
|
32
|
+
/** `X-Form-Signature` value: `t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">`. */
|
|
33
|
+
export declare function signWebhookBody(secret: string, body: string, now?: number): Promise<string>;
|
|
34
|
+
/** `msg_<submissionId>_<outputId>` when both exist, else `msg_` plus a random id; only `[A-Za-z0-9_]`. */
|
|
35
|
+
export declare function webhookMessageId(submissionId?: null | number | string, outputId?: null | string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Sends one webhook: JSON body, config headers under the secret headers, an optional signature
|
|
38
|
+
* (given resolved), a timeout per try (default 8 s) and up to 3 retries with growing pauses. A
|
|
39
|
+
* non-2xx answer or a timeout is a failed try.
|
|
40
|
+
*/
|
|
41
|
+
export declare function sendWebhook(settings: WebhookSettings, body: unknown): Promise<WebhookResult>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { hmacSha256Base64, hmacSha256Hex, standardWebhooksKey } from '../security/crypto.js';
|
|
2
|
+
/** Pauses between tries: 0.5 s, 1 s, 2 s. */ export const RETRY_PAUSES_MS = [
|
|
3
|
+
500,
|
|
4
|
+
1000,
|
|
5
|
+
2000
|
|
6
|
+
];
|
|
7
|
+
const sleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
8
|
+
/** `X-Form-Signature` value: `t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">`. */ export async function signWebhookBody(secret, body, now = Date.now()) {
|
|
9
|
+
const t = Math.floor(now / 1000);
|
|
10
|
+
return `t=${t},v1=${await hmacSha256Hex(secret, `${t}.${body}`)}`;
|
|
11
|
+
}
|
|
12
|
+
/** `msg_<submissionId>_<outputId>` when both exist, else `msg_` plus a random id; only `[A-Za-z0-9_]`. */ export function webhookMessageId(submissionId, outputId) {
|
|
13
|
+
const raw = submissionId !== undefined && submissionId !== null && submissionId !== '' && outputId ? `msg_${submissionId}_${outputId}` : `msg_${crypto.randomUUID().replace(/-/g, '')}`;
|
|
14
|
+
return raw.replace(/\W/g, '_');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Request headers in order, a later one replacing an earlier one whose name matches in any case:
|
|
18
|
+
* `Content-Type`, then each group in turn.
|
|
19
|
+
*/ function mergeHeaders(...groups) {
|
|
20
|
+
const merged = new Map([
|
|
21
|
+
[
|
|
22
|
+
'content-type',
|
|
23
|
+
[
|
|
24
|
+
'Content-Type',
|
|
25
|
+
'application/json'
|
|
26
|
+
]
|
|
27
|
+
]
|
|
28
|
+
]);
|
|
29
|
+
for (const group of groups){
|
|
30
|
+
for (const [key, value] of group){
|
|
31
|
+
merged.set(key.toLowerCase(), [
|
|
32
|
+
key,
|
|
33
|
+
value
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return Object.fromEntries(merged.values());
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Sends one webhook: JSON body, config headers under the secret headers, an optional signature
|
|
41
|
+
* (given resolved), a timeout per try (default 8 s) and up to 3 retries with growing pauses. A
|
|
42
|
+
* non-2xx answer or a timeout is a failed try.
|
|
43
|
+
*/ export async function sendWebhook(settings, body) {
|
|
44
|
+
const json = JSON.stringify(body);
|
|
45
|
+
const timeoutMs = Math.min(30_000, Math.max(500, Number(settings.timeoutMs) || 8000));
|
|
46
|
+
const retries = Math.min(3, Math.max(0, Math.floor(Number(settings.retries) || 0)));
|
|
47
|
+
const configHeaders = Object.entries(settings.configHeaders || {}).filter((entry)=>Boolean(entry[0]) && typeof entry[1] === 'string');
|
|
48
|
+
const adminHeaders = (settings.headers || []).flatMap((header)=>header?.key && header.value ? [
|
|
49
|
+
[
|
|
50
|
+
header.key,
|
|
51
|
+
header.value
|
|
52
|
+
]
|
|
53
|
+
] : []);
|
|
54
|
+
const secret = settings.signingSecret || '';
|
|
55
|
+
const webhookId = settings.webhookId ? settings.webhookId.replace(/\W/g, '_') : webhookMessageId();
|
|
56
|
+
const key = secret ? standardWebhooksKey(secret) : null;
|
|
57
|
+
let lastError = '';
|
|
58
|
+
let lastStatus;
|
|
59
|
+
let tries = 0;
|
|
60
|
+
for(let attempt = 0; attempt <= retries; attempt += 1){
|
|
61
|
+
if (attempt > 0) {
|
|
62
|
+
await sleep(RETRY_PAUSES_MS[Math.min(attempt - 1, RETRY_PAUSES_MS.length - 1)]);
|
|
63
|
+
}
|
|
64
|
+
tries += 1;
|
|
65
|
+
// Signature headers come last: neither config nor admin headers can replace them.
|
|
66
|
+
const signature = [
|
|
67
|
+
[
|
|
68
|
+
'webhook-id',
|
|
69
|
+
webhookId
|
|
70
|
+
]
|
|
71
|
+
];
|
|
72
|
+
if (secret && key) {
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
const t = Math.floor(now / 1000);
|
|
75
|
+
signature.push([
|
|
76
|
+
'X-Form-Signature',
|
|
77
|
+
await signWebhookBody(secret, json, now)
|
|
78
|
+
], [
|
|
79
|
+
'webhook-timestamp',
|
|
80
|
+
String(t)
|
|
81
|
+
], [
|
|
82
|
+
'webhook-signature',
|
|
83
|
+
`v1,${await hmacSha256Base64(key, `${webhookId}.${t}.${json}`)}`
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
const headers = mergeHeaders(configHeaders, adminHeaders, signature);
|
|
87
|
+
const controller = new AbortController();
|
|
88
|
+
const timer = setTimeout(()=>controller.abort(), timeoutMs);
|
|
89
|
+
try {
|
|
90
|
+
const response = await fetch(String(settings.url || ''), {
|
|
91
|
+
body: json,
|
|
92
|
+
headers,
|
|
93
|
+
method: settings.method || 'POST',
|
|
94
|
+
signal: controller.signal
|
|
95
|
+
});
|
|
96
|
+
lastStatus = response.status;
|
|
97
|
+
if (response.ok) {
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
status: response.status,
|
|
101
|
+
tries
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
lastError = `Webhook answered ${response.status}.`;
|
|
105
|
+
} catch (error) {
|
|
106
|
+
lastStatus = undefined;
|
|
107
|
+
lastError = controller.signal.aborted ? `Webhook timed out after ${timeoutMs} ms.` : error instanceof Error ? error.message : String(error);
|
|
108
|
+
} finally{
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
error: lastError,
|
|
114
|
+
ok: false,
|
|
115
|
+
status: lastStatus,
|
|
116
|
+
tries
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/delivery/webhook.ts"],"sourcesContent":["import { hmacSha256Base64, hmacSha256Hex, standardWebhooksKey } from '../security/crypto.js'\n\nexport type WebhookSettings = {\n /**\n * Headers applied under the admin `headers` (e.g. resolved from the plugin config). A later\n * header replaces an earlier one of the same name, in any case.\n */\n configHeaders?: null | Record<string, string>\n /** The webhook's own (admin) secret headers. */\n headers?: Array<{ key?: null | string; value?: null | string }> | null\n method?: 'PATCH' | 'POST' | 'PUT' | null\n retries?: null | number\n /** The secret to sign with, already resolved. Empty: the request is sent unsigned. */\n signingSecret?: null | string\n timeoutMs?: null | number\n url?: null | string\n /**\n * The Standard Webhooks `webhook-id`, the same on every try. Missing: `msg_` plus a random id\n * made once per call. Characters outside `[A-Za-z0-9_]` become `_`.\n */\n webhookId?: null | string\n}\n\nexport type WebhookResult = { error?: string; ok: boolean; status?: number; tries: number }\n\n/** Pauses between tries: 0.5 s, 1 s, 2 s. */\nexport const RETRY_PAUSES_MS = [500, 1000, 2000]\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))\n\n/** `X-Form-Signature` value: `t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<body>\">`. */\nexport async function signWebhookBody(secret: string, body: string, now = Date.now()): Promise<string> {\n const t = Math.floor(now / 1000)\n return `t=${t},v1=${await hmacSha256Hex(secret, `${t}.${body}`)}`\n}\n\n/** `msg_<submissionId>_<outputId>` when both exist, else `msg_` plus a random id; only `[A-Za-z0-9_]`. */\nexport function webhookMessageId(submissionId?: null | number | string, outputId?: null | string): string {\n const raw =\n submissionId !== undefined && submissionId !== null && submissionId !== '' && outputId\n ? `msg_${submissionId}_${outputId}`\n : `msg_${crypto.randomUUID().replace(/-/g, '')}`\n return raw.replace(/\\W/g, '_')\n}\n\n/**\n * Request headers in order, a later one replacing an earlier one whose name matches in any case:\n * `Content-Type`, then each group in turn.\n */\nfunction mergeHeaders(...groups: Array<Array<[string, string]>>): Record<string, string> {\n const merged = new Map<string, [string, string]>([['content-type', ['Content-Type', 'application/json']]])\n for (const group of groups) {\n for (const [key, value] of group) {\n merged.set(key.toLowerCase(), [key, value])\n }\n }\n return Object.fromEntries(merged.values())\n}\n\n/**\n * Sends one webhook: JSON body, config headers under the secret headers, an optional signature\n * (given resolved), a timeout per try (default 8 s) and up to 3 retries with growing pauses. A\n * non-2xx answer or a timeout is a failed try.\n */\nexport async function sendWebhook(settings: WebhookSettings, body: unknown): Promise<WebhookResult> {\n const json = JSON.stringify(body)\n const timeoutMs = Math.min(30_000, Math.max(500, Number(settings.timeoutMs) || 8000))\n const retries = Math.min(3, Math.max(0, Math.floor(Number(settings.retries) || 0)))\n const configHeaders = Object.entries(settings.configHeaders || {}).filter(\n (entry): entry is [string, string] => Boolean(entry[0]) && typeof entry[1] === 'string',\n )\n const adminHeaders = (settings.headers || []).flatMap((header): Array<[string, string]> =>\n header?.key && header.value ? [[header.key, header.value]] : [],\n )\n const secret = settings.signingSecret || ''\n const webhookId = settings.webhookId ? settings.webhookId.replace(/\\W/g, '_') : webhookMessageId()\n const key = secret ? standardWebhooksKey(secret) : null\n let lastError = ''\n let lastStatus: number | undefined\n let tries = 0\n for (let attempt = 0; attempt <= retries; attempt += 1) {\n if (attempt > 0) {\n await sleep(RETRY_PAUSES_MS[Math.min(attempt - 1, RETRY_PAUSES_MS.length - 1)])\n }\n tries += 1\n // Signature headers come last: neither config nor admin headers can replace them.\n const signature: Array<[string, string]> = [['webhook-id', webhookId]]\n if (secret && key) {\n const now = Date.now()\n const t = Math.floor(now / 1000)\n signature.push(\n ['X-Form-Signature', await signWebhookBody(secret, json, now)],\n ['webhook-timestamp', String(t)],\n ['webhook-signature', `v1,${await hmacSha256Base64(key, `${webhookId}.${t}.${json}`)}`],\n )\n }\n const headers = mergeHeaders(configHeaders, adminHeaders, signature)\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const response = await fetch(String(settings.url || ''), {\n body: json,\n headers,\n method: settings.method || 'POST',\n signal: controller.signal,\n })\n lastStatus = response.status\n if (response.ok) {\n return { ok: true, status: response.status, tries }\n }\n lastError = `Webhook answered ${response.status}.`\n } catch (error) {\n lastStatus = undefined\n lastError = controller.signal.aborted\n ? `Webhook timed out after ${timeoutMs} ms.`\n : error instanceof Error\n ? error.message\n : String(error)\n } finally {\n clearTimeout(timer)\n }\n }\n return { error: lastError, ok: false, status: lastStatus, tries }\n}\n"],"names":["hmacSha256Base64","hmacSha256Hex","standardWebhooksKey","RETRY_PAUSES_MS","sleep","ms","Promise","resolve","setTimeout","signWebhookBody","secret","body","now","Date","t","Math","floor","webhookMessageId","submissionId","outputId","raw","undefined","crypto","randomUUID","replace","mergeHeaders","groups","merged","Map","group","key","value","set","toLowerCase","Object","fromEntries","values","sendWebhook","settings","json","JSON","stringify","timeoutMs","min","max","Number","retries","configHeaders","entries","filter","entry","Boolean","adminHeaders","headers","flatMap","header","signingSecret","webhookId","lastError","lastStatus","tries","attempt","length","signature","push","String","controller","AbortController","timer","abort","response","fetch","url","method","signal","status","ok","error","aborted","Error","message","clearTimeout"],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload';
|
|
2
|
+
import type { ResolvedOptions } from '../types.js';
|
|
3
|
+
/** `GET /api/<forms>/public/:slug` — the published form, ready to render, without delivery settings. */
|
|
4
|
+
export declare function publicFormEndpoint(options: ResolvedOptions): Endpoint;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getPublicForm } from '../server/forms.js';
|
|
2
|
+
/** `GET /api/<forms>/public/:slug` — the published form, ready to render, without delivery settings. */ export function publicFormEndpoint(options) {
|
|
3
|
+
return {
|
|
4
|
+
handler: async (req)=>{
|
|
5
|
+
const slug = String(req.routeParams?.slug || '');
|
|
6
|
+
const form = slug ? await getPublicForm(req.payload, slug, {
|
|
7
|
+
locale: req.locale || undefined,
|
|
8
|
+
req
|
|
9
|
+
}) : null;
|
|
10
|
+
if (!form) {
|
|
11
|
+
return Response.json({
|
|
12
|
+
message: options.words.notFound,
|
|
13
|
+
ok: false
|
|
14
|
+
}, {
|
|
15
|
+
status: 404
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return Response.json(form, {
|
|
19
|
+
status: 200
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
method: 'get',
|
|
23
|
+
path: '/public/:slug'
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=public.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/public.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport type { ResolvedOptions } from '../types.js'\n\nimport { getPublicForm } from '../server/forms.js'\n\n/** `GET /api/<forms>/public/:slug` — the published form, ready to render, without delivery settings. */\nexport function publicFormEndpoint(options: ResolvedOptions): Endpoint {\n return {\n handler: async (req) => {\n const slug = String(req.routeParams?.slug || '')\n const form = slug ? await getPublicForm(req.payload, slug, { locale: (req.locale || undefined) as never, req }) : null\n if (!form) {\n return Response.json({ message: options.words.notFound, ok: false }, { status: 404 })\n }\n return Response.json(form, { status: 200 })\n },\n method: 'get',\n path: '/public/:slug',\n }\n}\n"],"names":["getPublicForm","publicFormEndpoint","options","handler","req","slug","String","routeParams","form","payload","locale","undefined","Response","json","message","words","notFound","ok","status","method","path"],"mappings":"AAIA,SAASA,aAAa,QAAQ,qBAAoB;AAElD,sGAAsG,GACtG,OAAO,SAASC,mBAAmBC,OAAwB;IACzD,OAAO;QACLC,SAAS,OAAOC;YACd,MAAMC,OAAOC,OAAOF,IAAIG,WAAW,EAAEF,QAAQ;YAC7C,MAAMG,OAAOH,OAAO,MAAML,cAAcI,IAAIK,OAAO,EAAEJ,MAAM;gBAAEK,QAASN,IAAIM,MAAM,IAAIC;gBAAqBP;YAAI,KAAK;YAClH,IAAI,CAACI,MAAM;gBACT,OAAOI,SAASC,IAAI,CAAC;oBAAEC,SAASZ,QAAQa,KAAK,CAACC,QAAQ;oBAAEC,IAAI;gBAAM,GAAG;oBAAEC,QAAQ;gBAAI;YACrF;YACA,OAAON,SAASC,IAAI,CAACL,MAAM;gBAAEU,QAAQ;YAAI;QAC3C;QACAC,QAAQ;QACRC,MAAM;IACR;AACF"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { RESEND_CONTEXT } from '../hooks/resendTick.js';
|
|
2
|
+
import { resendFailedDeliveries } from '../server/resend.js';
|
|
3
|
+
/** `POST /api/<submissions>/:id/resend` — admins only: runs the failed outputs again. */ export function resendEndpoint(options) {
|
|
4
|
+
return {
|
|
5
|
+
handler: async (req)=>{
|
|
6
|
+
if (!req.user || !options.isAdmin(req)) {
|
|
7
|
+
return Response.json({
|
|
8
|
+
message: 'Forbidden',
|
|
9
|
+
ok: false
|
|
10
|
+
}, {
|
|
11
|
+
status: 403
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
const id = String(req.routeParams?.id || '');
|
|
15
|
+
let submission;
|
|
16
|
+
try {
|
|
17
|
+
submission = await req.payload.findByID({
|
|
18
|
+
id,
|
|
19
|
+
collection: options.slugs.submissions,
|
|
20
|
+
depth: 0,
|
|
21
|
+
overrideAccess: true
|
|
22
|
+
});
|
|
23
|
+
} catch {
|
|
24
|
+
return Response.json({
|
|
25
|
+
message: 'Not found',
|
|
26
|
+
ok: false
|
|
27
|
+
}, {
|
|
28
|
+
status: 404
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const result = await resendFailedDeliveries(req.payload, submission, req);
|
|
32
|
+
if (result.added.length) {
|
|
33
|
+
await req.payload.update({
|
|
34
|
+
id,
|
|
35
|
+
collection: options.slugs.submissions,
|
|
36
|
+
context: {
|
|
37
|
+
[RESEND_CONTEXT]: true
|
|
38
|
+
},
|
|
39
|
+
data: {
|
|
40
|
+
deliveryLog: result.log,
|
|
41
|
+
status: result.status
|
|
42
|
+
},
|
|
43
|
+
overrideAccess: true
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return Response.json({
|
|
47
|
+
deliveryLog: result.log,
|
|
48
|
+
ok: true,
|
|
49
|
+
resent: result.added.length,
|
|
50
|
+
status: result.status
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
method: 'post',
|
|
54
|
+
path: '/:id/resend'
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=resend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/resend.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport type { ResolvedOptions } from '../types.js'\n\nimport { RESEND_CONTEXT } from '../hooks/resendTick.js'\nimport { resendFailedDeliveries, type SubmissionDoc } from '../server/resend.js'\n\n/** `POST /api/<submissions>/:id/resend` — admins only: runs the failed outputs again. */\nexport function resendEndpoint(options: ResolvedOptions): Endpoint {\n return {\n handler: async (req) => {\n if (!req.user || !options.isAdmin(req)) {\n return Response.json({ message: 'Forbidden', ok: false }, { status: 403 })\n }\n const id = String(req.routeParams?.id || '')\n let submission: SubmissionDoc\n try {\n submission = (await req.payload.findByID({\n id,\n collection: options.slugs.submissions as never,\n depth: 0,\n overrideAccess: true,\n })) as unknown as SubmissionDoc\n } catch {\n return Response.json({ message: 'Not found', ok: false }, { status: 404 })\n }\n const result = await resendFailedDeliveries(req.payload, submission, req)\n if (result.added.length) {\n await req.payload.update({\n id,\n collection: options.slugs.submissions as never,\n context: { [RESEND_CONTEXT]: true },\n data: { deliveryLog: result.log, status: result.status } as never,\n overrideAccess: true,\n })\n }\n return Response.json({ deliveryLog: result.log, ok: true, resent: result.added.length, status: result.status })\n },\n method: 'post',\n path: '/:id/resend',\n }\n}\n"],"names":["RESEND_CONTEXT","resendFailedDeliveries","resendEndpoint","options","handler","req","user","isAdmin","Response","json","message","ok","status","id","String","routeParams","submission","payload","findByID","collection","slugs","submissions","depth","overrideAccess","result","added","length","update","context","data","deliveryLog","log","resent","method","path"],"mappings":"AAIA,SAASA,cAAc,QAAQ,yBAAwB;AACvD,SAASC,sBAAsB,QAA4B,sBAAqB;AAEhF,uFAAuF,GACvF,OAAO,SAASC,eAAeC,OAAwB;IACrD,OAAO;QACLC,SAAS,OAAOC;YACd,IAAI,CAACA,IAAIC,IAAI,IAAI,CAACH,QAAQI,OAAO,CAACF,MAAM;gBACtC,OAAOG,SAASC,IAAI,CAAC;oBAAEC,SAAS;oBAAaC,IAAI;gBAAM,GAAG;oBAAEC,QAAQ;gBAAI;YAC1E;YACA,MAAMC,KAAKC,OAAOT,IAAIU,WAAW,EAAEF,MAAM;YACzC,IAAIG;YACJ,IAAI;gBACFA,aAAc,MAAMX,IAAIY,OAAO,CAACC,QAAQ,CAAC;oBACvCL;oBACAM,YAAYhB,QAAQiB,KAAK,CAACC,WAAW;oBACrCC,OAAO;oBACPC,gBAAgB;gBAClB;YACF,EAAE,OAAM;gBACN,OAAOf,SAASC,IAAI,CAAC;oBAAEC,SAAS;oBAAaC,IAAI;gBAAM,GAAG;oBAAEC,QAAQ;gBAAI;YAC1E;YACA,MAAMY,SAAS,MAAMvB,uBAAuBI,IAAIY,OAAO,EAAED,YAAYX;YACrE,IAAImB,OAAOC,KAAK,CAACC,MAAM,EAAE;gBACvB,MAAMrB,IAAIY,OAAO,CAACU,MAAM,CAAC;oBACvBd;oBACAM,YAAYhB,QAAQiB,KAAK,CAACC,WAAW;oBACrCO,SAAS;wBAAE,CAAC5B,eAAe,EAAE;oBAAK;oBAClC6B,MAAM;wBAAEC,aAAaN,OAAOO,GAAG;wBAAEnB,QAAQY,OAAOZ,MAAM;oBAAC;oBACvDW,gBAAgB;gBAClB;YACF;YACA,OAAOf,SAASC,IAAI,CAAC;gBAAEqB,aAAaN,OAAOO,GAAG;gBAAEpB,IAAI;gBAAMqB,QAAQR,OAAOC,KAAK,CAACC,MAAM;gBAAEd,QAAQY,OAAOZ,MAAM;YAAC;QAC/G;QACAqB,QAAQ;QACRC,MAAM;IACR;AACF"}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { hasDeliveries, runDeliveries, statusFromLog } from '../delivery/run.js';
|
|
3
|
+
import { checkRateLimit } from '../security/rateLimit.js';
|
|
4
|
+
import { getRequestIP, isHoneypotFilled } from '../security/request.js';
|
|
5
|
+
import { checkTurnstile } from '../security/turnstile.js';
|
|
6
|
+
import { loadPublishedForm } from '../server/forms.js';
|
|
7
|
+
import { buildSnapshot, checkAnswers } from '../shared/answers.js';
|
|
8
|
+
import { getFieldLabel, getInputFields } from '../shared/rules.js';
|
|
9
|
+
import { buildWhatsAppMessage, whatsappUrl } from '../shared/whatsapp.js';
|
|
10
|
+
function respond(status, body) {
|
|
11
|
+
return Response.json(body, {
|
|
12
|
+
status
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function isFile(value) {
|
|
16
|
+
return typeof value === 'object' && value !== null && typeof value.arrayBuffer === 'function' && 'name' in value;
|
|
17
|
+
}
|
|
18
|
+
async function parseBody(req) {
|
|
19
|
+
const type = req.headers.get('content-type') || '';
|
|
20
|
+
const raw = {};
|
|
21
|
+
const files = {};
|
|
22
|
+
if (type.includes('multipart/form-data') || type.includes('application/x-www-form-urlencoded')) {
|
|
23
|
+
const data = await req.formData();
|
|
24
|
+
for (const key of new Set(data.keys())){
|
|
25
|
+
const values = data.getAll(key);
|
|
26
|
+
const fileValues = values.filter(isFile).filter((f)=>f.size > 0);
|
|
27
|
+
if (fileValues.length) {
|
|
28
|
+
files[key] = fileValues;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const text = values.filter((v)=>typeof v === 'string');
|
|
32
|
+
raw[key] = text.length > 1 ? text : text[0];
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
files,
|
|
36
|
+
raw
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const json = await req.json();
|
|
40
|
+
if (json && typeof json === 'object' && !Array.isArray(json)) {
|
|
41
|
+
Object.assign(raw, json);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
files,
|
|
45
|
+
raw
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** `POST /api/<forms>/submit/:slug` — every check runs here, whatever the browser did. */ export function submitEndpoint(options) {
|
|
49
|
+
return {
|
|
50
|
+
handler: async (req)=>{
|
|
51
|
+
const payload = req.payload;
|
|
52
|
+
const slug = String(req.routeParams?.slug || '');
|
|
53
|
+
const locale = req.locale || undefined;
|
|
54
|
+
const loaded = slug ? await loadPublishedForm(payload, slug, {
|
|
55
|
+
locale,
|
|
56
|
+
req
|
|
57
|
+
}) : null;
|
|
58
|
+
if (!loaded) {
|
|
59
|
+
return respond(404, {
|
|
60
|
+
message: options.words.notFound,
|
|
61
|
+
ok: false
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const { delivery, form } = loaded;
|
|
65
|
+
const words = form.wording;
|
|
66
|
+
if (form.intakeState === 'closed') {
|
|
67
|
+
return respond(409, {
|
|
68
|
+
message: words.closed,
|
|
69
|
+
ok: false
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
let body;
|
|
73
|
+
try {
|
|
74
|
+
body = await parseBody(req);
|
|
75
|
+
} catch {
|
|
76
|
+
return respond(400, {
|
|
77
|
+
message: words.genericError,
|
|
78
|
+
ok: false
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const { files, raw } = body;
|
|
82
|
+
const ip = getRequestIP(req.headers);
|
|
83
|
+
if (form.spamChecks) {
|
|
84
|
+
if (isHoneypotFilled(raw, options.honeypotField)) {
|
|
85
|
+
return respond(400, {
|
|
86
|
+
message: words.genericError,
|
|
87
|
+
ok: false
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const human = await checkTurnstile({
|
|
91
|
+
ip,
|
|
92
|
+
logger: payload.logger,
|
|
93
|
+
settings: options.turnstile,
|
|
94
|
+
token: raw[options.turnstile.fieldName]
|
|
95
|
+
});
|
|
96
|
+
if (!human) {
|
|
97
|
+
return respond(400, {
|
|
98
|
+
message: words.securityCheckFailed,
|
|
99
|
+
ok: false
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (options.rateLimit) {
|
|
103
|
+
const allowed = await checkRateLimit({
|
|
104
|
+
formId: form.id,
|
|
105
|
+
ip,
|
|
106
|
+
max: options.rateLimit.max,
|
|
107
|
+
payload,
|
|
108
|
+
windowSeconds: options.rateLimit.windowSeconds
|
|
109
|
+
});
|
|
110
|
+
if (!allowed) {
|
|
111
|
+
return respond(429, {
|
|
112
|
+
message: words.tooManyTries,
|
|
113
|
+
ok: false
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const fileLikes = {};
|
|
119
|
+
for (const [key, list] of Object.entries(files)){
|
|
120
|
+
fileLikes[key] = list.map((f)=>({
|
|
121
|
+
name: f.name,
|
|
122
|
+
type: f.type,
|
|
123
|
+
size: f.size
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
const checked = checkAnswers(form, raw, {
|
|
127
|
+
fieldTypes: options.fieldTypes,
|
|
128
|
+
files: fileLikes,
|
|
129
|
+
limits: options.attachments || undefined,
|
|
130
|
+
words
|
|
131
|
+
});
|
|
132
|
+
if (!checked.ok) {
|
|
133
|
+
return respond(400, {
|
|
134
|
+
fieldErrors: checked.fieldErrors,
|
|
135
|
+
message: words.errorPrefix,
|
|
136
|
+
ok: false
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
const answers = checked.answers;
|
|
140
|
+
let waUrl;
|
|
141
|
+
if (delivery.whatsapp.enabled) {
|
|
142
|
+
const number = form.whatsapp?.number;
|
|
143
|
+
if (!number) {
|
|
144
|
+
payload.logger.error(`payload-dynamic-forms: form "${form.slug}" has WhatsApp on but no number (form or whatsapp.number).`);
|
|
145
|
+
return respond(500, {
|
|
146
|
+
message: words.genericError,
|
|
147
|
+
ok: false
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const message = buildWhatsAppMessage(form, answers, {
|
|
151
|
+
fieldTypes: options.fieldTypes,
|
|
152
|
+
rowTemplate: delivery.whatsapp.rowTemplate,
|
|
153
|
+
template: delivery.whatsapp.messageTemplate
|
|
154
|
+
});
|
|
155
|
+
waUrl = whatsappUrl(number, message);
|
|
156
|
+
}
|
|
157
|
+
const inputs = new Map(getInputFields(form).map((entry)=>[
|
|
158
|
+
entry.key,
|
|
159
|
+
entry
|
|
160
|
+
]));
|
|
161
|
+
const visibleUploads = checked.fileKeys.map((key)=>inputs.get(key)).filter(Boolean);
|
|
162
|
+
let submissionId;
|
|
163
|
+
let snapshot = buildSnapshot(form, answers, options.fieldTypes);
|
|
164
|
+
const created = [];
|
|
165
|
+
const outputs = hasDeliveries(delivery);
|
|
166
|
+
if (delivery.store) {
|
|
167
|
+
const attachmentSlug = options.attachments?.collection;
|
|
168
|
+
try {
|
|
169
|
+
for (const { field, key } of visibleUploads){
|
|
170
|
+
for (const file of files[key]){
|
|
171
|
+
const doc = await payload.create({
|
|
172
|
+
collection: attachmentSlug,
|
|
173
|
+
data: {
|
|
174
|
+
fieldKey: key,
|
|
175
|
+
fieldLabel: getFieldLabel(field, key),
|
|
176
|
+
form: form.id
|
|
177
|
+
},
|
|
178
|
+
file: {
|
|
179
|
+
name: file.name || 'upload',
|
|
180
|
+
data: Buffer.from(await file.arrayBuffer()),
|
|
181
|
+
mimetype: file.type || 'application/octet-stream',
|
|
182
|
+
size: file.size
|
|
183
|
+
},
|
|
184
|
+
overrideAccess: true
|
|
185
|
+
});
|
|
186
|
+
const filename = String(doc.filename || file.name);
|
|
187
|
+
created.push({
|
|
188
|
+
id: doc.id,
|
|
189
|
+
fieldKey: key,
|
|
190
|
+
filename
|
|
191
|
+
});
|
|
192
|
+
const prev = answers[key];
|
|
193
|
+
answers[key] = {
|
|
194
|
+
attachmentIds: [
|
|
195
|
+
...prev?.attachmentIds || [],
|
|
196
|
+
doc.id
|
|
197
|
+
],
|
|
198
|
+
filenames: [
|
|
199
|
+
...prev?.filenames || [],
|
|
200
|
+
filename
|
|
201
|
+
]
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
snapshot = buildSnapshot(form, answers, options.fieldTypes);
|
|
206
|
+
const submission = await payload.create({
|
|
207
|
+
collection: options.slugs.submissions,
|
|
208
|
+
data: {
|
|
209
|
+
answers,
|
|
210
|
+
attachments: created.map((c)=>c.id),
|
|
211
|
+
deliveryLog: [],
|
|
212
|
+
form: form.id,
|
|
213
|
+
metadata: {
|
|
214
|
+
ip: options.metadata.ip ? ip || null : null,
|
|
215
|
+
locale: req.locale || null,
|
|
216
|
+
userAgent: options.metadata.userAgent ? req.headers.get('user-agent') || null : null
|
|
217
|
+
},
|
|
218
|
+
snapshot,
|
|
219
|
+
status: outputs ? 'received' : 'processed'
|
|
220
|
+
},
|
|
221
|
+
overrideAccess: true
|
|
222
|
+
});
|
|
223
|
+
submissionId = submission.id;
|
|
224
|
+
for (const file of created){
|
|
225
|
+
await payload.update({
|
|
226
|
+
id: file.id,
|
|
227
|
+
collection: attachmentSlug,
|
|
228
|
+
data: {
|
|
229
|
+
submission: submissionId
|
|
230
|
+
},
|
|
231
|
+
overrideAccess: true
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
} catch (error) {
|
|
235
|
+
payload.logger.error({
|
|
236
|
+
err: error,
|
|
237
|
+
msg: `payload-dynamic-forms: could not store a submission of "${form.slug}"`
|
|
238
|
+
});
|
|
239
|
+
for (const file of created){
|
|
240
|
+
try {
|
|
241
|
+
await payload.delete({
|
|
242
|
+
id: file.id,
|
|
243
|
+
collection: attachmentSlug,
|
|
244
|
+
overrideAccess: true
|
|
245
|
+
});
|
|
246
|
+
} catch (deleteError) {
|
|
247
|
+
payload.logger.error({
|
|
248
|
+
err: deleteError,
|
|
249
|
+
msg: 'payload-dynamic-forms: could not remove an attachment'
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return respond(500, {
|
|
254
|
+
message: words.genericError,
|
|
255
|
+
ok: false
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (outputs) {
|
|
260
|
+
const task = async ()=>{
|
|
261
|
+
try {
|
|
262
|
+
const entries = await runDeliveries({
|
|
263
|
+
answers,
|
|
264
|
+
attachments: created,
|
|
265
|
+
delivery,
|
|
266
|
+
form,
|
|
267
|
+
options,
|
|
268
|
+
payload,
|
|
269
|
+
req,
|
|
270
|
+
snapshot,
|
|
271
|
+
submissionId
|
|
272
|
+
});
|
|
273
|
+
for (const entry of entries.filter((e)=>e.result === 'failed')){
|
|
274
|
+
payload.logger.error(`payload-dynamic-forms: ${entry.kind} "${entry.label}" of "${form.slug}" failed: ${entry.error}`);
|
|
275
|
+
}
|
|
276
|
+
if (submissionId !== undefined) {
|
|
277
|
+
await payload.update({
|
|
278
|
+
id: submissionId,
|
|
279
|
+
collection: options.slugs.submissions,
|
|
280
|
+
data: {
|
|
281
|
+
deliveryLog: entries,
|
|
282
|
+
status: statusFromLog(entries)
|
|
283
|
+
},
|
|
284
|
+
overrideAccess: true
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
} catch (error) {
|
|
288
|
+
payload.logger.error({
|
|
289
|
+
err: error,
|
|
290
|
+
msg: `payload-dynamic-forms: delivery of "${form.slug}" failed`
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
if (options.delivery?.schedule) {
|
|
295
|
+
options.delivery.schedule(task);
|
|
296
|
+
} else {
|
|
297
|
+
await task();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const result = {
|
|
301
|
+
message: words.success,
|
|
302
|
+
ok: true
|
|
303
|
+
};
|
|
304
|
+
if (form.successType === 'redirect' && form.successRedirectURL) {
|
|
305
|
+
result.redirectURL = form.successRedirectURL;
|
|
306
|
+
}
|
|
307
|
+
if (submissionId !== undefined) {
|
|
308
|
+
result.submissionId = submissionId;
|
|
309
|
+
}
|
|
310
|
+
if (waUrl) {
|
|
311
|
+
result.whatsappUrl = waUrl;
|
|
312
|
+
}
|
|
313
|
+
return respond(200, result);
|
|
314
|
+
},
|
|
315
|
+
method: 'post',
|
|
316
|
+
path: '/submit/:slug'
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
//# sourceMappingURL=submit.js.map
|