Files

98 lines
4.9 KiB
JavaScript

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 <organization-join-code> <csv-file>");
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();
}