Files
ScraperControl/scripts/debug/import-to-neon.ts
Albert 2c51513851 chore: sync with Gitea master and restore local-only files
Reset local main to gitea/master (new source of truth) and restored
local-only files: web scrapers, admin dashboard, ChromaDB integration,
debug scripts, and utility libraries that aren't tracked in Gitea.

Gitea master adds: discovermass, buscarmisas-network, hk-parishes,
bohosluzby, kerknet, gottesdienstzeiten, miserend importers,
ClaimRequest model, forward geocoding, heartbeat healthcheck.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 19:11:22 -04:00

233 lines
7.4 KiB
TypeScript

#!/usr/bin/env tsx
/**
* Import churches from JSON export to Neon database
* Run this LOCALLY (uses DATABASE_URL from .env pointing to Neon)
*/
import { PrismaClient } from '@prisma/client';
import fs from 'fs/promises';
import path from 'path';
interface ChurchExport {
id: string;
name: string;
latitude: number;
longitude: number;
country: string;
massSchedules?: any[];
confessionSchedules?: any[];
adorationSchedules?: any[];
[key: string]: any;
}
async function main() {
const country = process.argv[2] || 'PL';
const mode = process.argv[3] || 'dry-run';
const dryRun = mode === 'dry-run';
console.log(`📤 Importing ${country} data to Neon...`);
console.log(`DATABASE_URL: ${process.env.DATABASE_URL?.replace(/:[^:@]+@/, ':****@')}`);
if (dryRun) {
console.log('🔍 DRY RUN MODE - No data will be written');
}
// Read export file
const exportFile = path.join(process.cwd(), `export-${country}.json`);
try {
const data: ChurchExport[] = JSON.parse(await fs.readFile(exportFile, 'utf-8'));
console.log(`Loaded ${data.length} churches from export file`);
// Connect to Neon
const prisma = new PrismaClient();
try {
await prisma.$connect();
console.log('✅ Connected to Neon database');
let inserted = 0;
let updated = 0;
let skipped = 0;
let errors = 0;
let totalMassSchedules = 0;
let totalConfessionSchedules = 0;
let totalAdorationSchedules = 0;
for (const church of data) {
try {
const massSchedules = church.massSchedules || [];
const confessionSchedules = church.confessionSchedules || [];
const adorationSchedules = church.adorationSchedules || [];
// Remove relations and ids
delete church.massSchedules;
delete church.confessionSchedules;
delete church.adorationSchedules;
delete church.id;
if (!dryRun) {
// Check if church already exists
const existing = await prisma.churches.findFirst({
where: {
latitude: church.latitude,
longitude: church.longitude
}
});
if (existing) {
// Update existing church
await prisma.churches.update({
where: { id: existing.id },
data: church
});
// Delete existing schedules
await prisma.massSchedules.deleteMany({
where: { churchId: existing.id }
});
await prisma.confessionSchedules.deleteMany({
where: { churchId: existing.id }
});
await prisma.adorationSchedules.deleteMany({
where: { churchId: existing.id }
});
// Insert new schedules
for (const schedule of massSchedules) {
delete schedule.id;
await prisma.massSchedules.create({
data: {
...schedule,
churchId: existing.id
}
});
totalMassSchedules++;
}
for (const schedule of confessionSchedules) {
delete schedule.id;
await prisma.confessionSchedules.create({
data: {
...schedule,
churchId: existing.id
}
});
totalConfessionSchedules++;
}
for (const schedule of adorationSchedules) {
delete schedule.id;
await prisma.adorationSchedules.create({
data: {
...schedule,
churchId: existing.id
}
});
totalAdorationSchedules++;
}
updated++;
} else {
// Create new church
const result = await prisma.churches.create({
data: church
});
// Insert schedules
for (const schedule of massSchedules) {
delete schedule.id;
await prisma.massSchedules.create({
data: {
...schedule,
churchId: result.id
}
});
totalMassSchedules++;
}
for (const schedule of confessionSchedules) {
delete schedule.id;
await prisma.confessionSchedules.create({
data: {
...schedule,
churchId: result.id
}
});
totalConfessionSchedules++;
}
for (const schedule of adorationSchedules) {
delete schedule.id;
await prisma.adorationSchedules.create({
data: {
...schedule,
churchId: result.id
}
});
totalAdorationSchedules++;
}
inserted++;
}
} else {
// Dry run - just count
inserted++;
totalMassSchedules += massSchedules.length;
totalConfessionSchedules += confessionSchedules.length;
totalAdorationSchedules += adorationSchedules.length;
}
if ((inserted + updated) % 100 === 0) {
console.log(`Progress: ${inserted + updated} churches processed...`);
}
} catch (error) {
errors++;
console.error(`Error importing church ${church.name}:`, error instanceof Error ? error.message : error);
}
}
console.log('\n✅ Import complete!');
console.log(` - ${inserted} churches inserted`);
console.log(` - ${updated} churches updated`);
console.log(` - ${skipped} churches skipped`);
console.log(` - ${errors} errors`);
console.log(` - ${totalMassSchedules} mass schedules`);
console.log(` - ${totalConfessionSchedules} confession schedules`);
console.log(` - ${totalAdorationSchedules} adoration schedules`);
await prisma.$disconnect();
if (dryRun) {
console.log('\n💡 This was a DRY RUN. To actually import to Neon, run:');
console.log(` npx tsx scripts/import-to-neon.ts ${country} real-import`);
} else {
console.log('\n🎉 Data successfully uploaded to Neon!');
}
} catch (error) {
console.error('❌ Import failed:', error);
await prisma.$disconnect();
throw error;
}
} catch (error) {
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
console.error(`❌ Export file not found: ${exportFile}`);
console.error(`\nFirst, export data from NAS:`);
console.error(` ssh albert@192.168.0.145`);
console.error(` cd /volume1/docker/nearestmass`);
console.error(` /usr/local/bin/docker compose --profile tools run --rm scraper npx tsx scripts/export-from-nas.ts ${country}`);
console.error(`\nThen download the export:`);
console.error(` scp albert@192.168.0.145:/volume1/docker/nearestmass/export-${country}.json .`);
console.error(`\nFinally, run this import script again.`);
} else {
console.error('❌ Process failed:', error);
}
process.exit(1);
}
}
main().catch(console.error);