fix(forms): link by an email collected as a form field, not only submitter_email
Root cause: association only ever looked at the dedicated submitter_email
(public 'your email' box / auth email). A form that asks for email as an
ordinary field stored it in answers, so submitter_email stayed null and
nothing matched -- 'Re-link' reported 0 ('already linked') and the
submission never appeared under the member. New pure deriveSubmitterEmail
(explicit > email-type field answer > any email-looking answer; trimmed
lowercase) + 5 tests. submitForm now derives + persists submitter_email
from answers; relinkAllSubmissions re-derives per submission from its
form_snapshot and backfills member_id + submitter_email for existing
rows. Suite 469.
This commit is contained in:
@@ -101,6 +101,36 @@ export function escapeLike(s: string): string {
|
||||
return s.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_')
|
||||
}
|
||||
|
||||
const FORMS_EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
|
||||
// The email used to associate a submission with a member. The dedicated
|
||||
// `submitter_email` (the public "your email" box, or the signed-in user's
|
||||
// address) wins; otherwise fall back to the answer of an email-type field,
|
||||
// then any answer that is itself a valid email. Without this, a form that
|
||||
// collects email as an ordinary field never populates submitter_email and
|
||||
// so never links. Normalized to trimmed lowercase.
|
||||
export function deriveSubmitterEmail(
|
||||
fields: Array<{ key: string; type: string }>,
|
||||
answers: Record<string, unknown> | null | undefined,
|
||||
explicit?: string | null,
|
||||
): string | null {
|
||||
const norm = (v: unknown) => (typeof v === 'string' ? v.trim().toLowerCase() : '')
|
||||
const e = norm(explicit)
|
||||
if (e && FORMS_EMAIL_RE.test(e)) return e
|
||||
const a = answers ?? {}
|
||||
for (const f of fields) {
|
||||
if (f.type === 'email') {
|
||||
const v = norm(a[f.key])
|
||||
if (FORMS_EMAIL_RE.test(v)) return v
|
||||
}
|
||||
}
|
||||
for (const k of Object.keys(a)) {
|
||||
const v = norm(a[k])
|
||||
if (FORMS_EMAIL_RE.test(v)) return v
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// When an email matches more than one member row in a space (e.g. a manually
|
||||
// added duplicate), associate deterministically with the earliest-joined one
|
||||
// so the result is stable and reproducible.
|
||||
|
||||
Reference in New Issue
Block a user