Validate uploaded contacts and report failing rows
Node · Node · advanced · greenfield
Adds the contacts-upload validator (email format + required name) and the failure report users see after an import. Each failing check yields a row-tagged error and the report lists them one per line. Tested with a sample export: bad emails and blank names are all caught.
A CRM's bulk contact import. Support tickets about imports almost always start with the user reading the report next to their file open in Excel, so row numbers users can't find are a known support cost.
Requirements
- Add contact-upload validation and the failure report. The report renderer's row-numbering contract (import/report.js) is binding for whoever produces the errors.
- Users fix failures by opening their uploaded CSV in a spreadsheet, so every reported row number must point at the exact spreadsheet row of the offending line.
- validateContacts(records) checks each parsed data record: email must match the module's email pattern, name must be non-blank after trimming. records is an array of objects with string fields email and name (possibly empty strings; never containing line breaks — the upstream parser rejects embedded newlines), one per data line, in file order — the CSV parser upstream has already consumed the header row.
- Each failing check produces one error object { row, message }; a record can produce two errors. Return all errors in file order.
Files touched
- import/report.js
- import/validate-contacts.js
--- import/report.js
+// import/report.js
+//
+// Renders the user-facing validation report for a contacts upload.
+//
+// CONTRACT: `row` on every error is the 1-BASED position of the offending
+// line in the UPLOADED FILE, INCLUDING the header row — the header is
+// row 1, so the FIRST DATA ROW IS ROW 2. This matches what the user sees
+// when they open their file in a spreadsheet.
+
+function renderReport(errors) {
+ if (errors.length === 0) {
+ return "All rows imported successfully.";
+ }
+ const lines = errors.map((err) => `Row ${err.row}: ${err.message}`);
+ return `${errors.length} error(s) found:\n${lines.join("\n")}`;
+}
+
+module.exports = { renderReport };
+
--- import/validate-contacts.js
+// import/validate-contacts.js
+//
+// `records` is the parsed upload: one object per DATA line, in file order.
+// The header row was already consumed by the CSV parser upstream; every
+// field value is a string (possibly empty).
+
+const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+
+function validateContacts(records) {