#!/usr/bin/env tsx /** * Export churches from local NAS database and import to Neon */ import { PrismaClient } from '@prisma/client'; import fs from 'fs/promises'; import path from 'path'; interface ExportStats { churches: number; massSchedules: number; confessionSchedules: number; adorationSchedules: number; } async function exportFromNAS(country: string): Promise { console.log(`šŸ“¦ Exporting ${country} data from NAS...`); // Set DATABASE_URL to NAS const originalUrl = process.env.DATABASE_URL; process.env.DATABASE_URL = 'postgresql://postgres:postgres@192.168.0.145:5432/nearestmass'; const nasPrisma = new PrismaClient(); try { await nasPrisma.$connect(); console.log('āœ… Connected to NAS database'); // Export churches with all schedules const churches = await nasPrisma.churches.findMany({ where: { country }, include: { massSchedules: true, confessionSchedules: true, adorationSchedules: true, } }); console.log(`Found ${churches.length} churches in ${country}`); // Count schedules const stats: ExportStats = { churches: churches.length, massSchedules: churches.reduce((sum, c) => sum + (c.massSchedules?.length || 0), 0), confessionSchedules: churches.reduce((sum, c) => sum + (c.confessionSchedules?.length || 0), 0), adorationSchedules: churches.reduce((sum, c) => sum + (c.adorationSchedules?.length || 0), 0), }; // Save to file const exportFile = path.join(process.cwd(), `export-${country}.json`); await fs.writeFile(exportFile, JSON.stringify(churches, null, 2)); console.log(`āœ… Exported to ${exportFile}`); console.log(` - ${stats.churches} churches`); console.log(` - ${stats.massSchedules} mass schedules`); console.log(` - ${stats.confessionSchedules} confession schedules`); console.log(` - ${stats.adorationSchedules} adoration schedules`); await nasPrisma.$disconnect(); // Restore original DATABASE_URL if (originalUrl) { process.env.DATABASE_URL = originalUrl; } return stats; } catch (error) { console.error('āŒ Export failed:', error); await nasPrisma.$disconnect(); // Restore original DATABASE_URL if (originalUrl) { process.env.DATABASE_URL = originalUrl; } throw error; } } async function importToNeon(country: string, dryRun: boolean = true): Promise { console.log(`\nšŸ“¤ Importing ${country} data to Neon...`); if (dryRun) { console.log('šŸ” DRY RUN MODE - No data will be written'); } // Read export file const exportFile = path.join(process.cwd(), `export-${country}.json`); const data = JSON.parse(await fs.readFile(exportFile, 'utf-8')); console.log(`Loaded ${data.length} churches from export file`); // Connect to Neon const neonPrisma = new PrismaClient(); try { await neonPrisma.$connect(); console.log('āœ… Connected to Neon database'); let inserted = 0; let updated = 0; let errors = 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) { // Upsert church based on coordinates const result = await neonPrisma.churches.upsert({ where: { latitude_longitude: { latitude: church.latitude, longitude: church.longitude } }, create: church, update: church }); // Check if it was an insert or update const existing = await neonPrisma.churches.findFirst({ where: { latitude: church.latitude, longitude: church.longitude, createdAt: { lt: new Date(Date.now() - 1000) } // Created more than 1 sec ago } }); if (existing) { updated++; } else { inserted++; } // Insert schedules for (const schedule of massSchedules) { delete schedule.id; await neonPrisma.massSchedules.create({ data: { ...schedule, churchId: result.id } }); } for (const schedule of confessionSchedules) { delete schedule.id; await neonPrisma.confessionSchedules.create({ data: { ...schedule, churchId: result.id } }); } for (const schedule of adorationSchedules) { delete schedule.id; await neonPrisma.adorationSchedules.create({ data: { ...schedule, churchId: result.id } }); } } else { // Dry run - just count inserted++; } if (inserted % 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(` - ${errors} errors`); await neonPrisma.$disconnect(); } catch (error) { console.error('āŒ Import failed:', error); await neonPrisma.$disconnect(); throw error; } } async function main() { const country = process.argv[2] || 'PL'; const mode = process.argv[3] || 'dry-run'; const dryRun = mode === 'dry-run'; console.log('šŸŒ Export/Import to Neon'); console.log('========================\n'); try { // Step 1: Export from NAS const stats = await exportFromNAS(country); // Step 2: Import to Neon await importToNeon(country, dryRun); if (dryRun) { console.log('\nšŸ’” This was a DRY RUN. To actually import to Neon, run:'); console.log(` npx tsx scripts/export-import-to-neon.ts ${country} real-import`); } else { console.log('\nšŸŽ‰ Data successfully uploaded to Neon!'); } } catch (error) { console.error('āŒ Process failed:', error); process.exit(1); } } main().catch(console.error);