import { readFile } from "node:fs/promises"; import { createHash } from "node:crypto"; import { MongoClient } from "mongodb"; function parseCsv(text) { const rows = []; let row = []; let field = ""; let quoted = false; for (let index = 0; index < text.length; index++) { const char = text[index]; if (char === '"') { if (quoted && text[index + 1] === '"') { field += '"'; index++; } else quoted = !quoted; } else if (char === "," && !quoted) { row.push(field); field = ""; } else if ((char === "\n" || char === "\r") && !quoted) { if (char === "\r" && text[index + 1] === "\n") index++; row.push(field); if (row.some(Boolean)) rows.push(row); row = []; field = ""; } else field += char; } if (field || row.length) { row.push(field); rows.push(row); } const headers = rows.shift().map((header) => header.replace(/^\uFEFF/, "").trim()); return rows.map((values) => Object.fromEntries(headers.map((header, index) => [header, (values[index] || "").trim()]))); } const value = (row, name) => row[name] || ""; const personId = (key, role) => createHash("sha256").update(`${key}:${role}`).digest("hex").slice(0, 24); function person(row, key, role, number, lastName) { const prefix = `${role} ${number}`; const firstName = value(row, `${prefix} First Name`); if (!firstName) return null; return { id: personId(key, `${role}-${number}`), firstName, lastName, birthday: value(row, `${prefix} Birthday`) || undefined, email: (value(row, `${prefix} Email Address`) || value(row, `${prefix} Email Address `)).toLowerCase() || undefined, phone: value(row, `${prefix} Mobile Phone Number`) || undefined, }; } function additionalAdults(row, key, lastName) { const match = value(row, "Additional Details").match(/Additional adult household members:\s*([^;]+)/i); if (!match) return []; return match[1].split(",").map((name) => name.trim()).filter(Boolean).map((firstName, index) => ({ id: personId(key, `additional-${index}`), firstName, lastName, })); } const [joinCode, csvPath] = process.argv.slice(2); if (!joinCode || !csvPath) { console.error("Usage: yarn import:contacts "); process.exit(1); } const client = new MongoClient(process.env.MONGODB_URI || "mongodb://127.0.0.1:27017"); await client.connect(); try { const database = client.db(process.env.MONGODB_DB || "kinship_directory"); const organization = await database.collection("organizations").findOne({ joinCode: joinCode.toUpperCase() }); if (!organization) throw new Error(`Organization ${joinCode} was not found.`); const rows = parseCsv(await readFile(csvPath, "utf8")); const users = await database.collection("users").find({ organizationId: organization._id }).toArray(); const familyByEmail = new Map(users.map((user) => [String(user.email).toLowerCase(), user.familyId])); let created = 0; let updated = 0; let adultsImported = 0; let childrenImported = 0; for (const [index, row] of rows.entries()) { const lastName = value(row, "Family Last Name"); if (!lastName) continue; const key = `${joinCode.toUpperCase()}:${index + 2}:${lastName}:${value(row, "Address")}`; const adults = [person(row, key, "Adult", 1, lastName), person(row, key, "Adult", 2, lastName)].filter(Boolean); adults.push(...additionalAdults(row, key, lastName)); const children = [1, 2, 3, 4].map((number) => person(row, key, "Child", number, lastName)).filter(Boolean); const accountFamilyId = adults.map((adult) => adult.email?.toLowerCase()).filter(Boolean).map((email) => familyByEmail.get(email)).find(Boolean); const address = [value(row, "Address"), value(row, "Address Line 2")].filter(Boolean).join(", "); const document = { organizationId: organization._id, familyName: lastName, address: address || undefined, city: value(row, "City") || undefined, state: value(row, "State") || undefined, postalCode: value(row, "ZIP Code") || undefined, homePhone: value(row, "Family Phone") || undefined, adults, children, importKey: key, importDetails: value(row, "Additional Details") || undefined, anniversary: value(row, "Anniversary Date") || undefined, updatedAt: new Date(), }; const existing = accountFamilyId ? await database.collection("families").findOne({ _id: accountFamilyId, organizationId: organization._id }) : await database.collection("families").findOne({ organizationId: organization._id, importKey: key }); if (existing) { await database.collection("families").updateOne({ _id: existing._id }, { $set: document }); updated++; } else { await database.collection("families").insertOne({ ...document, createdAt: new Date() }); created++; } adultsImported += adults.length; childrenImported += children.length; } console.log(JSON.stringify({ organization: organization.name, rows: rows.length, created, updated, adults: adultsImported, children: childrenImported }, null, 2)); } finally { await client.close(); }