payload-dynamic-forms 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +461 -2
- package/dist/access.d.ts +7 -0
- package/dist/access.js +10 -0
- package/dist/access.js.map +1 -0
- package/dist/collections/attachments.d.ts +6 -0
- package/dist/collections/attachments.js +67 -0
- package/dist/collections/attachments.js.map +1 -0
- package/dist/collections/blocks.d.ts +27 -0
- package/dist/collections/blocks.js +495 -0
- package/dist/collections/blocks.js.map +1 -0
- package/dist/collections/forms.d.ts +3 -0
- package/dist/collections/forms.js +558 -0
- package/dist/collections/forms.js.map +1 -0
- package/dist/collections/overrides.d.ts +7 -0
- package/dist/collections/overrides.js +49 -0
- package/dist/collections/overrides.js.map +1 -0
- package/dist/collections/ruleFields.d.ts +6 -0
- package/dist/collections/ruleFields.js +97 -0
- package/dist/collections/ruleFields.js.map +1 -0
- package/dist/collections/submissions.d.ts +7 -0
- package/dist/collections/submissions.js +225 -0
- package/dist/collections/submissions.js.map +1 -0
- package/dist/delivery/run.d.ts +37 -0
- package/dist/delivery/run.js +234 -0
- package/dist/delivery/run.js.map +1 -0
- package/dist/delivery/webhook.d.ts +41 -0
- package/dist/delivery/webhook.js +120 -0
- package/dist/delivery/webhook.js.map +1 -0
- package/dist/endpoints/public.d.ts +4 -0
- package/dist/endpoints/public.js +27 -0
- package/dist/endpoints/public.js.map +1 -0
- package/dist/endpoints/resend.d.ts +4 -0
- package/dist/endpoints/resend.js +58 -0
- package/dist/endpoints/resend.js.map +1 -0
- package/dist/endpoints/submit.d.ts +4 -0
- package/dist/endpoints/submit.js +320 -0
- package/dist/endpoints/submit.js.map +1 -0
- package/dist/exports/shared.d.ts +13 -0
- package/dist/exports/shared.js +13 -0
- package/dist/exports/shared.js.map +1 -0
- package/dist/formBlock.d.ts +13 -0
- package/dist/formBlock.js +25 -0
- package/dist/formBlock.js.map +1 -0
- package/dist/hooks/resendTick.d.ts +8 -0
- package/dist/hooks/resendTick.js +55 -0
- package/dist/hooks/resendTick.js.map +1 -0
- package/dist/hooks/validateForm.d.ts +8 -0
- package/dist/hooks/validateForm.js +251 -0
- package/dist/hooks/validateForm.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +130 -0
- package/dist/index.js.map +1 -0
- package/dist/security/crypto.d.ts +9 -0
- package/dist/security/crypto.js +54 -0
- package/dist/security/crypto.js.map +1 -0
- package/dist/security/rateLimit.d.ts +13 -0
- package/dist/security/rateLimit.js +28 -0
- package/dist/security/rateLimit.js.map +1 -0
- package/dist/security/request.d.ts +3 -0
- package/dist/security/request.js +13 -0
- package/dist/security/request.js.map +1 -0
- package/dist/security/turnstile.d.ts +28 -0
- package/dist/security/turnstile.js +56 -0
- package/dist/security/turnstile.js.map +1 -0
- package/dist/server/forms.d.ts +73 -0
- package/dist/server/forms.js +215 -0
- package/dist/server/forms.js.map +1 -0
- package/dist/server/resend.d.ts +27 -0
- package/dist/server/resend.js +84 -0
- package/dist/server/resend.js.map +1 -0
- package/dist/shared/answers.d.ts +47 -0
- package/dist/shared/answers.js +282 -0
- package/dist/shared/answers.js.map +1 -0
- package/dist/shared/operators.d.ts +41 -0
- package/dist/shared/operators.js +54 -0
- package/dist/shared/operators.js.map +1 -0
- package/dist/shared/rules.d.ts +58 -0
- package/dist/shared/rules.js +324 -0
- package/dist/shared/rules.js.map +1 -0
- package/dist/shared/steps.d.ts +28 -0
- package/dist/shared/steps.js +109 -0
- package/dist/shared/steps.js.map +1 -0
- package/dist/shared/tokens.d.ts +25 -0
- package/dist/shared/tokens.js +96 -0
- package/dist/shared/tokens.js.map +1 -0
- package/dist/shared/types.d.ts +181 -0
- package/dist/shared/types.js +5 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/shared/whatsapp.d.ts +20 -0
- package/dist/shared/whatsapp.js +26 -0
- package/dist/shared/whatsapp.js.map +1 -0
- package/dist/shared/words.d.ts +9 -0
- package/dist/shared/words.js +41 -0
- package/dist/shared/words.js.map +1 -0
- package/dist/types.d.ts +182 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +114 -4
|
@@ -0,0 +1 @@
|
|
|
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;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A page block that picks one form. Add it to any blocks field; the app's renderer reads the
|
|
3
|
+
* form with `getPublicForm` or the public endpoint. It holds no words of its own.
|
|
4
|
+
*/ export function formBlock(options = {}) {
|
|
5
|
+
return {
|
|
6
|
+
slug: options.slug || 'formBlock',
|
|
7
|
+
...options.interfaceName ? {
|
|
8
|
+
interfaceName: options.interfaceName
|
|
9
|
+
} : {},
|
|
10
|
+
fields: [
|
|
11
|
+
{
|
|
12
|
+
name: 'form',
|
|
13
|
+
type: 'relationship',
|
|
14
|
+
relationTo: options.formsSlug || 'custom-forms',
|
|
15
|
+
required: true
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
labels: {
|
|
19
|
+
plural: 'Forms',
|
|
20
|
+
singular: 'Form'
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=formBlock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/formBlock.ts"],"sourcesContent":["import type { Block } from 'payload'\n\nexport type FormBlockOptions = {\n /** Forms collection slug (default `custom-forms`; use the plugin's `slugs.forms` if renamed). */\n formsSlug?: string\n interfaceName?: string\n /** Block slug, default `formBlock`. */\n slug?: string\n}\n\n/**\n * A page block that picks one form. Add it to any blocks field; the app's renderer reads the\n * form with `getPublicForm` or the public endpoint. It holds no words of its own.\n */\nexport function formBlock(options: FormBlockOptions = {}): Block {\n return {\n slug: options.slug || 'formBlock',\n ...(options.interfaceName ? { interfaceName: options.interfaceName } : {}),\n fields: [\n {\n name: 'form',\n type: 'relationship',\n relationTo: (options.formsSlug || 'custom-forms') as never,\n required: true,\n },\n ],\n labels: { plural: 'Forms', singular: 'Form' },\n }\n}\n"],"names":["formBlock","options","slug","interfaceName","fields","name","type","relationTo","formsSlug","required","labels","plural","singular"],"mappings":"AAUA;;;CAGC,GACD,OAAO,SAASA,UAAUC,UAA4B,CAAC,CAAC;IACtD,OAAO;QACLC,MAAMD,QAAQC,IAAI,IAAI;QACtB,GAAID,QAAQE,aAAa,GAAG;YAAEA,eAAeF,QAAQE,aAAa;QAAC,IAAI,CAAC,CAAC;QACzEC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNC,YAAaN,QAAQO,SAAS,IAAI;gBAClCC,UAAU;YACZ;SACD;QACDC,QAAQ;YAAEC,QAAQ;YAASC,UAAU;QAAO;IAC9C;AACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CollectionBeforeChangeHook } from 'payload';
|
|
2
|
+
import type { ResolvedOptions } from '../types.js';
|
|
3
|
+
export declare const RESEND_CONTEXT = "payloadFormsResend";
|
|
4
|
+
/**
|
|
5
|
+
* "Resend failed deliveries" in the sidebar: on save with the box ticked, run the failed outputs
|
|
6
|
+
* again (through `delivery.schedule` when set), add the tries to the log and save the box unticked.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resendTickHook(options: ResolvedOptions): CollectionBeforeChangeHook;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { resendFailedDeliveries } from '../server/resend.js';
|
|
2
|
+
export const RESEND_CONTEXT = 'payloadFormsResend';
|
|
3
|
+
/**
|
|
4
|
+
* "Resend failed deliveries" in the sidebar: on save with the box ticked, run the failed outputs
|
|
5
|
+
* again (through `delivery.schedule` when set), add the tries to the log and save the box unticked.
|
|
6
|
+
*/ export function resendTickHook(options) {
|
|
7
|
+
return async ({ context, data, operation, originalDoc, req })=>{
|
|
8
|
+
if (operation !== 'update' || !data?.resendFailed || context?.[RESEND_CONTEXT]) {
|
|
9
|
+
return data;
|
|
10
|
+
}
|
|
11
|
+
data.resendFailed = false;
|
|
12
|
+
const submission = {
|
|
13
|
+
...originalDoc,
|
|
14
|
+
...data
|
|
15
|
+
};
|
|
16
|
+
if (options.delivery?.schedule) {
|
|
17
|
+
const payload = req.payload;
|
|
18
|
+
options.delivery.schedule(async ()=>{
|
|
19
|
+
try {
|
|
20
|
+
const fresh = await payload.findByID({
|
|
21
|
+
id: submission.id,
|
|
22
|
+
collection: options.slugs.submissions,
|
|
23
|
+
depth: 0,
|
|
24
|
+
overrideAccess: true
|
|
25
|
+
});
|
|
26
|
+
const result = await resendFailedDeliveries(payload, fresh, req);
|
|
27
|
+
await payload.update({
|
|
28
|
+
id: submission.id,
|
|
29
|
+
collection: options.slugs.submissions,
|
|
30
|
+
context: {
|
|
31
|
+
[RESEND_CONTEXT]: true
|
|
32
|
+
},
|
|
33
|
+
data: {
|
|
34
|
+
deliveryLog: result.log,
|
|
35
|
+
status: result.status
|
|
36
|
+
},
|
|
37
|
+
overrideAccess: true
|
|
38
|
+
});
|
|
39
|
+
} catch (error) {
|
|
40
|
+
payload.logger.error({
|
|
41
|
+
err: error,
|
|
42
|
+
msg: 'payload-dynamic-forms: scheduled resend failed'
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return data;
|
|
47
|
+
}
|
|
48
|
+
const result = await resendFailedDeliveries(req.payload, submission, req);
|
|
49
|
+
data.deliveryLog = result.log;
|
|
50
|
+
data.status = result.status;
|
|
51
|
+
return data;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=resendTick.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/resendTick.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\nimport type { ResolvedOptions } from '../types.js'\n\nimport { resendFailedDeliveries, type SubmissionDoc } from '../server/resend.js'\n\nexport const RESEND_CONTEXT = 'payloadFormsResend'\n\n/**\n * \"Resend failed deliveries\" in the sidebar: on save with the box ticked, run the failed outputs\n * again (through `delivery.schedule` when set), add the tries to the log and save the box unticked.\n */\nexport function resendTickHook(options: ResolvedOptions): CollectionBeforeChangeHook {\n return async ({ context, data, operation, originalDoc, req }) => {\n if (operation !== 'update' || !data?.resendFailed || context?.[RESEND_CONTEXT]) {\n return data\n }\n data.resendFailed = false\n const submission = { ...(originalDoc as SubmissionDoc), ...(data as Partial<SubmissionDoc>) } as SubmissionDoc\n if (options.delivery?.schedule) {\n const payload = req.payload\n options.delivery.schedule(async () => {\n try {\n const fresh = (await payload.findByID({\n id: submission.id,\n collection: options.slugs.submissions as never,\n depth: 0,\n overrideAccess: true,\n })) as unknown as SubmissionDoc\n const result = await resendFailedDeliveries(payload, fresh, req)\n await payload.update({\n id: submission.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 } catch (error) {\n payload.logger.error({ err: error, msg: 'payload-dynamic-forms: scheduled resend failed' })\n }\n })\n return data\n }\n const result = await resendFailedDeliveries(req.payload, submission, req)\n data.deliveryLog = result.log\n data.status = result.status\n return data\n }\n}\n"],"names":["resendFailedDeliveries","RESEND_CONTEXT","resendTickHook","options","context","data","operation","originalDoc","req","resendFailed","submission","delivery","schedule","payload","fresh","findByID","id","collection","slugs","submissions","depth","overrideAccess","result","update","deliveryLog","log","status","error","logger","err","msg"],"mappings":"AAIA,SAASA,sBAAsB,QAA4B,sBAAqB;AAEhF,OAAO,MAAMC,iBAAiB,qBAAoB;AAElD;;;CAGC,GACD,OAAO,SAASC,eAAeC,OAAwB;IACrD,OAAO,OAAO,EAAEC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,GAAG,EAAE;QAC1D,IAAIF,cAAc,YAAY,CAACD,MAAMI,gBAAgBL,SAAS,CAACH,eAAe,EAAE;YAC9E,OAAOI;QACT;QACAA,KAAKI,YAAY,GAAG;QACpB,MAAMC,aAAa;YAAE,GAAIH,WAAW;YAAoB,GAAIF,IAAI;QAA4B;QAC5F,IAAIF,QAAQQ,QAAQ,EAAEC,UAAU;YAC9B,MAAMC,UAAUL,IAAIK,OAAO;YAC3BV,QAAQQ,QAAQ,CAACC,QAAQ,CAAC;gBACxB,IAAI;oBACF,MAAME,QAAS,MAAMD,QAAQE,QAAQ,CAAC;wBACpCC,IAAIN,WAAWM,EAAE;wBACjBC,YAAYd,QAAQe,KAAK,CAACC,WAAW;wBACrCC,OAAO;wBACPC,gBAAgB;oBAClB;oBACA,MAAMC,SAAS,MAAMtB,uBAAuBa,SAASC,OAAON;oBAC5D,MAAMK,QAAQU,MAAM,CAAC;wBACnBP,IAAIN,WAAWM,EAAE;wBACjBC,YAAYd,QAAQe,KAAK,CAACC,WAAW;wBACrCf,SAAS;4BAAE,CAACH,eAAe,EAAE;wBAAK;wBAClCI,MAAM;4BAAEmB,aAAaF,OAAOG,GAAG;4BAAEC,QAAQJ,OAAOI,MAAM;wBAAC;wBACvDL,gBAAgB;oBAClB;gBACF,EAAE,OAAOM,OAAO;oBACdd,QAAQe,MAAM,CAACD,KAAK,CAAC;wBAAEE,KAAKF;wBAAOG,KAAK;oBAAiD;gBAC3F;YACF;YACA,OAAOzB;QACT;QACA,MAAMiB,SAAS,MAAMtB,uBAAuBQ,IAAIK,OAAO,EAAEH,YAAYF;QACrEH,KAAKmB,WAAW,GAAGF,OAAOG,GAAG;QAC7BpB,KAAKqB,MAAM,GAAGJ,OAAOI,MAAM;QAC3B,OAAOrB;IACT;AACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CollectionBeforeValidateHook } from 'payload';
|
|
2
|
+
import type { ResolvedOptions } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Save checks: normalise keys to kebab-case, then refuse duplicate or reserved keys, rules and
|
|
5
|
+
* step targets naming missing keys, step loops, tokens in addresses, a visitor copy without an
|
|
6
|
+
* email field, uploads on a form that does not store, and webhooks that are not https.
|
|
7
|
+
*/
|
|
8
|
+
export declare function validateFormBeforeSave(options: ResolvedOptions): CollectionBeforeValidateHook;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { ValidationError } from 'payload';
|
|
2
|
+
import { isInputBlock, normalizeKey, splitRuleKey } from '../shared/rules.js';
|
|
3
|
+
function normalizeRuleKeys(group) {
|
|
4
|
+
for (const row of group?.rows || []){
|
|
5
|
+
if (!row || typeof row.fieldKey !== 'string') {
|
|
6
|
+
continue;
|
|
7
|
+
}
|
|
8
|
+
const { attribute, key } = splitRuleKey(row.fieldKey);
|
|
9
|
+
row.fieldKey = attribute ? `${key}.${attribute}` : key;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Finds a cycle in the step graph (next-step rules plus the fall-through to the next section). */ function findLoop(sections) {
|
|
13
|
+
const keys = sections.map((s)=>normalizeKey(s.key));
|
|
14
|
+
const edges = new Map();
|
|
15
|
+
keys.forEach((key, index)=>{
|
|
16
|
+
const out = new Set();
|
|
17
|
+
for (const rule of sections[index].nextStepRules || []){
|
|
18
|
+
const target = normalizeKey(rule?.targetSectionKey);
|
|
19
|
+
if (target && keys.includes(target)) {
|
|
20
|
+
out.add(target);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (keys[index + 1]) {
|
|
24
|
+
out.add(keys[index + 1]);
|
|
25
|
+
}
|
|
26
|
+
edges.set(key, out);
|
|
27
|
+
});
|
|
28
|
+
const state = new Map();
|
|
29
|
+
const stack = [];
|
|
30
|
+
const visit = (key)=>{
|
|
31
|
+
state.set(key, 'open');
|
|
32
|
+
stack.push(key);
|
|
33
|
+
for (const next of edges.get(key) || []){
|
|
34
|
+
if (state.get(next) === 'open') {
|
|
35
|
+
return [
|
|
36
|
+
...stack.slice(stack.indexOf(next)),
|
|
37
|
+
next
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
if (!state.has(next)) {
|
|
41
|
+
const found = visit(next);
|
|
42
|
+
if (found) {
|
|
43
|
+
return found;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
stack.pop();
|
|
48
|
+
state.set(key, 'done');
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
51
|
+
for (const key of keys){
|
|
52
|
+
if (!state.has(key)) {
|
|
53
|
+
const found = visit(key);
|
|
54
|
+
if (found) {
|
|
55
|
+
return found;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const TOKEN = /\{\{/;
|
|
62
|
+
/**
|
|
63
|
+
* Save checks: normalise keys to kebab-case, then refuse duplicate or reserved keys, rules and
|
|
64
|
+
* step targets naming missing keys, step loops, tokens in addresses, a visitor copy without an
|
|
65
|
+
* email field, uploads on a form that does not store, and webhooks that are not https.
|
|
66
|
+
*/ export function validateFormBeforeSave(options) {
|
|
67
|
+
const reserved = [
|
|
68
|
+
options.honeypotField,
|
|
69
|
+
options.turnstile.fieldName
|
|
70
|
+
].map((name)=>name.trim().toLowerCase());
|
|
71
|
+
return ({ collection, data, originalDoc, req })=>{
|
|
72
|
+
if (!data) {
|
|
73
|
+
return data;
|
|
74
|
+
}
|
|
75
|
+
const form = data;
|
|
76
|
+
const original = originalDoc || {};
|
|
77
|
+
const issues = [];
|
|
78
|
+
const sections = Array.isArray(form.sections) ? form.sections : original.sections || [];
|
|
79
|
+
// Keys, duplicates, reserved names.
|
|
80
|
+
const sectionKeys = new Set();
|
|
81
|
+
const fieldTypes = new Map();
|
|
82
|
+
sections.forEach((section, s)=>{
|
|
83
|
+
if (!section) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
section.key = normalizeKey(section.key);
|
|
87
|
+
if (section.key && sectionKeys.has(section.key)) {
|
|
88
|
+
issues.push({
|
|
89
|
+
message: `Two sections use the key "${section.key}".`,
|
|
90
|
+
path: `sections.${s}.key`
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
sectionKeys.add(section.key || '');
|
|
94
|
+
(section.fields || []).forEach((field, f)=>{
|
|
95
|
+
if (!isInputBlock(field)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const raw = typeof field.fieldKey === 'string' ? field.fieldKey.trim().toLowerCase() : '';
|
|
99
|
+
const key = normalizeKey(field.fieldKey);
|
|
100
|
+
field.fieldKey = key;
|
|
101
|
+
const path = `sections.${s}.fields.${f}.fieldKey`;
|
|
102
|
+
if (reserved.includes(raw) || reserved.includes(key)) {
|
|
103
|
+
issues.push({
|
|
104
|
+
message: `The key "${raw || key}" is reserved for the spam checks. Pick another key.`,
|
|
105
|
+
path
|
|
106
|
+
});
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (key && fieldTypes.has(key)) {
|
|
110
|
+
issues.push({
|
|
111
|
+
message: `Two fields use the key "${key}".`,
|
|
112
|
+
path
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (key) {
|
|
117
|
+
fieldTypes.set(key, field.blockType);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
// Rules and step targets.
|
|
122
|
+
const checkRules = (group, path)=>{
|
|
123
|
+
normalizeRuleKeys(group);
|
|
124
|
+
(group?.rows || []).forEach((row, r)=>{
|
|
125
|
+
const { key } = splitRuleKey(row?.fieldKey);
|
|
126
|
+
if (key && !fieldTypes.has(key)) {
|
|
127
|
+
issues.push({
|
|
128
|
+
message: `A rule names the field key "${key}", which this form does not have.`,
|
|
129
|
+
path: `${path}.rows.${r}.fieldKey`
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
sections.forEach((section, s)=>{
|
|
135
|
+
if (!section) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
checkRules(section.visibilityRules, `sections.${s}.visibilityRules`);
|
|
139
|
+
(section.fields || []).forEach((field, f)=>{
|
|
140
|
+
if (isInputBlock(field)) {
|
|
141
|
+
checkRules(field.visibilityRules, `sections.${s}.fields.${f}.visibilityRules`);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
(section.nextStepRules || []).forEach((rule, r)=>{
|
|
145
|
+
if (!rule) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
checkRules(rule, `sections.${s}.nextStepRules.${r}`);
|
|
149
|
+
rule.targetSectionKey = normalizeKey(rule.targetSectionKey);
|
|
150
|
+
if (rule.targetSectionKey && !sectionKeys.has(rule.targetSectionKey)) {
|
|
151
|
+
issues.push({
|
|
152
|
+
message: `A next-step rule targets the section key "${rule.targetSectionKey}", which this form does not have.`,
|
|
153
|
+
path: `sections.${s}.nextStepRules.${r}.targetSectionKey`
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
const loop = findLoop(sections.filter(Boolean));
|
|
159
|
+
if (loop) {
|
|
160
|
+
issues.push({
|
|
161
|
+
message: `The next-step rules make a loop: ${loop.join(' → ')}.`,
|
|
162
|
+
path: 'sections'
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// Delivery.
|
|
166
|
+
const delivery = form.delivery ?? original.delivery ?? {};
|
|
167
|
+
const store = delivery.store !== false;
|
|
168
|
+
if (!store) {
|
|
169
|
+
for (const [key, type] of fieldTypes){
|
|
170
|
+
if (type === 'fileUpload') {
|
|
171
|
+
issues.push({
|
|
172
|
+
message: `The upload field "${key}" needs "Store submissions" on.`,
|
|
173
|
+
path: 'delivery.store'
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
;
|
|
179
|
+
(delivery.emails || []).forEach((email, e)=>{
|
|
180
|
+
if (!email) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
for (const name of [
|
|
184
|
+
'to',
|
|
185
|
+
'cc',
|
|
186
|
+
'bcc',
|
|
187
|
+
'from'
|
|
188
|
+
]){
|
|
189
|
+
const value = email[name];
|
|
190
|
+
if (typeof value === 'string' && TOKEN.test(value)) {
|
|
191
|
+
issues.push({
|
|
192
|
+
message: `Email "${email.label || e + 1}": ${name} can't hold {{tokens}}. To write to the visitor, use a visitor copy instead.`,
|
|
193
|
+
path: `delivery.emails.${e}.${name}`
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (email.kind === 'visitor') {
|
|
198
|
+
const key = normalizeKey(email.visitorField);
|
|
199
|
+
email.visitorField = key;
|
|
200
|
+
if (fieldTypes.get(key) !== 'email') {
|
|
201
|
+
issues.push({
|
|
202
|
+
message: `Email "${email.label || e + 1}": the visitor copy needs the key of an email field ("${key}" is not one).`,
|
|
203
|
+
path: `delivery.emails.${e}.visitorField`
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
} else if (!email.to?.trim()) {
|
|
207
|
+
issues.push({
|
|
208
|
+
message: `Email "${email.label || e + 1}": add at least one address in "to".`,
|
|
209
|
+
path: `delivery.emails.${e}.to`
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
if (email.replyToField) {
|
|
213
|
+
const key = normalizeKey(email.replyToField);
|
|
214
|
+
email.replyToField = key;
|
|
215
|
+
if (fieldTypes.get(key) !== 'email') {
|
|
216
|
+
issues.push({
|
|
217
|
+
message: `Email "${email.label || e + 1}": reply-to needs the key of an email field ("${key}" is not one).`,
|
|
218
|
+
path: `delivery.emails.${e}.replyToField`
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
(delivery.webhooks || []).forEach((hook, w)=>{
|
|
224
|
+
if (!hook) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
let ok = false;
|
|
228
|
+
try {
|
|
229
|
+
ok = new URL(String(hook.url || '')).protocol === 'https:';
|
|
230
|
+
} catch {
|
|
231
|
+
ok = false;
|
|
232
|
+
}
|
|
233
|
+
if (!ok) {
|
|
234
|
+
issues.push({
|
|
235
|
+
message: `Webhook "${hook.label || w + 1}": the URL ${hook.url || '(empty)'} must start with https://.`,
|
|
236
|
+
path: `delivery.webhooks.${w}.url`
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
if (issues.length) {
|
|
241
|
+
throw new ValidationError({
|
|
242
|
+
collection: collection.slug,
|
|
243
|
+
errors: issues,
|
|
244
|
+
req
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return data;
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
//# sourceMappingURL=validateForm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/validateForm.ts"],"sourcesContent":["import type { CollectionBeforeValidateHook } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { FieldBlock, FormSection, InputBlock, RuleGroup } from '../shared/types.js'\nimport type { ResolvedOptions } from '../types.js'\n\nimport { isInputBlock, normalizeKey, splitRuleKey } from '../shared/rules.js'\n\ntype Issue = { message: string; path: string }\n\ntype EmailOutput = {\n bcc?: null | string\n cc?: null | string\n from?: null | string\n kind?: 'team' | 'visitor' | null\n label?: null | string\n replyToField?: null | string\n to?: null | string\n visitorField?: null | string\n}\n\ntype Webhook = { label?: null | string; url?: null | string }\n\ntype FormData = {\n delivery?: { emails?: EmailOutput[] | null; store?: boolean | null; webhooks?: null | Webhook[] } | null\n sections?: FormSection[] | null\n}\n\nfunction normalizeRuleKeys(group: null | RuleGroup | undefined): void {\n for (const row of group?.rows || []) {\n if (!row || typeof row.fieldKey !== 'string') {\n continue\n }\n const { attribute, key } = splitRuleKey(row.fieldKey)\n row.fieldKey = attribute ? `${key}.${attribute}` : key\n }\n}\n\n/** Finds a cycle in the step graph (next-step rules plus the fall-through to the next section). */\nfunction findLoop(sections: FormSection[]): null | string[] {\n const keys = sections.map((s) => normalizeKey(s.key))\n const edges = new Map<string, Set<string>>()\n keys.forEach((key, index) => {\n const out = new Set<string>()\n for (const rule of sections[index].nextStepRules || []) {\n const target = normalizeKey(rule?.targetSectionKey)\n if (target && keys.includes(target)) {\n out.add(target)\n }\n }\n if (keys[index + 1]) {\n out.add(keys[index + 1])\n }\n edges.set(key, out)\n })\n const state = new Map<string, 'done' | 'open'>()\n const stack: string[] = []\n const visit = (key: string): null | string[] => {\n state.set(key, 'open')\n stack.push(key)\n for (const next of edges.get(key) || []) {\n if (state.get(next) === 'open') {\n return [...stack.slice(stack.indexOf(next)), next]\n }\n if (!state.has(next)) {\n const found = visit(next)\n if (found) {\n return found\n }\n }\n }\n stack.pop()\n state.set(key, 'done')\n return null\n }\n for (const key of keys) {\n if (!state.has(key)) {\n const found = visit(key)\n if (found) {\n return found\n }\n }\n }\n return null\n}\n\nconst TOKEN = /\\{\\{/\n\n/**\n * Save checks: normalise keys to kebab-case, then refuse duplicate or reserved keys, rules and\n * step targets naming missing keys, step loops, tokens in addresses, a visitor copy without an\n * email field, uploads on a form that does not store, and webhooks that are not https.\n */\nexport function validateFormBeforeSave(options: ResolvedOptions): CollectionBeforeValidateHook {\n const reserved = [options.honeypotField, options.turnstile.fieldName].map((name) => name.trim().toLowerCase())\n\n return ({ collection, data, originalDoc, req }) => {\n if (!data) {\n return data\n }\n const form = data as FormData\n const original = (originalDoc || {}) as FormData\n const issues: Issue[] = []\n const sections = Array.isArray(form.sections) ? form.sections : original.sections || []\n\n // Keys, duplicates, reserved names.\n const sectionKeys = new Set<string>()\n const fieldTypes = new Map<string, string>()\n sections.forEach((section, s) => {\n if (!section) {\n return\n }\n section.key = normalizeKey(section.key)\n if (section.key && sectionKeys.has(section.key)) {\n issues.push({ message: `Two sections use the key \"${section.key}\".`, path: `sections.${s}.key` })\n }\n sectionKeys.add(section.key || '')\n ;(section.fields || []).forEach((field: FieldBlock, f) => {\n if (!isInputBlock(field)) {\n return\n }\n const raw = typeof field.fieldKey === 'string' ? field.fieldKey.trim().toLowerCase() : ''\n const key = normalizeKey(field.fieldKey)\n field.fieldKey = key\n const path = `sections.${s}.fields.${f}.fieldKey`\n if (reserved.includes(raw) || reserved.includes(key)) {\n issues.push({ message: `The key \"${raw || key}\" is reserved for the spam checks. Pick another key.`, path })\n return\n }\n if (key && fieldTypes.has(key)) {\n issues.push({ message: `Two fields use the key \"${key}\".`, path })\n return\n }\n if (key) {\n fieldTypes.set(key, field.blockType)\n }\n })\n })\n\n // Rules and step targets.\n const checkRules = (group: null | RuleGroup | undefined, path: string) => {\n normalizeRuleKeys(group)\n ;(group?.rows || []).forEach((row, r) => {\n const { key } = splitRuleKey(row?.fieldKey)\n if (key && !fieldTypes.has(key)) {\n issues.push({ message: `A rule names the field key \"${key}\", which this form does not have.`, path: `${path}.rows.${r}.fieldKey` })\n }\n })\n }\n sections.forEach((section, s) => {\n if (!section) {\n return\n }\n checkRules(section.visibilityRules, `sections.${s}.visibilityRules`)\n ;(section.fields || []).forEach((field: FieldBlock, f) => {\n if (isInputBlock(field)) {\n checkRules((field).visibilityRules, `sections.${s}.fields.${f}.visibilityRules`)\n }\n })\n ;(section.nextStepRules || []).forEach((rule, r) => {\n if (!rule) {\n return\n }\n checkRules(rule, `sections.${s}.nextStepRules.${r}`)\n rule.targetSectionKey = normalizeKey(rule.targetSectionKey)\n if (rule.targetSectionKey && !sectionKeys.has(rule.targetSectionKey)) {\n issues.push({\n message: `A next-step rule targets the section key \"${rule.targetSectionKey}\", which this form does not have.`,\n path: `sections.${s}.nextStepRules.${r}.targetSectionKey`,\n })\n }\n })\n })\n const loop = findLoop(sections.filter(Boolean))\n if (loop) {\n issues.push({ message: `The next-step rules make a loop: ${loop.join(' → ')}.`, path: 'sections' })\n }\n\n // Delivery.\n const delivery = form.delivery ?? original.delivery ?? {}\n const store = delivery.store !== false\n if (!store) {\n for (const [key, type] of fieldTypes) {\n if (type === 'fileUpload') {\n issues.push({ message: `The upload field \"${key}\" needs \"Store submissions\" on.`, path: 'delivery.store' })\n }\n }\n }\n ;(delivery.emails || []).forEach((email, e) => {\n if (!email) {\n return\n }\n for (const name of ['to', 'cc', 'bcc', 'from'] as const) {\n const value = email[name]\n if (typeof value === 'string' && TOKEN.test(value)) {\n issues.push({\n message: `Email \"${email.label || e + 1}\": ${name} can't hold {{tokens}}. To write to the visitor, use a visitor copy instead.`,\n path: `delivery.emails.${e}.${name}`,\n })\n }\n }\n if (email.kind === 'visitor') {\n const key = normalizeKey(email.visitorField)\n email.visitorField = key\n if (fieldTypes.get(key) !== 'email') {\n issues.push({\n message: `Email \"${email.label || e + 1}\": the visitor copy needs the key of an email field (\"${key}\" is not one).`,\n path: `delivery.emails.${e}.visitorField`,\n })\n }\n } else if (!email.to?.trim()) {\n issues.push({ message: `Email \"${email.label || e + 1}\": add at least one address in \"to\".`, path: `delivery.emails.${e}.to` })\n }\n if (email.replyToField) {\n const key = normalizeKey(email.replyToField)\n email.replyToField = key\n if (fieldTypes.get(key) !== 'email') {\n issues.push({ message: `Email \"${email.label || e + 1}\": reply-to needs the key of an email field (\"${key}\" is not one).`, path: `delivery.emails.${e}.replyToField` })\n }\n }\n })\n ;(delivery.webhooks || []).forEach((hook, w) => {\n if (!hook) {\n return\n }\n let ok = false\n try {\n ok = new URL(String(hook.url || '')).protocol === 'https:'\n } catch {\n ok = false\n }\n if (!ok) {\n issues.push({ message: `Webhook \"${hook.label || w + 1}\": the URL ${hook.url || '(empty)'} must start with https://.`, path: `delivery.webhooks.${w}.url` })\n }\n })\n\n if (issues.length) {\n throw new ValidationError({ collection: collection.slug, errors: issues, req })\n }\n return data\n }\n}\n"],"names":["ValidationError","isInputBlock","normalizeKey","splitRuleKey","normalizeRuleKeys","group","row","rows","fieldKey","attribute","key","findLoop","sections","keys","map","s","edges","Map","forEach","index","out","Set","rule","nextStepRules","target","targetSectionKey","includes","add","set","state","stack","visit","push","next","get","slice","indexOf","has","found","pop","TOKEN","validateFormBeforeSave","options","reserved","honeypotField","turnstile","fieldName","name","trim","toLowerCase","collection","data","originalDoc","req","form","original","issues","Array","isArray","sectionKeys","fieldTypes","section","message","path","fields","field","f","raw","blockType","checkRules","r","visibilityRules","loop","filter","Boolean","join","delivery","store","type","emails","email","e","value","test","label","kind","visitorField","to","replyToField","webhooks","hook","w","ok","URL","String","url","protocol","length","slug","errors"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Config } from 'payload';
|
|
2
|
+
import type { FormsPluginOptions, ResolvedOptions } from './types.js';
|
|
3
|
+
export declare const DEFAULT_SLUGS: {
|
|
4
|
+
readonly attachments: "custom-form-attachments";
|
|
5
|
+
readonly forms: "custom-forms";
|
|
6
|
+
readonly submissions: "custom-form-submissions";
|
|
7
|
+
};
|
|
8
|
+
export declare function resolveOptions(options?: FormsPluginOptions): ResolvedOptions;
|
|
9
|
+
/**
|
|
10
|
+
* Adds forms, submissions and private attachments, with their endpoints, to a Payload config.
|
|
11
|
+
* `formsPlugin({ enabled: false })` returns the config untouched.
|
|
12
|
+
*/
|
|
13
|
+
export declare const formsPlugin: (pluginOptions?: FormsPluginOptions) => (config: Config) => Config;
|
|
14
|
+
export { signWebhookBody } from './delivery/webhook.js';
|
|
15
|
+
export { formBlock, type FormBlockOptions } from './formBlock.js';
|
|
16
|
+
export { getPublicForm, loadPublishedForm } from './server/forms.js';
|
|
17
|
+
export type { Answers, AnswerValue, ChoiceOption, FieldBlock, FieldTypeChecks, FieldWords, FormSection, FormWords, InputBlock, PublicForm, RuleGroup, RuleRow, SnapshotRow, SubmissionStatus, SubmitResult, } from './shared/types.js';
|
|
18
|
+
export type { CollectionOverride, FieldTypeDefinition, FormsPluginOptions, OptionSource, ResolvedOptions } from './types.js';
|