#!/bin/bash # PocketBase to PostgreSQL Migration Script set -e echo "๐Ÿš€ Migrating PocketBase data to PostgreSQL..." # Configuration POCKETBASE_URL="http://localhost:8090" # Adjust if different POSTGRES_URL="$DATABASE_URL" MIGRATION_DIR="/tmp/pb_migration" API_URL="https://api.rockvilletollandsda.church" # Create migration directory mkdir -p "$MIGRATION_DIR" cd "$MIGRATION_DIR" echo "๐Ÿ“ฆ Step 1: Export data from PocketBase..." # Function to export PocketBase collection data export_collection() { local collection=$1 echo " Exporting $collection..." # Get all records from collection (adjust perPage if you have many records) curl -s "${POCKETBASE_URL}/api/collections/${collection}/records?perPage=500" \ -o "${collection}.json" if [ $? -eq 0 ]; then echo " โœ… Exported $(jq '.items | length' ${collection}.json) records from $collection" else echo " โŒ Failed to export $collection" fi } # Export all collections export_collection "bulletins" export_collection "events" export_collection "pending_events" export_collection "config" export_collection "bible_verses" export_collection "Quarterly_Schedule" export_collection "Offering_and_Sunset_Times_Schedule" export_collection "rtsda_android" echo "๐Ÿ“ฅ Step 2: Transform and import data..." # Create Python script for data transformation cat > transform_data.py << 'EOF' import json import sys import uuid from datetime import datetime import psycopg2 from psycopg2.extras import RealDictCursor import os # Database connection conn = psycopg2.connect(os.environ['DATABASE_URL']) cur = conn.cursor(cursor_factory=RealDictCursor) def load_json(filename): try: with open(filename, 'r') as f: data = json.load(f) return data.get('items', []) except FileNotFoundError: print(f"โš ๏ธ {filename} not found, skipping...") return [] def convert_date(pb_date): """Convert PocketBase date to PostgreSQL format""" if not pb_date: return None try: # PocketBase uses ISO format dt = datetime.fromisoformat(pb_date.replace('Z', '+00:00')) return dt except: return None def generate_uuid(): """Generate PostgreSQL-compatible UUID""" return str(uuid.uuid4()) print("๐Ÿ”„ Transforming bulletins...") bulletins = load_json('bulletins.json') for bulletin in bulletins: cur.execute(""" INSERT INTO bulletins (id, title, date, url, pdf_url, is_active, pdf_file, sabbath_school, divine_worship, scripture_reading, sunset, cover_image, created_at, updated_at) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON CONFLICT (id) DO NOTHING """, ( generate_uuid(), bulletin.get('title'), convert_date(bulletin.get('date')), bulletin.get('url'), bulletin.get('pdf_url'), bulletin.get('is_active', True), bulletin.get('pdf'), bulletin.get('sabbath_school'), bulletin.get('divine_worship'), bulletin.get('scripture_reading'), bulletin.get('sunset'), bulletin.get('cover_image'), convert_date(bulletin.get('created')), convert_date(bulletin.get('updated')) )) print("๐Ÿ”„ Transforming events...") events = load_json('events.json') for event in events: cur.execute(""" INSERT INTO events (id, title, description, start_time, end_time, location, location_url, image, thumbnail, category, is_featured, recurring_type, approved_from, created_at, updated_at) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON CONFLICT (id) DO NOTHING """, ( generate_uuid(), event.get('title'), event.get('description'), convert_date(event.get('start_time')), convert_date(event.get('end_time')), event.get('location'), event.get('location_url'), event.get('image'), event.get('thumbnail'), event.get('category'), event.get('is_featured', False), event.get('reoccuring'), # Note: PB uses 'reoccuring', PG uses 'recurring_type' event.get('approved_from'), convert_date(event.get('created')), convert_date(event.get('updated')) )) print("๐Ÿ”„ Transforming pending events...") pending_events = load_json('pending_events.json') for event in pending_events: cur.execute(""" INSERT INTO pending_events (id, title, description, start_time, end_time, location, location_url, image, thumbnail, category, is_featured, recurring_type, approval_status, submitted_at, bulletin_week, admin_notes, submitter_email, email_sent, pending_email_sent, rejection_email_sent, approval_email_sent, created_at, updated_at) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON CONFLICT (id) DO NOTHING """, ( generate_uuid(), event.get('title'), event.get('description'), convert_date(event.get('start_time')), convert_date(event.get('end_time')), event.get('location'), event.get('location_url'), event.get('image'), event.get('thumbnail'), event.get('category'), event.get('is_featured', False), event.get('reoccuring'), event.get('approval_status', 'pending'), convert_date(event.get('submitted_at')), event.get('bulletin_week'), event.get('admin_notes'), event.get('submitter_email'), event.get('email_sent', False), event.get('pending_email_sent', False), event.get('rejection_email_sent', False), event.get('approval_email_sent', False), convert_date(event.get('created')), convert_date(event.get('updated')) )) print("๐Ÿ”„ Transforming church config...") configs = load_json('config.json') for config in configs: cur.execute(""" INSERT INTO church_config (id, church_name, contact_email, contact_phone, church_address, po_box, google_maps_url, about_text, api_keys, created_at, updated_at) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON CONFLICT (id) DO NOTHING """, ( generate_uuid(), config.get('church_name'), config.get('contact_email'), config.get('contact_phone'), config.get('church_address'), config.get('po_box'), config.get('google_maps_url'), config.get('about_text'), json.dumps(config.get('api_key', {})), convert_date(config.get('created')), convert_date(config.get('updated')) )) print("๐Ÿ”„ Transforming bible verses...") verses = load_json('bible_verses.json') for verse_record in verses: cur.execute(""" INSERT INTO bible_verses (id, verses, created_at, updated_at) VALUES (%s, %s, %s, %s) ON CONFLICT (id) DO NOTHING """, ( generate_uuid(), json.dumps(verse_record.get('verses', {})), convert_date(verse_record.get('created')), convert_date(verse_record.get('updated')) )) print("๐Ÿ”„ Transforming schedules...") # Quarterly schedules quarterly = load_json('Quarterly_Schedule.json') for schedule in quarterly: cur.execute(""" INSERT INTO schedules (id, schedule_type, year, quarter, schedule_data, created_at, updated_at) VALUES (%s, %s, %s, %s, %s, %s, %s) ON CONFLICT (id) DO NOTHING """, ( generate_uuid(), 'quarterly', schedule.get('year'), schedule.get('quarter'), json.dumps(schedule.get('schedule_data', {})), convert_date(schedule.get('created')), convert_date(schedule.get('updated')) )) # Offering and sunset schedules offering = load_json('Offering_and_Sunset_Times_Schedule.json') for schedule in offering: cur.execute(""" INSERT INTO schedules (id, schedule_type, year, quarter, schedule_data, created_at, updated_at) VALUES (%s, %s, %s, %s, %s, %s, %s) ON CONFLICT (id) DO NOTHING """, ( generate_uuid(), 'offering_sunset', schedule.get('year'), None, json.dumps(schedule.get('schedule_data', {})), convert_date(schedule.get('created')), convert_date(schedule.get('updated')) )) print("๐Ÿ”„ Transforming app versions...") app_versions = load_json('rtsda_android.json') for app in app_versions: cur.execute(""" INSERT INTO app_versions (id, platform, version_name, version_code, download_url, update_required, description, created_at, updated_at) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) ON CONFLICT (id) DO NOTHING """, ( generate_uuid(), 'android', app.get('version_name'), app.get('version_code'), None, # You'll need to set download URLs manually app.get('update_required', False), app.get('update_description'), convert_date(app.get('created')), convert_date(app.get('updated')) )) # Commit all changes conn.commit() cur.close() conn.close() print("โœ… Data transformation complete!") EOF # Install required Python packages pip3 install psycopg2-binary > /dev/null 2>&1 # Run the transformation python3 transform_data.py echo "๐Ÿ“Š Step 3: Verifying migration..." # Check what was migrated psql "$POSTGRES_URL" -c " SELECT 'bulletins' as table_name, COUNT(*) as records FROM bulletins UNION ALL SELECT 'events', COUNT(*) FROM events UNION ALL SELECT 'pending_events', COUNT(*) FROM pending_events UNION ALL SELECT 'church_config', COUNT(*) FROM church_config UNION ALL SELECT 'bible_verses', COUNT(*) FROM bible_verses UNION ALL SELECT 'schedules', COUNT(*) FROM schedules UNION ALL SELECT 'app_versions', COUNT(*) FROM app_versions; " echo "๐ŸŽ‰ Migration complete!" echo "" echo "๐Ÿ“‹ Next steps:" echo "1. Verify data looks correct in PostgreSQL" echo "2. Test API endpoints to ensure data is accessible" echo "3. Update any file URLs that point to PocketBase" echo "4. Shut down PocketBase once everything is working" echo "" echo "๐Ÿงช Test your migrated data:" echo " curl $API_URL/api/bulletins" echo " curl $API_URL/api/events" # Cleanup rm -rf "$MIGRATION_DIR"