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:
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user