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

Files touched

--- 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) {

Review this PR

Node practice