feat: add DB operations and CLI wiring for HK parish import

upsertChurch() handles matched churches (replace schedules atomically
via $transaction, update contact fields if null) and new churches
(create with source='diocese-hk', lat/lng=0 for later geocoding).
main() wires up CLI args, file reading, matching loop, and summary.
Guards main() call with ESM import.meta.url check to prevent execution
on import during tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
albertfj114
2026-04-03 16:27:02 -04:00
parent 8075072c24
commit 92265cf27f

View File

@@ -437,3 +437,148 @@ export function findMatch(
return null; return null;
} }
// ─── DB Operations ────────────────────────────────────────────────────────────
async function upsertChurch(
entry: ParsedEntry,
matched: ExistingChurch | null,
dryRun: boolean,
stats: ImportStats
): Promise<void> {
const tag = matched ? `[MATCH] ${matched.name}${entry.locationName}` : `[NEW] ${entry.locationName}`;
const schedCount = entry.schedules.length;
if (dryRun) {
console.log(tag);
if (!matched && entry.address) console.log(` Address: ${entry.address}`);
console.log(` ${schedCount} schedules`);
if (matched) stats.matched++; else stats.created++;
stats.schedulesWritten += schedCount;
return;
}
if (matched) {
const update: Record<string, string> = {};
if (!matched.phone && entry.phone) update.phone = entry.phone;
if (!matched.email && entry.email) update.email = entry.email;
await prisma.$transaction(async tx => {
if (Object.keys(update).length > 0) {
await tx.church.update({ where: { id: matched.id }, data: update });
}
await tx.massSchedule.deleteMany({ where: { churchId: matched.id } });
if (entry.schedules.length > 0) {
await tx.massSchedule.createMany({
data: entry.schedules.map(s => ({
churchId: matched.id,
dayOfWeek: s.dayOfWeek,
time: s.time,
language: s.language,
notes: s.notes ?? null,
})),
});
}
});
stats.matched++;
} else {
const newChurch = await prisma.church.create({
data: {
name: entry.locationName,
country: 'HK',
source: 'diocese-hk',
address: entry.address ?? undefined,
phone: entry.phone ?? undefined,
email: entry.email ?? undefined,
latitude: 0,
longitude: 0,
hasWebsite: false,
},
});
if (entry.schedules.length > 0) {
await prisma.massSchedule.createMany({
data: entry.schedules.map(s => ({
churchId: newChurch.id,
dayOfWeek: s.dayOfWeek,
time: s.time,
language: s.language,
notes: s.notes ?? null,
})),
});
}
stats.created++;
}
stats.schedulesWritten += schedCount;
console.log(tag);
}
// ─── Main ─────────────────────────────────────────────────────────────────────
async function main() {
const args = process.argv.slice(2);
const dryRun = args.includes('--dry-run');
const fileArgIdx = args.indexOf('--file');
const filePath = fileArgIdx >= 0 ? args[fileArgIdx + 1] : path.resolve(process.cwd(), 'scripts/hk-parishes.txt');
console.log(`\n${'='.repeat(60)}`);
console.log(`HK Diocese Parish Import`);
console.log(`File: ${filePath}`);
console.log(`Dry run: ${dryRun ? 'Yes' : 'No'}`);
console.log(`${'='.repeat(60)}\n`);
const raw = fs.readFileSync(filePath, 'utf-8');
const entryStrings = splitEntries(raw);
console.log(`Found ${entryStrings.length} entries in file\n`);
const existing = await prisma.church.findMany({
where: { country: 'HK' },
select: { id: true, name: true, address: true, phone: true, email: true },
});
console.log(`Loaded ${existing.length} existing HK churches\n`);
const stats: ImportStats = { matched: 0, created: 0, schedulesWritten: 0, skipped: 0 };
for (const entryStr of entryStrings) {
let entry: ParsedEntry;
try {
entry = parseEntry(entryStr);
} catch (err) {
console.warn(`[SKIP] Failed to parse entry: ${(err as Error).message}`);
stats.skipped++;
continue;
}
if (!entry.locationName || entry.locationName === 'Unknown') {
stats.skipped++;
continue;
}
const matched = findMatch(entry.locationName, entry.address, existing);
await upsertChurch(entry, matched, dryRun, stats);
}
console.log(`\n${'='.repeat(60)}`);
console.log(`Import Summary`);
console.log(`${'='.repeat(60)}`);
console.log(`Matched existing: ${stats.matched}`);
console.log(`New churches: ${stats.created}`);
console.log(`Skipped: ${stats.skipped}`);
console.log(`Schedules written: ${stats.schedulesWritten}`);
console.log(`${'='.repeat(60)}\n`);
await prisma.$disconnect();
await pool.end();
}
// Only run when executed directly (not imported by tests)
import { fileURLToPath } from 'url';
if (process.argv[1] === fileURLToPath(import.meta.url)) {
main().catch(err => {
console.error('Fatal error:', err);
process.exit(1);
});
}