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 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/security/rateLimit.ts"],"sourcesContent":["import type { Payload } from 'payload'\n\nimport { sha256Hex } from './crypto.js'\n\ntype Entry = { count: number; start: number }\n\n/**\n * Soft per-form, per-visitor limit in Payload's KV store. The key holds a hash of the IP and the\n * app secret, never the IP. Returns false when the visitor is over the limit.\n */\nexport async function checkRateLimit(args: {\n formId: number | string\n ip: string\n max: number\n now?: number\n payload: Payload\n windowSeconds: number\n}): Promise<boolean> {\n const { payload } = args\n const now = args.now ?? Date.now()\n const hash = await sha256Hex(`${args.ip || 'unknown'}${payload.secret}`)\n const key = `forms:rl:${args.formId}:${hash}`\n const entry = await payload.kv.get<Entry>(key)\n if (!entry || typeof entry.start !== 'number' || now - entry.start >= args.windowSeconds * 1000) {\n await payload.kv.set(key, { count: 1, start: now })\n return true\n }\n if (entry.count >= args.max) {\n return false\n }\n await payload.kv.set(key, { count: entry.count + 1, start: entry.start })\n return true\n}\n"],"names":["sha256Hex","checkRateLimit","args","payload","now","Date","hash","ip","secret","key","formId","entry","kv","get","start","windowSeconds","set","count","max"],"mappings":"AAEA,SAASA,SAAS,QAAQ,cAAa;AAIvC;;;CAGC,GACD,OAAO,eAAeC,eAAeC,IAOpC;IACC,MAAM,EAAEC,OAAO,EAAE,GAAGD;IACpB,MAAME,MAAMF,KAAKE,GAAG,IAAIC,KAAKD,GAAG;IAChC,MAAME,OAAO,MAAMN,UAAU,GAAGE,KAAKK,EAAE,IAAI,YAAYJ,QAAQK,MAAM,EAAE;IACvE,MAAMC,MAAM,CAAC,SAAS,EAAEP,KAAKQ,MAAM,CAAC,CAAC,EAAEJ,MAAM;IAC7C,MAAMK,QAAQ,MAAMR,QAAQS,EAAE,CAACC,GAAG,CAAQJ;IAC1C,IAAI,CAACE,SAAS,OAAOA,MAAMG,KAAK,KAAK,YAAYV,MAAMO,MAAMG,KAAK,IAAIZ,KAAKa,aAAa,GAAG,MAAM;QAC/F,MAAMZ,QAAQS,EAAE,CAACI,GAAG,CAACP,KAAK;YAAEQ,OAAO;YAAGH,OAAOV;QAAI;QACjD,OAAO;IACT;IACA,IAAIO,MAAMM,KAAK,IAAIf,KAAKgB,GAAG,EAAE;QAC3B,OAAO;IACT;IACA,MAAMf,QAAQS,EAAE,CAACI,GAAG,CAACP,KAAK;QAAEQ,OAAON,MAAMM,KAAK,GAAG;QAAGH,OAAOH,MAAMG,KAAK;IAAC;IACvE,OAAO;AACT"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** The visitor IP: `cf-connecting-ip`, else the first `x-forwarded-for`, else `x-real-ip`. */ export function getRequestIP(headers) {
|
|
2
|
+
const value = headers.get('cf-connecting-ip') || headers.get('x-forwarded-for') || headers.get('x-real-ip') || '';
|
|
3
|
+
return value.split(',')[0]?.trim() || '';
|
|
4
|
+
}
|
|
5
|
+
export function isHoneypotFilled(raw, fieldName) {
|
|
6
|
+
const value = raw[fieldName];
|
|
7
|
+
const list = Array.isArray(value) ? value : [
|
|
8
|
+
value
|
|
9
|
+
];
|
|
10
|
+
return list.some((v)=>typeof v === 'string' && v.trim() !== '');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/security/request.ts"],"sourcesContent":["/** The visitor IP: `cf-connecting-ip`, else the first `x-forwarded-for`, else `x-real-ip`. */\nexport function getRequestIP(headers: Headers): string {\n const value =\n headers.get('cf-connecting-ip') || headers.get('x-forwarded-for') || headers.get('x-real-ip') || ''\n return value.split(',')[0]?.trim() || ''\n}\n\nexport function isHoneypotFilled(raw: Record<string, unknown>, fieldName: string): boolean {\n const value = raw[fieldName]\n const list = Array.isArray(value) ? value : [value]\n return list.some((v) => typeof v === 'string' && v.trim() !== '')\n}\n"],"names":["getRequestIP","headers","value","get","split","trim","isHoneypotFilled","raw","fieldName","list","Array","isArray","some","v"],"mappings":"AAAA,4FAA4F,GAC5F,OAAO,SAASA,aAAaC,OAAgB;IAC3C,MAAMC,QACJD,QAAQE,GAAG,CAAC,uBAAuBF,QAAQE,GAAG,CAAC,sBAAsBF,QAAQE,GAAG,CAAC,gBAAgB;IACnG,OAAOD,MAAME,KAAK,CAAC,IAAI,CAAC,EAAE,EAAEC,UAAU;AACxC;AAEA,OAAO,SAASC,iBAAiBC,GAA4B,EAAEC,SAAiB;IAC9E,MAAMN,QAAQK,GAAG,CAACC,UAAU;IAC5B,MAAMC,OAAOC,MAAMC,OAAO,CAACT,SAASA,QAAQ;QAACA;KAAM;IACnD,OAAOO,KAAKG,IAAI,CAAC,CAACC,IAAM,OAAOA,MAAM,YAAYA,EAAER,IAAI,OAAO;AAChE"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const SITEVERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
|
|
2
|
+
type Logger = {
|
|
3
|
+
error: (msg: unknown, ...rest: unknown[]) => void;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Verifies a Turnstile token with Cloudflare's siteverify: secret, token, visitor IP and an
|
|
7
|
+
* idempotency key (safe retries). Any error counts as a failed check.
|
|
8
|
+
*/
|
|
9
|
+
export declare function verifyTurnstile(args: {
|
|
10
|
+
idempotencyKey?: string;
|
|
11
|
+
ip?: string;
|
|
12
|
+
secret: string;
|
|
13
|
+
token: string;
|
|
14
|
+
}): Promise<boolean>;
|
|
15
|
+
/**
|
|
16
|
+
* The Turnstile check for one plugin setup. Off: passes. On without a secret: refuses every
|
|
17
|
+
* submission and logs one error line. On: the token must verify.
|
|
18
|
+
*/
|
|
19
|
+
export declare function checkTurnstile(args: {
|
|
20
|
+
ip: string;
|
|
21
|
+
logger: Logger;
|
|
22
|
+
settings: {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
secretKey: string;
|
|
25
|
+
};
|
|
26
|
+
token: unknown;
|
|
27
|
+
}): Promise<boolean>;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const SITEVERIFY_URL = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies a Turnstile token with Cloudflare's siteverify: secret, token, visitor IP and an
|
|
4
|
+
* idempotency key (safe retries). Any error counts as a failed check.
|
|
5
|
+
*/ export async function verifyTurnstile(args) {
|
|
6
|
+
try {
|
|
7
|
+
const response = await fetch(SITEVERIFY_URL, {
|
|
8
|
+
body: JSON.stringify({
|
|
9
|
+
idempotency_key: args.idempotencyKey || crypto.randomUUID(),
|
|
10
|
+
remoteip: args.ip || undefined,
|
|
11
|
+
response: args.token,
|
|
12
|
+
secret: args.secret
|
|
13
|
+
}),
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'application/json'
|
|
16
|
+
},
|
|
17
|
+
method: 'POST'
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const data = await response.json();
|
|
23
|
+
return data.success === true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const warned = new WeakSet();
|
|
29
|
+
/**
|
|
30
|
+
* The Turnstile check for one plugin setup. Off: passes. On without a secret: refuses every
|
|
31
|
+
* submission and logs one error line. On: the token must verify.
|
|
32
|
+
*/ export async function checkTurnstile(args) {
|
|
33
|
+
const { settings } = args;
|
|
34
|
+
if (!settings.enabled) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (!settings.secretKey) {
|
|
38
|
+
if (!warned.has(settings)) {
|
|
39
|
+
warned.add(settings);
|
|
40
|
+
args.logger.error('payload-dynamic-forms: Turnstile is on but has no secret key; every submission is refused.');
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const token = Array.isArray(args.token) ? args.token[0] : args.token;
|
|
45
|
+
if (typeof token !== 'string' || !token.trim()) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return verifyTurnstile({
|
|
49
|
+
idempotencyKey: crypto.randomUUID(),
|
|
50
|
+
ip: args.ip,
|
|
51
|
+
secret: settings.secretKey,
|
|
52
|
+
token: token.trim()
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=turnstile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/security/turnstile.ts"],"sourcesContent":["export const SITEVERIFY_URL = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'\n\ntype Logger = { error: (msg: unknown, ...rest: unknown[]) => void }\n\n/**\n * Verifies a Turnstile token with Cloudflare's siteverify: secret, token, visitor IP and an\n * idempotency key (safe retries). Any error counts as a failed check.\n */\nexport async function verifyTurnstile(args: {\n idempotencyKey?: string\n ip?: string\n secret: string\n token: string\n}): Promise<boolean> {\n try {\n const response = await fetch(SITEVERIFY_URL, {\n body: JSON.stringify({\n idempotency_key: args.idempotencyKey || crypto.randomUUID(),\n remoteip: args.ip || undefined,\n response: args.token,\n secret: args.secret,\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n if (!response.ok) {\n return false\n }\n const data = (await response.json()) as { success?: boolean }\n return data.success === true\n } catch {\n return false\n }\n}\n\nconst warned = new WeakSet<object>()\n\n/**\n * The Turnstile check for one plugin setup. Off: passes. On without a secret: refuses every\n * submission and logs one error line. On: the token must verify.\n */\nexport async function checkTurnstile(args: {\n ip: string\n logger: Logger\n settings: { enabled: boolean; secretKey: string }\n token: unknown\n}): Promise<boolean> {\n const { settings } = args\n if (!settings.enabled) {\n return true\n }\n if (!settings.secretKey) {\n if (!warned.has(settings)) {\n warned.add(settings)\n args.logger.error('payload-dynamic-forms: Turnstile is on but has no secret key; every submission is refused.')\n }\n return false\n }\n const token = Array.isArray(args.token) ? args.token[0] : args.token\n if (typeof token !== 'string' || !token.trim()) {\n return false\n }\n return verifyTurnstile({ idempotencyKey: crypto.randomUUID(), ip: args.ip, secret: settings.secretKey, token: token.trim() })\n}\n"],"names":["SITEVERIFY_URL","verifyTurnstile","args","response","fetch","body","JSON","stringify","idempotency_key","idempotencyKey","crypto","randomUUID","remoteip","ip","undefined","token","secret","headers","method","ok","data","json","success","warned","WeakSet","checkTurnstile","settings","enabled","secretKey","has","add","logger","error","Array","isArray","trim"],"mappings":"AAAA,OAAO,MAAMA,iBAAiB,4DAA2D;AAIzF;;;CAGC,GACD,OAAO,eAAeC,gBAAgBC,IAKrC;IACC,IAAI;QACF,MAAMC,WAAW,MAAMC,MAAMJ,gBAAgB;YAC3CK,MAAMC,KAAKC,SAAS,CAAC;gBACnBC,iBAAiBN,KAAKO,cAAc,IAAIC,OAAOC,UAAU;gBACzDC,UAAUV,KAAKW,EAAE,IAAIC;gBACrBX,UAAUD,KAAKa,KAAK;gBACpBC,QAAQd,KAAKc,MAAM;YACrB;YACAC,SAAS;gBAAE,gBAAgB;YAAmB;YAC9CC,QAAQ;QACV;QACA,IAAI,CAACf,SAASgB,EAAE,EAAE;YAChB,OAAO;QACT;QACA,MAAMC,OAAQ,MAAMjB,SAASkB,IAAI;QACjC,OAAOD,KAAKE,OAAO,KAAK;IAC1B,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA,MAAMC,SAAS,IAAIC;AAEnB;;;CAGC,GACD,OAAO,eAAeC,eAAevB,IAKpC;IACC,MAAM,EAAEwB,QAAQ,EAAE,GAAGxB;IACrB,IAAI,CAACwB,SAASC,OAAO,EAAE;QACrB,OAAO;IACT;IACA,IAAI,CAACD,SAASE,SAAS,EAAE;QACvB,IAAI,CAACL,OAAOM,GAAG,CAACH,WAAW;YACzBH,OAAOO,GAAG,CAACJ;YACXxB,KAAK6B,MAAM,CAACC,KAAK,CAAC;QACpB;QACA,OAAO;IACT;IACA,MAAMjB,QAAQkB,MAAMC,OAAO,CAAChC,KAAKa,KAAK,IAAIb,KAAKa,KAAK,CAAC,EAAE,GAAGb,KAAKa,KAAK;IACpE,IAAI,OAAOA,UAAU,YAAY,CAACA,MAAMoB,IAAI,IAAI;QAC9C,OAAO;IACT;IACA,OAAOlC,gBAAgB;QAAEQ,gBAAgBC,OAAOC,UAAU;QAAIE,IAAIX,KAAKW,EAAE;QAAEG,QAAQU,SAASE,SAAS;QAAEb,OAAOA,MAAMoB,IAAI;IAAG;AAC7H"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Payload, PayloadRequest, TypedLocale } from 'payload';
|
|
2
|
+
import type { PublicForm } from '../shared/types.js';
|
|
3
|
+
import type { ResolvedOptions } from '../types.js';
|
|
4
|
+
export type EmailOutput = {
|
|
5
|
+
bcc?: null | string;
|
|
6
|
+
body?: null | string;
|
|
7
|
+
cc?: null | string;
|
|
8
|
+
enabled?: boolean | null;
|
|
9
|
+
from?: null | string;
|
|
10
|
+
id?: null | string;
|
|
11
|
+
kind?: 'team' | 'visitor' | null;
|
|
12
|
+
label?: null | string;
|
|
13
|
+
replyToField?: null | string;
|
|
14
|
+
subject?: null | string;
|
|
15
|
+
to?: null | string;
|
|
16
|
+
visitorField?: null | string;
|
|
17
|
+
};
|
|
18
|
+
export type WebhookOutput = {
|
|
19
|
+
enabled?: boolean | null;
|
|
20
|
+
headers?: Array<{
|
|
21
|
+
key?: null | string;
|
|
22
|
+
value?: null | string;
|
|
23
|
+
}> | null;
|
|
24
|
+
id?: null | string;
|
|
25
|
+
label?: null | string;
|
|
26
|
+
method?: 'PATCH' | 'POST' | 'PUT' | null;
|
|
27
|
+
payloadMode?: 'answersOnly' | 'full' | null;
|
|
28
|
+
retries?: null | number;
|
|
29
|
+
signingSecret?: null | string;
|
|
30
|
+
timeoutMs?: null | number;
|
|
31
|
+
url?: null | string;
|
|
32
|
+
};
|
|
33
|
+
export type FormDelivery = {
|
|
34
|
+
emails: EmailOutput[];
|
|
35
|
+
store: boolean;
|
|
36
|
+
webhooks: WebhookOutput[];
|
|
37
|
+
whatsapp: {
|
|
38
|
+
enabled: boolean;
|
|
39
|
+
messageTemplate?: null | string;
|
|
40
|
+
number?: null | string;
|
|
41
|
+
rowTemplate?: null | string;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export type LoadedForm = {
|
|
45
|
+
delivery: FormDelivery;
|
|
46
|
+
form: PublicForm;
|
|
47
|
+
};
|
|
48
|
+
type Locale = 'all' | TypedLocale | undefined;
|
|
49
|
+
/** The plugin options of an app, kept on its config by `formsPlugin`. */
|
|
50
|
+
export declare function getPluginOptions(payload: Payload): ResolvedOptions;
|
|
51
|
+
/**
|
|
52
|
+
* Loads a published form by slug, as a visitor would see it, plus its delivery settings for the
|
|
53
|
+
* server. Null when the slug is unknown or the form is not published.
|
|
54
|
+
*/
|
|
55
|
+
export declare function loadPublishedForm(payload: Payload, slug: string, args?: {
|
|
56
|
+
locale?: Locale;
|
|
57
|
+
req?: PayloadRequest;
|
|
58
|
+
}): Promise<LoadedForm | null>;
|
|
59
|
+
/** Loads a form by id with its delivery settings (trusted server use: resend). */
|
|
60
|
+
export declare function loadFormById(payload: Payload, id: number | string, args?: {
|
|
61
|
+
locale?: Locale;
|
|
62
|
+
req?: PayloadRequest;
|
|
63
|
+
}): Promise<LoadedForm | null>;
|
|
64
|
+
/**
|
|
65
|
+
* A published form ready to render: sections, choices from sources resolved with their
|
|
66
|
+
* attributes, words filled from the defaults, the WhatsApp number when WhatsApp is on, and none of
|
|
67
|
+
* its other delivery settings. Null for unknown or unpublished slugs.
|
|
68
|
+
*/
|
|
69
|
+
export declare function getPublicForm(payload: Payload, slug: string, args?: {
|
|
70
|
+
locale?: Locale;
|
|
71
|
+
req?: PayloadRequest;
|
|
72
|
+
}): Promise<null | PublicForm>;
|
|
73
|
+
export {};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { createLocalReq } from 'payload';
|
|
2
|
+
import { isChoiceBlock, isInputBlock } from '../shared/rules.js';
|
|
3
|
+
import { fillWords } from '../shared/words.js';
|
|
4
|
+
/** The plugin options of an app, kept on its config by `formsPlugin`. */ export function getPluginOptions(payload) {
|
|
5
|
+
const options = payload.config.custom?.payloadForms;
|
|
6
|
+
if (!options) {
|
|
7
|
+
throw new Error('payload-dynamic-forms: formsPlugin is not installed (or is switched off) in this config.');
|
|
8
|
+
}
|
|
9
|
+
return options;
|
|
10
|
+
}
|
|
11
|
+
function pick(doc, names = []) {
|
|
12
|
+
const out = {};
|
|
13
|
+
for (const name of names){
|
|
14
|
+
if (doc[name] !== undefined && doc[name] !== null) {
|
|
15
|
+
out[name] = doc[name];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Turns source-based choices into `{ label, value, attributes }`: picked items in their order, or
|
|
22
|
+
* every published item in the source's default sort when the pick is empty. Unpublished and
|
|
23
|
+
* missing items are left out.
|
|
24
|
+
*/ async function resolveChoices(payload, options, sections, locale) {
|
|
25
|
+
const sources = options.optionSources || {};
|
|
26
|
+
for (const section of sections){
|
|
27
|
+
for (const field of section.fields || []){
|
|
28
|
+
if (!isInputBlock(field) || !isChoiceBlock(field)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const block = field;
|
|
32
|
+
const sourceKey = block.optionsFrom;
|
|
33
|
+
const source = sourceKey && sourceKey !== 'typed' ? sources[sourceKey] : undefined;
|
|
34
|
+
const picked = Array.isArray(block.sourceItems) ? block.sourceItems : [];
|
|
35
|
+
delete block.sourceItems;
|
|
36
|
+
if (!source) {
|
|
37
|
+
block.options = (block.options || []).map((o)=>({
|
|
38
|
+
label: o.label,
|
|
39
|
+
value: o.value
|
|
40
|
+
}));
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const slug = String(source.collection);
|
|
44
|
+
const config = payload.collections[slug]?.config;
|
|
45
|
+
if (!config) {
|
|
46
|
+
block.options = [];
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const ids = picked.filter((item)=>item && item.relationTo === slug).map((item)=>item.value && typeof item.value === 'object' ? item.value.id : item.value);
|
|
50
|
+
const and = [];
|
|
51
|
+
if (picked.length) {
|
|
52
|
+
and.push({
|
|
53
|
+
id: {
|
|
54
|
+
in: ids.length ? ids : [
|
|
55
|
+
-1
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (config.versions?.drafts) {
|
|
61
|
+
and.push({
|
|
62
|
+
_status: {
|
|
63
|
+
equals: 'published'
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Trusted read of the items the editor picked: only label, value and listed attributes leave it.
|
|
68
|
+
const { docs } = await payload.find({
|
|
69
|
+
collection: slug,
|
|
70
|
+
depth: 0,
|
|
71
|
+
draft: false,
|
|
72
|
+
locale: locale,
|
|
73
|
+
overrideAccess: true,
|
|
74
|
+
pagination: false,
|
|
75
|
+
where: and.length ? {
|
|
76
|
+
and
|
|
77
|
+
} : undefined
|
|
78
|
+
});
|
|
79
|
+
const byId = new Map(docs.map((doc)=>[
|
|
80
|
+
String(doc.id),
|
|
81
|
+
doc
|
|
82
|
+
]));
|
|
83
|
+
const ordered = picked.length ? ids.map((id)=>byId.get(String(id))).filter((d)=>Boolean(d)) : docs;
|
|
84
|
+
block.options = ordered.map((doc)=>({
|
|
85
|
+
attributes: pick(doc, source.attributes),
|
|
86
|
+
label: String(doc[source.labelField] ?? ''),
|
|
87
|
+
value: String(doc[source.valueField] ?? '')
|
|
88
|
+
})).filter((o)=>o.value);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function buildLoaded(payload, options, raw, locale, req) {
|
|
93
|
+
const sections = structuredClone(raw.sections || []).filter(Boolean);
|
|
94
|
+
await resolveChoices(payload, options, sections, locale);
|
|
95
|
+
const d = raw.delivery || {};
|
|
96
|
+
const delivery = {
|
|
97
|
+
emails: (d.emails || []).filter(Boolean),
|
|
98
|
+
store: d.store !== false,
|
|
99
|
+
webhooks: (d.webhooks || []).filter(Boolean),
|
|
100
|
+
whatsapp: {
|
|
101
|
+
enabled: false,
|
|
102
|
+
...d.whatsapp || {}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const sends = delivery.emails.some((e)=>e.enabled !== false) || delivery.webhooks.some((w)=>w.enabled !== false);
|
|
106
|
+
const form = {
|
|
107
|
+
id: raw.id,
|
|
108
|
+
slug: String(raw.slug || ''),
|
|
109
|
+
honeypotField: options.honeypotField,
|
|
110
|
+
intakeState: raw.intakeState === 'closed' ? 'closed' : 'open',
|
|
111
|
+
presentationMode: raw.presentationMode === 'steps' ? 'steps' : 'singlePage',
|
|
112
|
+
sections,
|
|
113
|
+
spamChecks: delivery.store || sends,
|
|
114
|
+
successRedirectURL: raw.successType === 'redirect' ? raw.successRedirectURL || null : null,
|
|
115
|
+
successType: raw.successType === 'redirect' ? 'redirect' : 'message',
|
|
116
|
+
title: String(raw.title || ''),
|
|
117
|
+
turnstile: {
|
|
118
|
+
enabled: options.turnstile.enabled,
|
|
119
|
+
fieldName: options.turnstile.fieldName
|
|
120
|
+
},
|
|
121
|
+
wording: fillWords(raw.wording, options.words)
|
|
122
|
+
};
|
|
123
|
+
if (delivery.whatsapp.enabled) {
|
|
124
|
+
let number = delivery.whatsapp.number?.trim() || '';
|
|
125
|
+
if (!number && options.whatsapp?.number) {
|
|
126
|
+
const request = req || await createLocalReq({}, payload);
|
|
127
|
+
number = String(await options.whatsapp.number({
|
|
128
|
+
form,
|
|
129
|
+
req: request
|
|
130
|
+
}) || '').trim();
|
|
131
|
+
}
|
|
132
|
+
if (number) {
|
|
133
|
+
form.whatsapp = {
|
|
134
|
+
number
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
delivery,
|
|
140
|
+
form
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Loads a published form by slug, as a visitor would see it, plus its delivery settings for the
|
|
145
|
+
* server. Null when the slug is unknown or the form is not published.
|
|
146
|
+
*/ export async function loadPublishedForm(payload, slug, args = {}) {
|
|
147
|
+
const options = getPluginOptions(payload);
|
|
148
|
+
// Read as an anonymous visitor: access control decides, delivery fields are stripped.
|
|
149
|
+
const { docs } = await payload.find({
|
|
150
|
+
collection: options.slugs.forms,
|
|
151
|
+
depth: 0,
|
|
152
|
+
draft: false,
|
|
153
|
+
limit: 1,
|
|
154
|
+
locale: args.locale,
|
|
155
|
+
overrideAccess: false,
|
|
156
|
+
pagination: false,
|
|
157
|
+
where: {
|
|
158
|
+
and: [
|
|
159
|
+
{
|
|
160
|
+
slug: {
|
|
161
|
+
equals: slug
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
_status: {
|
|
166
|
+
equals: 'published'
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
const visible = docs[0];
|
|
173
|
+
if (!visible) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
// Server-side read of the same published document for its delivery settings and secrets.
|
|
177
|
+
const full = await payload.findByID({
|
|
178
|
+
id: visible.id,
|
|
179
|
+
collection: options.slugs.forms,
|
|
180
|
+
depth: 0,
|
|
181
|
+
draft: false,
|
|
182
|
+
locale: args.locale,
|
|
183
|
+
overrideAccess: true
|
|
184
|
+
});
|
|
185
|
+
return buildLoaded(payload, options, {
|
|
186
|
+
...full,
|
|
187
|
+
id: visible.id
|
|
188
|
+
}, args.locale, args.req);
|
|
189
|
+
}
|
|
190
|
+
/** Loads a form by id with its delivery settings (trusted server use: resend). */ export async function loadFormById(payload, id, args = {}) {
|
|
191
|
+
const options = getPluginOptions(payload);
|
|
192
|
+
try {
|
|
193
|
+
const full = await payload.findByID({
|
|
194
|
+
id,
|
|
195
|
+
collection: options.slugs.forms,
|
|
196
|
+
depth: 0,
|
|
197
|
+
draft: false,
|
|
198
|
+
locale: args.locale,
|
|
199
|
+
overrideAccess: true
|
|
200
|
+
});
|
|
201
|
+
return full ? buildLoaded(payload, options, full, args.locale, args.req) : null;
|
|
202
|
+
} catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* A published form ready to render: sections, choices from sources resolved with their
|
|
208
|
+
* attributes, words filled from the defaults, the WhatsApp number when WhatsApp is on, and none of
|
|
209
|
+
* its other delivery settings. Null for unknown or unpublished slugs.
|
|
210
|
+
*/ export async function getPublicForm(payload, slug, args = {}) {
|
|
211
|
+
const loaded = await loadPublishedForm(payload, slug, args);
|
|
212
|
+
return loaded ? loaded.form : null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
//# sourceMappingURL=forms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/forms.ts"],"sourcesContent":["import type { Payload, PayloadRequest, TypedLocale, Where } from 'payload'\n\nimport { createLocalReq } from 'payload'\n\nimport type { ChoiceOption, FormSection, InputBlock, PublicForm } from '../shared/types.js'\nimport type { ResolvedOptions } from '../types.js'\n\nimport { isChoiceBlock, isInputBlock } from '../shared/rules.js'\nimport { fillWords } from '../shared/words.js'\n\nexport type EmailOutput = {\n bcc?: null | string\n body?: null | string\n cc?: null | string\n enabled?: boolean | null\n from?: null | string\n id?: null | string\n kind?: 'team' | 'visitor' | null\n label?: null | string\n replyToField?: null | string\n subject?: null | string\n to?: null | string\n visitorField?: null | string\n}\n\nexport type WebhookOutput = {\n enabled?: boolean | null\n headers?: Array<{ key?: null | string; value?: null | string }> | null\n id?: null | string\n label?: null | string\n method?: 'PATCH' | 'POST' | 'PUT' | null\n payloadMode?: 'answersOnly' | 'full' | null\n retries?: null | number\n signingSecret?: null | string\n timeoutMs?: null | number\n url?: null | string\n}\n\nexport type FormDelivery = {\n emails: EmailOutput[]\n store: boolean\n webhooks: WebhookOutput[]\n whatsapp: { enabled: boolean; messageTemplate?: null | string; number?: null | string; rowTemplate?: null | string }\n}\n\nexport type LoadedForm = {\n delivery: FormDelivery\n form: PublicForm\n}\n\ntype RawForm = {\n _status?: string\n delivery?: null | Partial<FormDelivery>\n id: number | string\n intakeState?: 'closed' | 'open' | null\n presentationMode?: 'singlePage' | 'steps' | null\n sections?: FormSection[] | null\n slug?: null | string\n successRedirectURL?: null | string\n successType?: 'message' | 'redirect' | null\n title?: null | string\n wording?: null | Record<string, null | string>\n}\n\ntype Locale = 'all' | TypedLocale | undefined\n\n/** The plugin options of an app, kept on its config by `formsPlugin`. */\nexport function getPluginOptions(payload: Payload): ResolvedOptions {\n const options = (payload.config.custom as { payloadForms?: ResolvedOptions } | undefined)?.payloadForms\n if (!options) {\n throw new Error('payload-dynamic-forms: formsPlugin is not installed (or is switched off) in this config.')\n }\n return options\n}\n\nfunction pick(doc: Record<string, unknown>, names: string[] = []): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const name of names) {\n if (doc[name] !== undefined && doc[name] !== null) {\n out[name] = doc[name]\n }\n }\n return out\n}\n\n/**\n * Turns source-based choices into `{ label, value, attributes }`: picked items in their order, or\n * every published item in the source's default sort when the pick is empty. Unpublished and\n * missing items are left out.\n */\nasync function resolveChoices(payload: Payload, options: ResolvedOptions, sections: FormSection[], locale: Locale) {\n const sources = options.optionSources || {}\n for (const section of sections) {\n for (const field of section.fields || []) {\n if (!isInputBlock(field) || !isChoiceBlock(field)) {\n continue\n }\n const block = field as { sourceItems?: unknown } & InputBlock\n const sourceKey = block.optionsFrom\n const source = sourceKey && sourceKey !== 'typed' ? sources[sourceKey] : undefined\n const picked = Array.isArray(block.sourceItems) ? block.sourceItems : []\n delete block.sourceItems\n if (!source) {\n block.options = (block.options || []).map((o: ChoiceOption) => ({ label: o.label, value: o.value }))\n continue\n }\n const slug = String(source.collection)\n const config = payload.collections[slug as never]?.config\n if (!config) {\n block.options = []\n continue\n }\n const ids = picked\n .filter((item: { relationTo?: string }) => item && item.relationTo === slug)\n .map((item: { value: unknown }) =>\n item.value && typeof item.value === 'object' ? (item.value as { id: number | string }).id : item.value,\n ) as Array<number | string>\n const and: Where[] = []\n if (picked.length) {\n and.push({ id: { in: ids.length ? ids : [-1] } })\n }\n if (config.versions?.drafts) {\n and.push({ _status: { equals: 'published' } })\n }\n // Trusted read of the items the editor picked: only label, value and listed attributes leave it.\n const { docs } = await payload.find({\n collection: slug as never,\n depth: 0,\n draft: false,\n locale: locale as never,\n overrideAccess: true,\n pagination: false,\n where: and.length ? { and } : undefined,\n })\n const byId = new Map(docs.map((doc) => [String((doc as { id: unknown }).id), doc as Record<string, unknown>]))\n const ordered = picked.length\n ? ids.map((id) => byId.get(String(id))).filter((d): d is Record<string, unknown> => Boolean(d))\n : (docs as Record<string, unknown>[])\n block.options = ordered\n .map((doc) => ({\n attributes: pick(doc, source.attributes),\n label: String(doc[source.labelField] ?? ''),\n value: String(doc[source.valueField] ?? ''),\n }))\n .filter((o) => o.value)\n }\n }\n}\n\nasync function buildLoaded(\n payload: Payload,\n options: ResolvedOptions,\n raw: RawForm,\n locale: Locale,\n req?: PayloadRequest,\n): Promise<LoadedForm> {\n const sections = structuredClone(raw.sections || []).filter(Boolean)\n await resolveChoices(payload, options, sections, locale)\n const d = raw.delivery || {}\n const delivery: FormDelivery = {\n emails: (d.emails || []).filter(Boolean),\n store: d.store !== false,\n webhooks: (d.webhooks || []).filter(Boolean),\n whatsapp: { enabled: false, ...(d.whatsapp || {}) },\n }\n const sends =\n delivery.emails.some((e) => e.enabled !== false) || delivery.webhooks.some((w) => w.enabled !== false)\n const form: PublicForm = {\n id: raw.id,\n slug: String(raw.slug || ''),\n honeypotField: options.honeypotField,\n intakeState: raw.intakeState === 'closed' ? 'closed' : 'open',\n presentationMode: raw.presentationMode === 'steps' ? 'steps' : 'singlePage',\n sections,\n spamChecks: delivery.store || sends,\n successRedirectURL: raw.successType === 'redirect' ? raw.successRedirectURL || null : null,\n successType: raw.successType === 'redirect' ? 'redirect' : 'message',\n title: String(raw.title || ''),\n turnstile: { enabled: options.turnstile.enabled, fieldName: options.turnstile.fieldName },\n wording: fillWords(raw.wording as never, options.words),\n }\n if (delivery.whatsapp.enabled) {\n let number = delivery.whatsapp.number?.trim() || ''\n if (!number && options.whatsapp?.number) {\n const request = req || (await createLocalReq({}, payload))\n number = String((await options.whatsapp.number({ form, req: request })) || '').trim()\n }\n if (number) {\n form.whatsapp = { number }\n }\n }\n return { delivery, form }\n}\n\n/**\n * Loads a published form by slug, as a visitor would see it, plus its delivery settings for the\n * server. Null when the slug is unknown or the form is not published.\n */\nexport async function loadPublishedForm(\n payload: Payload,\n slug: string,\n args: { locale?: Locale; req?: PayloadRequest } = {},\n): Promise<LoadedForm | null> {\n const options = getPluginOptions(payload)\n // Read as an anonymous visitor: access control decides, delivery fields are stripped.\n const { docs } = await payload.find({\n collection: options.slugs.forms as never,\n depth: 0,\n draft: false,\n limit: 1,\n locale: args.locale as never,\n overrideAccess: false,\n pagination: false,\n where: { and: [{ slug: { equals: slug } }, { _status: { equals: 'published' } }] },\n })\n const visible = docs[0] as RawForm | undefined\n if (!visible) {\n return null\n }\n // Server-side read of the same published document for its delivery settings and secrets.\n const full = (await payload.findByID({\n id: visible.id,\n collection: options.slugs.forms as never,\n depth: 0,\n draft: false,\n locale: args.locale as never,\n overrideAccess: true,\n })) as unknown as RawForm\n return buildLoaded(payload, options, { ...full, id: visible.id }, args.locale, args.req)\n}\n\n/** Loads a form by id with its delivery settings (trusted server use: resend). */\nexport async function loadFormById(\n payload: Payload,\n id: number | string,\n args: { locale?: Locale; req?: PayloadRequest } = {},\n): Promise<LoadedForm | null> {\n const options = getPluginOptions(payload)\n try {\n const full = (await payload.findByID({\n id,\n collection: options.slugs.forms as never,\n depth: 0,\n draft: false,\n locale: args.locale as never,\n overrideAccess: true,\n })) as unknown as RawForm\n return full ? buildLoaded(payload, options, full, args.locale, args.req) : null\n } catch {\n return null\n }\n}\n\n/**\n * A published form ready to render: sections, choices from sources resolved with their\n * attributes, words filled from the defaults, the WhatsApp number when WhatsApp is on, and none of\n * its other delivery settings. Null for unknown or unpublished slugs.\n */\nexport async function getPublicForm(\n payload: Payload,\n slug: string,\n args: { locale?: Locale; req?: PayloadRequest } = {},\n): Promise<null | PublicForm> {\n const loaded = await loadPublishedForm(payload, slug, args)\n return loaded ? loaded.form : null\n}\n"],"names":["createLocalReq","isChoiceBlock","isInputBlock","fillWords","getPluginOptions","payload","options","config","custom","payloadForms","Error","pick","doc","names","out","name","undefined","resolveChoices","sections","locale","sources","optionSources","section","field","fields","block","sourceKey","optionsFrom","source","picked","Array","isArray","sourceItems","map","o","label","value","slug","String","collection","collections","ids","filter","item","relationTo","id","and","length","push","in","versions","drafts","_status","equals","docs","find","depth","draft","overrideAccess","pagination","where","byId","Map","ordered","get","d","Boolean","attributes","labelField","valueField","buildLoaded","raw","req","structuredClone","delivery","emails","store","webhooks","whatsapp","enabled","sends","some","e","w","form","honeypotField","intakeState","presentationMode","spamChecks","successRedirectURL","successType","title","turnstile","fieldName","wording","words","number","trim","request","loadPublishedForm","args","slugs","forms","limit","visible","full","findByID","loadFormById","getPublicForm","loaded"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Payload, PayloadRequest } from 'payload';
|
|
2
|
+
import type { Answers, SnapshotRow, SubmissionStatus } from '../shared/types.js';
|
|
3
|
+
import { type LogEntry } from '../delivery/run.js';
|
|
4
|
+
export type SubmissionDoc = {
|
|
5
|
+
answers?: Answers | null;
|
|
6
|
+
attachments?: Array<{
|
|
7
|
+
id: number | string;
|
|
8
|
+
} | number | string> | null;
|
|
9
|
+
deliveryLog?: LogEntry[] | null;
|
|
10
|
+
form?: {
|
|
11
|
+
id: number | string;
|
|
12
|
+
} | null | number | string;
|
|
13
|
+
id: number | string;
|
|
14
|
+
metadata?: {
|
|
15
|
+
locale?: null | string;
|
|
16
|
+
} | null;
|
|
17
|
+
snapshot?: null | SnapshotRow[];
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Runs again the outputs whose last try failed and that still exist on the form. Outputs removed
|
|
21
|
+
* since get a "skipped" entry. Returns the full new log and status (the caller saves them).
|
|
22
|
+
*/
|
|
23
|
+
export declare function resendFailedDeliveries(payload: Payload, submission: SubmissionDoc, req: PayloadRequest): Promise<{
|
|
24
|
+
added: LogEntry[];
|
|
25
|
+
log: LogEntry[];
|
|
26
|
+
status: SubmissionStatus;
|
|
27
|
+
}>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { runDeliveries, statusFromLog } from '../delivery/run.js';
|
|
2
|
+
import { getPluginOptions, loadFormById } from './forms.js';
|
|
3
|
+
/**
|
|
4
|
+
* Runs again the outputs whose last try failed and that still exist on the form. Outputs removed
|
|
5
|
+
* since get a "skipped" entry. Returns the full new log and status (the caller saves them).
|
|
6
|
+
*/ export async function resendFailedDeliveries(payload, submission, req) {
|
|
7
|
+
const options = getPluginOptions(payload);
|
|
8
|
+
const log = (submission.deliveryLog || []).map((entry)=>{
|
|
9
|
+
const { id: _id, ...rest } = entry;
|
|
10
|
+
return rest;
|
|
11
|
+
});
|
|
12
|
+
const formId = typeof submission.form === 'object' && submission.form ? submission.form.id : submission.form;
|
|
13
|
+
const loaded = formId != null ? await loadFormById(payload, formId, {
|
|
14
|
+
locale: submission.metadata?.locale || undefined,
|
|
15
|
+
req
|
|
16
|
+
}) : null;
|
|
17
|
+
const last = new Map();
|
|
18
|
+
for (const entry of log){
|
|
19
|
+
if (entry.outputId) {
|
|
20
|
+
last.set(entry.outputId, entry);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const failed = [
|
|
24
|
+
...last.values()
|
|
25
|
+
].filter((entry)=>entry.result === 'failed');
|
|
26
|
+
if (!failed.length) {
|
|
27
|
+
return {
|
|
28
|
+
added: [],
|
|
29
|
+
log,
|
|
30
|
+
status: statusFromLog(log)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const existing = new Set(loaded ? [
|
|
34
|
+
...loaded.delivery.emails,
|
|
35
|
+
...loaded.delivery.webhooks
|
|
36
|
+
].filter((o)=>o.enabled !== false && o.id).map((o)=>String(o.id)) : []);
|
|
37
|
+
const added = [];
|
|
38
|
+
const rerun = new Set();
|
|
39
|
+
for (const entry of failed){
|
|
40
|
+
if (existing.has(entry.outputId)) {
|
|
41
|
+
rerun.add(entry.outputId);
|
|
42
|
+
} else {
|
|
43
|
+
added.push({
|
|
44
|
+
at: new Date().toISOString(),
|
|
45
|
+
error: 'This output is no longer on the form (or is switched off); not sent again.',
|
|
46
|
+
kind: entry.kind,
|
|
47
|
+
label: entry.label,
|
|
48
|
+
outputId: entry.outputId,
|
|
49
|
+
result: 'skipped',
|
|
50
|
+
target: entry.target,
|
|
51
|
+
tries: 0
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (loaded && rerun.size) {
|
|
56
|
+
const attachments = (submission.attachments || []).map((a)=>typeof a === 'object' ? a.id : a);
|
|
57
|
+
added.push(...await runDeliveries({
|
|
58
|
+
answers: submission.answers || {},
|
|
59
|
+
attachments: attachments.map((id)=>({
|
|
60
|
+
id,
|
|
61
|
+
fieldKey: '',
|
|
62
|
+
filename: ''
|
|
63
|
+
})),
|
|
64
|
+
delivery: loaded.delivery,
|
|
65
|
+
form: loaded.form,
|
|
66
|
+
options,
|
|
67
|
+
payload,
|
|
68
|
+
req,
|
|
69
|
+
snapshot: submission.snapshot || [],
|
|
70
|
+
submissionId: submission.id
|
|
71
|
+
}, rerun));
|
|
72
|
+
}
|
|
73
|
+
const next = [
|
|
74
|
+
...log,
|
|
75
|
+
...added
|
|
76
|
+
];
|
|
77
|
+
return {
|
|
78
|
+
added,
|
|
79
|
+
log: next,
|
|
80
|
+
status: statusFromLog(next)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=resend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/resend.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\n\nimport type { Answers, SnapshotRow, SubmissionStatus } from '../shared/types.js'\n\nimport { type LogEntry, runDeliveries, statusFromLog } from '../delivery/run.js'\nimport { getPluginOptions, loadFormById } from './forms.js'\n\nexport type SubmissionDoc = {\n answers?: Answers | null\n attachments?: Array<{ id: number | string } | number | string> | null\n deliveryLog?: LogEntry[] | null\n form?: { id: number | string } | null | number | string\n id: number | string\n metadata?: { locale?: null | string } | null\n snapshot?: null | SnapshotRow[]\n}\n\n/**\n * Runs again the outputs whose last try failed and that still exist on the form. Outputs removed\n * since get a \"skipped\" entry. Returns the full new log and status (the caller saves them).\n */\nexport async function resendFailedDeliveries(\n payload: Payload,\n submission: SubmissionDoc,\n req: PayloadRequest,\n): Promise<{ added: LogEntry[]; log: LogEntry[]; status: SubmissionStatus }> {\n const options = getPluginOptions(payload)\n const log = (submission.deliveryLog || []).map((entry) => {\n const { id: _id, ...rest } = entry as { id?: string } & LogEntry\n return rest as LogEntry\n })\n const formId = typeof submission.form === 'object' && submission.form ? submission.form.id : submission.form\n const loaded = formId != null ? await loadFormById(payload, formId, { locale: (submission.metadata?.locale || undefined) as never, req }) : null\n const last = new Map<string, LogEntry>()\n for (const entry of log) {\n if (entry.outputId) {\n last.set(entry.outputId, entry)\n }\n }\n const failed = [...last.values()].filter((entry) => entry.result === 'failed')\n if (!failed.length) {\n return { added: [], log, status: statusFromLog(log) }\n }\n const existing = new Set<string>(\n loaded\n ? [...loaded.delivery.emails, ...loaded.delivery.webhooks]\n .filter((o) => o.enabled !== false && o.id)\n .map((o) => String(o.id))\n : [],\n )\n const added: LogEntry[] = []\n const rerun = new Set<string>()\n for (const entry of failed) {\n if (existing.has(entry.outputId!)) {\n rerun.add(entry.outputId!)\n } else {\n added.push({\n at: new Date().toISOString(),\n error: 'This output is no longer on the form (or is switched off); not sent again.',\n kind: entry.kind,\n label: entry.label,\n outputId: entry.outputId,\n result: 'skipped',\n target: entry.target,\n tries: 0,\n })\n }\n }\n if (loaded && rerun.size) {\n const attachments = (submission.attachments || []).map((a) => (typeof a === 'object' ? a.id : a))\n added.push(\n ...(await runDeliveries(\n {\n answers: submission.answers || {},\n attachments: attachments.map((id) => ({ id, fieldKey: '', filename: '' })),\n delivery: loaded.delivery,\n form: loaded.form,\n options,\n payload,\n req,\n snapshot: submission.snapshot || [],\n submissionId: submission.id,\n },\n rerun,\n )),\n )\n }\n const next = [...log, ...added]\n return { added, log: next, status: statusFromLog(next) }\n}\n"],"names":["runDeliveries","statusFromLog","getPluginOptions","loadFormById","resendFailedDeliveries","payload","submission","req","options","log","deliveryLog","map","entry","id","_id","rest","formId","form","loaded","locale","metadata","undefined","last","Map","outputId","set","failed","values","filter","result","length","added","status","existing","Set","delivery","emails","webhooks","o","enabled","String","rerun","has","add","push","at","Date","toISOString","error","kind","label","target","tries","size","attachments","a","answers","fieldKey","filename","snapshot","submissionId","next"],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Answers, AnswerValue, FieldTypeChecks, FileLike, FormLike, FormWords, InputBlock, SnapshotRow, YesNoWords } from './types.js';
|
|
2
|
+
/** Default limits for file-upload fields. */
|
|
3
|
+
export declare const DEFAULT_FILE_LIMITS: {
|
|
4
|
+
readonly maxFileSize: number;
|
|
5
|
+
readonly maxTotalSize: number;
|
|
6
|
+
readonly mimeTypes: readonly ["application/pdf", "image/jpeg", "image/png", "image/webp", "text/plain", "text/csv", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"];
|
|
7
|
+
};
|
|
8
|
+
export type FileLimits = {
|
|
9
|
+
maxFileSize?: number;
|
|
10
|
+
maxTotalSize?: number;
|
|
11
|
+
mimeTypes?: readonly string[];
|
|
12
|
+
};
|
|
13
|
+
/** One valid address: no spaces, commas or lists, a dot in the domain. */
|
|
14
|
+
export declare function isValidEmail(value: unknown): boolean;
|
|
15
|
+
/** Turns a raw answer (form data or JSON) into the value the field type keeps. */
|
|
16
|
+
export declare function normalizeAnswer(field: InputBlock, raw: unknown, fieldTypes?: Record<string, FieldTypeChecks>): AnswerValue;
|
|
17
|
+
/**
|
|
18
|
+
* The text of an answer in messages and snapshots: option labels, file names, and the form's
|
|
19
|
+
* `yes` / `no` words for a checkbox (pass `fillWords(form.wording)` or the public form's wording).
|
|
20
|
+
*/
|
|
21
|
+
export declare function answerToText(field: InputBlock, value: AnswerValue, words: YesNoWords, fieldTypes?: Record<string, FieldTypeChecks>): string;
|
|
22
|
+
/** Checks the files given to one upload field: count, MIME type and size. */
|
|
23
|
+
export declare function checkFiles(field: InputBlock, files: FileLike[], limits?: FileLimits): boolean;
|
|
24
|
+
export type CheckOptions = {
|
|
25
|
+
/** Checks for app-registered field types. */
|
|
26
|
+
fieldTypes?: Record<string, FieldTypeChecks>;
|
|
27
|
+
/** Files per upload field key. */
|
|
28
|
+
files?: Record<string, FileLike[]>;
|
|
29
|
+
limits?: FileLimits;
|
|
30
|
+
/** Form words; default: the form's wording filled from the defaults. */
|
|
31
|
+
words?: FormWords;
|
|
32
|
+
};
|
|
33
|
+
export type CheckResult = {
|
|
34
|
+
/** Shown answers only, normalised. Upload fields are left out (the server adds them). */
|
|
35
|
+
answers: Answers;
|
|
36
|
+
fieldErrors: Record<string, string>;
|
|
37
|
+
/** Keys of shown upload fields that got files. */
|
|
38
|
+
fileKeys: string[];
|
|
39
|
+
ok: boolean;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Normalises raw answers, drops hidden ones (and, in steps, those of sections off the path), and
|
|
43
|
+
* reports a field error for every shown field that is required and empty or has a bad value.
|
|
44
|
+
*/
|
|
45
|
+
export declare function checkAnswers(form: FormLike, raw: Record<string, unknown>, options?: CheckOptions): CheckResult;
|
|
46
|
+
/** Section title, label and value per answered, shown field, as worded now. */
|
|
47
|
+
export declare function buildSnapshot(form: FormLike, answers: Answers, fieldTypes?: Record<string, FieldTypeChecks>): SnapshotRow[];
|