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,71 @@
|
|
|
1
|
+
import { hmacSha256Hex } 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
|
+
/**
|
|
13
|
+
* Sends one webhook: JSON body, secret headers, optional signature, a timeout per try (default
|
|
14
|
+
* 8 s) and up to 3 retries with growing pauses. A non-2xx answer or a timeout is a failed try.
|
|
15
|
+
*/ export async function sendWebhook(settings, body) {
|
|
16
|
+
const json = JSON.stringify(body);
|
|
17
|
+
const timeoutMs = Math.min(30_000, Math.max(500, Number(settings.timeoutMs) || 8000));
|
|
18
|
+
const retries = Math.min(3, Math.max(0, Math.floor(Number(settings.retries) || 0)));
|
|
19
|
+
let lastError = '';
|
|
20
|
+
let lastStatus;
|
|
21
|
+
let tries = 0;
|
|
22
|
+
for(let attempt = 0; attempt <= retries; attempt += 1){
|
|
23
|
+
if (attempt > 0) {
|
|
24
|
+
await sleep(RETRY_PAUSES_MS[Math.min(attempt - 1, RETRY_PAUSES_MS.length - 1)]);
|
|
25
|
+
}
|
|
26
|
+
tries += 1;
|
|
27
|
+
const headers = {
|
|
28
|
+
'Content-Type': 'application/json'
|
|
29
|
+
};
|
|
30
|
+
for (const header of settings.headers || []){
|
|
31
|
+
if (header?.key && header.value) {
|
|
32
|
+
headers[header.key] = header.value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (settings.signingSecret) {
|
|
36
|
+
headers['X-Form-Signature'] = await signWebhookBody(settings.signingSecret, json);
|
|
37
|
+
}
|
|
38
|
+
const controller = new AbortController();
|
|
39
|
+
const timer = setTimeout(()=>controller.abort(), timeoutMs);
|
|
40
|
+
try {
|
|
41
|
+
const response = await fetch(String(settings.url || ''), {
|
|
42
|
+
body: json,
|
|
43
|
+
headers,
|
|
44
|
+
method: settings.method || 'POST',
|
|
45
|
+
signal: controller.signal
|
|
46
|
+
});
|
|
47
|
+
lastStatus = response.status;
|
|
48
|
+
if (response.ok) {
|
|
49
|
+
return {
|
|
50
|
+
ok: true,
|
|
51
|
+
status: response.status,
|
|
52
|
+
tries
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
lastError = `Webhook answered ${response.status}.`;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
lastStatus = undefined;
|
|
58
|
+
lastError = controller.signal.aborted ? `Webhook timed out after ${timeoutMs} ms.` : error instanceof Error ? error.message : String(error);
|
|
59
|
+
} finally{
|
|
60
|
+
clearTimeout(timer);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
error: lastError,
|
|
65
|
+
ok: false,
|
|
66
|
+
status: lastStatus,
|
|
67
|
+
tries
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/delivery/webhook.ts"],"sourcesContent":["import { hmacSha256Hex } from '../security/crypto.js'\n\nexport type WebhookSettings = {\n headers?: Array<{ key?: null | string; value?: null | string }> | null\n method?: 'PATCH' | 'POST' | 'PUT' | null\n retries?: null | number\n signingSecret?: null | string\n timeoutMs?: null | number\n url?: 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/**\n * Sends one webhook: JSON body, secret headers, optional signature, a timeout per try (default\n * 8 s) and up to 3 retries with growing pauses. A 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 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 const headers: Record<string, string> = { 'Content-Type': 'application/json' }\n for (const header of settings.headers || []) {\n if (header?.key && header.value) {\n headers[header.key] = header.value\n }\n }\n if (settings.signingSecret) {\n headers['X-Form-Signature'] = await signWebhookBody(settings.signingSecret, json)\n }\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":["hmacSha256Hex","RETRY_PAUSES_MS","sleep","ms","Promise","resolve","setTimeout","signWebhookBody","secret","body","now","Date","t","Math","floor","sendWebhook","settings","json","JSON","stringify","timeoutMs","min","max","Number","retries","lastError","lastStatus","tries","attempt","length","headers","header","key","value","signingSecret","controller","AbortController","timer","abort","response","fetch","String","url","method","signal","status","ok","error","undefined","aborted","Error","message","clearTimeout"],"mappings":"AAAA,SAASA,aAAa,QAAQ,wBAAuB;AAarD,2CAA2C,GAC3C,OAAO,MAAMC,kBAAkB;IAAC;IAAK;IAAM;CAAK,CAAA;AAEhD,MAAMC,QAAQ,CAACC,KAAe,IAAIC,QAAc,CAACC,UAAYC,WAAWD,SAASF;AAEjF,uFAAuF,GACvF,OAAO,eAAeI,gBAAgBC,MAAc,EAAEC,IAAY,EAAEC,MAAMC,KAAKD,GAAG,EAAE;IAClF,MAAME,IAAIC,KAAKC,KAAK,CAACJ,MAAM;IAC3B,OAAO,CAAC,EAAE,EAAEE,EAAE,IAAI,EAAE,MAAMZ,cAAcQ,QAAQ,GAAGI,EAAE,CAAC,EAAEH,MAAM,GAAG;AACnE;AAEA;;;CAGC,GACD,OAAO,eAAeM,YAAYC,QAAyB,EAAEP,IAAa;IACxE,MAAMQ,OAAOC,KAAKC,SAAS,CAACV;IAC5B,MAAMW,YAAYP,KAAKQ,GAAG,CAAC,QAAQR,KAAKS,GAAG,CAAC,KAAKC,OAAOP,SAASI,SAAS,KAAK;IAC/E,MAAMI,UAAUX,KAAKQ,GAAG,CAAC,GAAGR,KAAKS,GAAG,CAAC,GAAGT,KAAKC,KAAK,CAACS,OAAOP,SAASQ,OAAO,KAAK;IAC/E,IAAIC,YAAY;IAChB,IAAIC;IACJ,IAAIC,QAAQ;IACZ,IAAK,IAAIC,UAAU,GAAGA,WAAWJ,SAASI,WAAW,EAAG;QACtD,IAAIA,UAAU,GAAG;YACf,MAAM1B,MAAMD,eAAe,CAACY,KAAKQ,GAAG,CAACO,UAAU,GAAG3B,gBAAgB4B,MAAM,GAAG,GAAG;QAChF;QACAF,SAAS;QACT,MAAMG,UAAkC;YAAE,gBAAgB;QAAmB;QAC7E,KAAK,MAAMC,UAAUf,SAASc,OAAO,IAAI,EAAE,CAAE;YAC3C,IAAIC,QAAQC,OAAOD,OAAOE,KAAK,EAAE;gBAC/BH,OAAO,CAACC,OAAOC,GAAG,CAAC,GAAGD,OAAOE,KAAK;YACpC;QACF;QACA,IAAIjB,SAASkB,aAAa,EAAE;YAC1BJ,OAAO,CAAC,mBAAmB,GAAG,MAAMvB,gBAAgBS,SAASkB,aAAa,EAAEjB;QAC9E;QACA,MAAMkB,aAAa,IAAIC;QACvB,MAAMC,QAAQ/B,WAAW,IAAM6B,WAAWG,KAAK,IAAIlB;QACnD,IAAI;YACF,MAAMmB,WAAW,MAAMC,MAAMC,OAAOzB,SAAS0B,GAAG,IAAI,KAAK;gBACvDjC,MAAMQ;gBACNa;gBACAa,QAAQ3B,SAAS2B,MAAM,IAAI;gBAC3BC,QAAQT,WAAWS,MAAM;YAC3B;YACAlB,aAAaa,SAASM,MAAM;YAC5B,IAAIN,SAASO,EAAE,EAAE;gBACf,OAAO;oBAAEA,IAAI;oBAAMD,QAAQN,SAASM,MAAM;oBAAElB;gBAAM;YACpD;YACAF,YAAY,CAAC,iBAAiB,EAAEc,SAASM,MAAM,CAAC,CAAC,CAAC;QACpD,EAAE,OAAOE,OAAO;YACdrB,aAAasB;YACbvB,YAAYU,WAAWS,MAAM,CAACK,OAAO,GACjC,CAAC,wBAAwB,EAAE7B,UAAU,IAAI,CAAC,GAC1C2B,iBAAiBG,QACfH,MAAMI,OAAO,GACbV,OAAOM;QACf,SAAU;YACRK,aAAaf;QACf;IACF;IACA,OAAO;QAAEU,OAAOtB;QAAWqB,IAAI;QAAOD,QAAQnB;QAAYC;IAAM;AAClE"}
|
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/submit.ts"],"sourcesContent":["import type { Endpoint, PayloadRequest } from 'payload'\n\nimport { Buffer } from 'node:buffer'\n\nimport type { Answers, FileAnswer, FileLike, SubmitResult } from '../shared/types.js'\nimport type { ResolvedOptions } from '../types.js'\n\nimport { hasDeliveries, type LogEntry, runDeliveries, statusFromLog } from '../delivery/run.js'\nimport { checkRateLimit } from '../security/rateLimit.js'\nimport { getRequestIP, isHoneypotFilled } from '../security/request.js'\nimport { checkTurnstile } from '../security/turnstile.js'\nimport { loadPublishedForm } from '../server/forms.js'\nimport { buildSnapshot, checkAnswers } from '../shared/answers.js'\nimport { getFieldLabel, getInputFields } from '../shared/rules.js'\nimport { buildWhatsAppMessage, whatsappUrl } from '../shared/whatsapp.js'\n\ntype Raw = Record<string, unknown>\n\nfunction respond(status: number, body: SubmitResult): Response {\n return Response.json(body, { status })\n}\n\nfunction isFile(value: unknown): value is File {\n return typeof value === 'object' && value !== null && typeof (value as Blob).arrayBuffer === 'function' && 'name' in value\n}\n\nasync function parseBody(req: PayloadRequest): Promise<{ files: Record<string, File[]>; raw: Raw }> {\n const type = req.headers.get('content-type') || ''\n const raw: Raw = {}\n const files: Record<string, File[]> = {}\n if (type.includes('multipart/form-data') || type.includes('application/x-www-form-urlencoded')) {\n const data = await req.formData!()\n for (const key of new Set(data.keys())) {\n const values = data.getAll(key)\n const fileValues = values.filter(isFile).filter((f) => f.size > 0)\n if (fileValues.length) {\n files[key] = fileValues\n continue\n }\n const text = values.filter((v): v is string => typeof v === 'string')\n raw[key] = text.length > 1 ? text : text[0]\n }\n return { files, raw }\n }\n const json = (await req.json!()) as unknown\n if (json && typeof json === 'object' && !Array.isArray(json)) {\n Object.assign(raw, json)\n }\n return { files, raw }\n}\n\n/** `POST /api/<forms>/submit/:slug` — every check runs here, whatever the browser did. */\nexport function submitEndpoint(options: ResolvedOptions): Endpoint {\n return {\n handler: async (req) => {\n const payload = req.payload\n const slug = String(req.routeParams?.slug || '')\n const locale = (req.locale || undefined) as never\n const loaded = slug ? await loadPublishedForm(payload, slug, { locale, req }) : null\n if (!loaded) {\n return respond(404, { message: options.words.notFound, ok: false })\n }\n const { delivery, form } = loaded\n const words = form.wording\n if (form.intakeState === 'closed') {\n return respond(409, { message: words.closed, ok: false })\n }\n\n let body: Awaited<ReturnType<typeof parseBody>>\n try {\n body = await parseBody(req)\n } catch {\n return respond(400, { message: words.genericError, ok: false })\n }\n const { files, raw } = body\n const ip = getRequestIP(req.headers)\n\n if (form.spamChecks) {\n if (isHoneypotFilled(raw, options.honeypotField)) {\n return respond(400, { message: words.genericError, ok: false })\n }\n const human = await checkTurnstile({\n ip,\n logger: payload.logger,\n settings: options.turnstile,\n token: raw[options.turnstile.fieldName],\n })\n if (!human) {\n return respond(400, { message: words.securityCheckFailed, ok: false })\n }\n if (options.rateLimit) {\n const allowed = await checkRateLimit({\n formId: form.id,\n ip,\n max: options.rateLimit.max,\n payload,\n windowSeconds: options.rateLimit.windowSeconds,\n })\n if (!allowed) {\n return respond(429, { message: words.tooManyTries, ok: false })\n }\n }\n }\n\n const fileLikes: Record<string, FileLike[]> = {}\n for (const [key, list] of Object.entries(files)) {\n fileLikes[key] = list.map((f) => ({ name: f.name, type: f.type, size: f.size }))\n }\n const checked = checkAnswers(form, raw, {\n fieldTypes: options.fieldTypes,\n files: fileLikes,\n limits: options.attachments || undefined,\n words,\n })\n if (!checked.ok) {\n return respond(400, { fieldErrors: checked.fieldErrors, message: words.errorPrefix, ok: false })\n }\n const answers: Answers = checked.answers\n\n let waUrl: string | undefined\n if (delivery.whatsapp.enabled) {\n const number = form.whatsapp?.number\n if (!number) {\n payload.logger.error(`payload-dynamic-forms: form \"${form.slug}\" has WhatsApp on but no number (form or whatsapp.number).`)\n return respond(500, { message: words.genericError, ok: false })\n }\n const message = buildWhatsAppMessage(form, answers, {\n fieldTypes: options.fieldTypes,\n rowTemplate: delivery.whatsapp.rowTemplate,\n template: delivery.whatsapp.messageTemplate,\n })\n waUrl = whatsappUrl(number, message)\n }\n\n const inputs = new Map(getInputFields(form).map((entry) => [entry.key, entry]))\n const visibleUploads = checked.fileKeys.map((key) => inputs.get(key)!).filter(Boolean)\n\n let submissionId: number | string | undefined\n let snapshot = buildSnapshot(form, answers, options.fieldTypes)\n const created: Array<{ fieldKey: string; filename: string; id: number | string }> = []\n const outputs = hasDeliveries(delivery)\n\n if (delivery.store) {\n const attachmentSlug = options.attachments?.collection\n try {\n for (const { field, key } of visibleUploads) {\n for (const file of files[key]) {\n const doc = await payload.create({\n collection: attachmentSlug as never,\n data: { fieldKey: key, fieldLabel: getFieldLabel(field, key), form: form.id } as never,\n file: {\n name: file.name || 'upload',\n data: Buffer.from(await file.arrayBuffer()),\n mimetype: file.type || 'application/octet-stream',\n size: file.size,\n },\n overrideAccess: true,\n })\n const filename = String((doc as { filename?: string }).filename || file.name)\n created.push({ id: doc.id, fieldKey: key, filename })\n const prev = answers[key] as FileAnswer | undefined\n answers[key] = {\n attachmentIds: [...(prev?.attachmentIds || []), doc.id],\n filenames: [...(prev?.filenames || []), filename],\n }\n }\n }\n snapshot = buildSnapshot(form, answers, options.fieldTypes)\n const submission = await payload.create({\n collection: options.slugs.submissions as never,\n data: {\n answers,\n attachments: created.map((c) => c.id),\n deliveryLog: [],\n form: form.id,\n metadata: {\n ip: options.metadata.ip ? ip || null : null,\n locale: req.locale || null,\n userAgent: options.metadata.userAgent ? req.headers.get('user-agent') || null : null,\n },\n snapshot,\n status: outputs ? 'received' : 'processed',\n } as never,\n overrideAccess: true,\n })\n submissionId = submission.id\n for (const file of created) {\n await payload.update({\n id: file.id,\n collection: attachmentSlug as never,\n data: { submission: submissionId } as never,\n overrideAccess: true,\n })\n }\n } catch (error) {\n payload.logger.error({ err: error, msg: `payload-dynamic-forms: could not store a submission of \"${form.slug}\"` })\n for (const file of created) {\n try {\n await payload.delete({ id: file.id, collection: attachmentSlug as never, overrideAccess: true })\n } catch (deleteError) {\n payload.logger.error({ err: deleteError, msg: 'payload-dynamic-forms: could not remove an attachment' })\n }\n }\n return respond(500, { message: words.genericError, ok: false })\n }\n }\n\n if (outputs) {\n const task = async () => {\n try {\n const entries: LogEntry[] = await runDeliveries({\n answers,\n attachments: created,\n delivery,\n form,\n options,\n payload,\n req,\n snapshot,\n submissionId,\n })\n for (const entry of entries.filter((e) => e.result === 'failed')) {\n payload.logger.error(`payload-dynamic-forms: ${entry.kind} \"${entry.label}\" of \"${form.slug}\" failed: ${entry.error}`)\n }\n if (submissionId !== undefined) {\n await payload.update({\n id: submissionId,\n collection: options.slugs.submissions as never,\n data: { deliveryLog: entries, status: statusFromLog(entries) } as never,\n overrideAccess: true,\n })\n }\n } catch (error) {\n payload.logger.error({ err: error, msg: `payload-dynamic-forms: delivery of \"${form.slug}\" failed` })\n }\n }\n if (options.delivery?.schedule) {\n options.delivery.schedule(task)\n } else {\n await task()\n }\n }\n\n const result: SubmitResult = { message: words.success, ok: true }\n if (form.successType === 'redirect' && form.successRedirectURL) {\n result.redirectURL = form.successRedirectURL\n }\n if (submissionId !== undefined) {\n result.submissionId = submissionId\n }\n if (waUrl) {\n result.whatsappUrl = waUrl\n }\n return respond(200, result)\n },\n method: 'post',\n path: '/submit/:slug',\n }\n}\n"],"names":["Buffer","hasDeliveries","runDeliveries","statusFromLog","checkRateLimit","getRequestIP","isHoneypotFilled","checkTurnstile","loadPublishedForm","buildSnapshot","checkAnswers","getFieldLabel","getInputFields","buildWhatsAppMessage","whatsappUrl","respond","status","body","Response","json","isFile","value","arrayBuffer","parseBody","req","type","headers","get","raw","files","includes","data","formData","key","Set","keys","values","getAll","fileValues","filter","f","size","length","text","v","Array","isArray","Object","assign","submitEndpoint","options","handler","payload","slug","String","routeParams","locale","undefined","loaded","message","words","notFound","ok","delivery","form","wording","intakeState","closed","genericError","ip","spamChecks","honeypotField","human","logger","settings","turnstile","token","fieldName","securityCheckFailed","rateLimit","allowed","formId","id","max","windowSeconds","tooManyTries","fileLikes","list","entries","map","name","checked","fieldTypes","limits","attachments","fieldErrors","errorPrefix","answers","waUrl","whatsapp","enabled","number","error","rowTemplate","template","messageTemplate","inputs","Map","entry","visibleUploads","fileKeys","Boolean","submissionId","snapshot","created","outputs","store","attachmentSlug","collection","field","file","doc","create","fieldKey","fieldLabel","from","mimetype","overrideAccess","filename","push","prev","attachmentIds","filenames","submission","slugs","submissions","c","deliveryLog","metadata","userAgent","update","err","msg","delete","deleteError","task","e","result","kind","label","schedule","success","successType","successRedirectURL","redirectURL","method","path"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `payload-dynamic-forms/shared`: the rule engine, answer checks, tokens,
|
|
3
|
+
* messages and words. Runs the same in the browser, on Workers and in the submit endpoint.
|
|
4
|
+
* Imports nothing from Payload, React or Node at runtime.
|
|
5
|
+
*/
|
|
6
|
+
export { answerToText, buildSnapshot, checkAnswers, checkFiles, type CheckOptions, type CheckResult, DEFAULT_FILE_LIMITS, type FileLimits, isValidEmail, normalizeAnswer, } from '../shared/answers.js';
|
|
7
|
+
export { MATCH_MODES, OPERATOR_VALUES, OPERATORS, type OperatorValue } from '../shared/operators.js';
|
|
8
|
+
export { defaultAnswer, evaluateRow, evaluateRules, getFieldKey, getFieldLabel, getInputFieldMap, getInputFields, getOptions, getRowLabel, getSectionKey, getSections, getVisibleSections, hasAnswer, initialAnswers, type InputEntry, isChoiceBlock, isFieldVisible, isInputBlock, isSectionVisible, normalizeKey, readRuleValue, splitRuleKey, } from '../shared/rules.js';
|
|
9
|
+
export { getNextSectionKey, getStepPath, getStepProgress, getVisibility, type Visibility } from '../shared/steps.js';
|
|
10
|
+
export { buildMessageRows, buildTemplateValues, escapeHtml, interpolate, type InterpolateOptions, type MessageRow, type TokenValue, } from '../shared/tokens.js';
|
|
11
|
+
export type * from '../shared/types.js';
|
|
12
|
+
export { buildWhatsAppMessage, DEFAULT_WHATSAPP_ROW_TEMPLATE, DEFAULT_WHATSAPP_TEMPLATE, type WhatsAppMessageOptions, whatsappUrl, } from '../shared/whatsapp.js';
|
|
13
|
+
export { DEFAULT_WORDS, fillWords, WORD_KEYS } from '../shared/words.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `payload-dynamic-forms/shared`: the rule engine, answer checks, tokens,
|
|
3
|
+
* messages and words. Runs the same in the browser, on Workers and in the submit endpoint.
|
|
4
|
+
* Imports nothing from Payload, React or Node at runtime.
|
|
5
|
+
*/ export { answerToText, buildSnapshot, checkAnswers, checkFiles, DEFAULT_FILE_LIMITS, isValidEmail, normalizeAnswer } from '../shared/answers.js';
|
|
6
|
+
export { MATCH_MODES, OPERATOR_VALUES, OPERATORS } from '../shared/operators.js';
|
|
7
|
+
export { defaultAnswer, evaluateRow, evaluateRules, getFieldKey, getFieldLabel, getInputFieldMap, getInputFields, getOptions, getRowLabel, getSectionKey, getSections, getVisibleSections, hasAnswer, initialAnswers, isChoiceBlock, isFieldVisible, isInputBlock, isSectionVisible, normalizeKey, readRuleValue, splitRuleKey } from '../shared/rules.js';
|
|
8
|
+
export { getNextSectionKey, getStepPath, getStepProgress, getVisibility } from '../shared/steps.js';
|
|
9
|
+
export { buildMessageRows, buildTemplateValues, escapeHtml, interpolate } from '../shared/tokens.js';
|
|
10
|
+
export { buildWhatsAppMessage, DEFAULT_WHATSAPP_ROW_TEMPLATE, DEFAULT_WHATSAPP_TEMPLATE, whatsappUrl } from '../shared/whatsapp.js';
|
|
11
|
+
export { DEFAULT_WORDS, fillWords, WORD_KEYS } from '../shared/words.js';
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/exports/shared.ts"],"sourcesContent":["/**\n * `payload-dynamic-forms/shared`: the rule engine, answer checks, tokens,\n * messages and words. Runs the same in the browser, on Workers and in the submit endpoint.\n * Imports nothing from Payload, React or Node at runtime.\n */\nexport {\n answerToText,\n buildSnapshot,\n checkAnswers,\n checkFiles,\n type CheckOptions,\n type CheckResult,\n DEFAULT_FILE_LIMITS,\n type FileLimits,\n isValidEmail,\n normalizeAnswer,\n} from '../shared/answers.js'\nexport { MATCH_MODES, OPERATOR_VALUES, OPERATORS, type OperatorValue } from '../shared/operators.js'\nexport {\n defaultAnswer,\n evaluateRow,\n evaluateRules,\n getFieldKey,\n getFieldLabel,\n getInputFieldMap,\n getInputFields,\n getOptions,\n getRowLabel,\n getSectionKey,\n getSections,\n getVisibleSections,\n hasAnswer,\n initialAnswers,\n type InputEntry,\n isChoiceBlock,\n isFieldVisible,\n isInputBlock,\n isSectionVisible,\n normalizeKey,\n readRuleValue,\n splitRuleKey,\n} from '../shared/rules.js'\nexport { getNextSectionKey, getStepPath, getStepProgress, getVisibility, type Visibility } from '../shared/steps.js'\nexport {\n buildMessageRows,\n buildTemplateValues,\n escapeHtml,\n interpolate,\n type InterpolateOptions,\n type MessageRow,\n type TokenValue,\n} from '../shared/tokens.js'\nexport type * from '../shared/types.js'\nexport {\n buildWhatsAppMessage,\n DEFAULT_WHATSAPP_ROW_TEMPLATE,\n DEFAULT_WHATSAPP_TEMPLATE,\n type WhatsAppMessageOptions,\n whatsappUrl,\n} from '../shared/whatsapp.js'\nexport { DEFAULT_WORDS, fillWords, WORD_KEYS } from '../shared/words.js'\n"],"names":["answerToText","buildSnapshot","checkAnswers","checkFiles","DEFAULT_FILE_LIMITS","isValidEmail","normalizeAnswer","MATCH_MODES","OPERATOR_VALUES","OPERATORS","defaultAnswer","evaluateRow","evaluateRules","getFieldKey","getFieldLabel","getInputFieldMap","getInputFields","getOptions","getRowLabel","getSectionKey","getSections","getVisibleSections","hasAnswer","initialAnswers","isChoiceBlock","isFieldVisible","isInputBlock","isSectionVisible","normalizeKey","readRuleValue","splitRuleKey","getNextSectionKey","getStepPath","getStepProgress","getVisibility","buildMessageRows","buildTemplateValues","escapeHtml","interpolate","buildWhatsAppMessage","DEFAULT_WHATSAPP_ROW_TEMPLATE","DEFAULT_WHATSAPP_TEMPLATE","whatsappUrl","DEFAULT_WORDS","fillWords","WORD_KEYS"],"mappings":"AAAA;;;;CAIC,GACD,SACEA,YAAY,EACZC,aAAa,EACbC,YAAY,EACZC,UAAU,EAGVC,mBAAmB,EAEnBC,YAAY,EACZC,eAAe,QACV,uBAAsB;AAC7B,SAASC,WAAW,EAAEC,eAAe,EAAEC,SAAS,QAA4B,yBAAwB;AACpG,SACEC,aAAa,EACbC,WAAW,EACXC,aAAa,EACbC,WAAW,EACXC,aAAa,EACbC,gBAAgB,EAChBC,cAAc,EACdC,UAAU,EACVC,WAAW,EACXC,aAAa,EACbC,WAAW,EACXC,kBAAkB,EAClBC,SAAS,EACTC,cAAc,EAEdC,aAAa,EACbC,cAAc,EACdC,YAAY,EACZC,gBAAgB,EAChBC,YAAY,EACZC,aAAa,EACbC,YAAY,QACP,qBAAoB;AAC3B,SAASC,iBAAiB,EAAEC,WAAW,EAAEC,eAAe,EAAEC,aAAa,QAAyB,qBAAoB;AACpH,SACEC,gBAAgB,EAChBC,mBAAmB,EACnBC,UAAU,EACVC,WAAW,QAIN,sBAAqB;AAE5B,SACEC,oBAAoB,EACpBC,6BAA6B,EAC7BC,yBAAyB,EAEzBC,WAAW,QACN,wBAAuB;AAC9B,SAASC,aAAa,EAAEC,SAAS,EAAEC,SAAS,QAAQ,qBAAoB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Block } from 'payload';
|
|
2
|
+
export type FormBlockOptions = {
|
|
3
|
+
/** Forms collection slug (default `custom-forms`; use the plugin's `slugs.forms` if renamed). */
|
|
4
|
+
formsSlug?: string;
|
|
5
|
+
interfaceName?: string;
|
|
6
|
+
/** Block slug, default `formBlock`. */
|
|
7
|
+
slug?: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* A page block that picks one form. Add it to any blocks field; the app's renderer reads the
|
|
11
|
+
* form with `getPublicForm` or the public endpoint. It holds no words of its own.
|
|
12
|
+
*/
|
|
13
|
+
export declare function formBlock(options?: FormBlockOptions): Block;
|