From 9854ca2f590c70704d130b81e5058d8a7e3b95be Mon Sep 17 00:00:00 2001 From: paul Date: Thu, 24 Jul 2025 22:40:25 +0200 Subject: [PATCH] Reorganize migrations for new vs existing deployments - Created core/ directory for essential migrations that always run - Created legacy/ directory for migrations only needed when upgrading - New deployments will only run core migrations for a clean database - Existing deployments will run all migrations in proper sequence - Fixed duplicate migration numbers (014 and 027) - Updated migration runners to handle new directory structure - Added README explaining the migration organization This change optimizes deployment for new users who will get a clean schema without running unnecessary upgrade migrations. --- .gitignore | 2 + backend/migrations/README.md | 46 +++++ .../029_add_backup_service_tables.js | 0 .../030_add_database_backup_tables.js | 0 .../031_add_backup_manifest_columns.js | 0 .../{ => core}/032_add_restore_runs_table.js | 0 .../{ => core}/033_add_gallery_feedback.js | 0 .../{ => core}/034_add_version_to_backups.js | 0 .../{ => core}/035_enhance_backup_system.js | 0 backend/migrations/{ => core}/init.js | 0 .../004_add_categories_and_cms.js | 0 .../006_add_photo_counter_to_categories.js | 0 .../007_add_read_at_to_activity_logs.js | 0 ...add_language_support_to_email_templates.js | 0 .../009_update_german_email_templates.js | 0 .../010_add_missing_email_templates.js | 0 .../011_add_user_upload_settings.js | 0 .../{ => legacy}/012_add_hero_photo_id.js | 0 .../013_fix_email_links_and_date_format.js | 0 .../014_add_default_welcome_message.js | 0 .../014_add_host_name_to_events_duplicate.js} | 0 .../015_add_login_attempts_table.js | 0 .../016_add_auth_security_columns.js | 0 .../017_add_token_revocation_tables.js | 0 .../018_add_created_at_to_email_queue.js | 0 .../019_fix_email_templates_columns.js | 0 .../020_ensure_default_email_templates.js | 0 .../{ => legacy}/021_add_default_cms_pages.js | 0 .../{ => legacy}/022_fix_json_columns.js | 0 .../023_ensure_postgres_compatibility.js | 0 .../024_fix_boolean_compatibility.js | 0 .../025_fix_email_queue_updated_at.js | 0 .../026_fix_german_email_templates.js | 0 .../027_add_language_preferences.js | 0 .../027_add_rate_limit_settings_duplicate.js} | 0 ...pdate_english_templates_to_match_german.js | 0 backend/migrations/run-migrations-safe.js | 78 +++++-- backend/migrations/run-migrations.js | 60 +++++- backend/scripts/check-db-issues.js | 60 ------ backend/scripts/check-db-schema.js | 42 ---- backend/scripts/check-email-environment.js | 131 ------------ backend/scripts/check-email-processor.js | 157 -------------- backend/scripts/check-email-templates.js | 66 ------ backend/scripts/check-german-templates.js | 53 ----- backend/scripts/create-test-event.js | 58 ------ backend/scripts/debug-500-errors.js | 92 --------- backend/scripts/debug-email-queue.js | 146 ------------- backend/scripts/diagnose-thumbnails.js | 132 ------------ backend/scripts/fix-email-queue-schema.js | 99 --------- backend/scripts/fix-final-german-templates.js | 88 -------- backend/scripts/fix-production-issues.js | 145 ------------- backend/scripts/fix-stuck-emails.js | 126 ------------ backend/scripts/run-email-processor.js | 111 ---------- backend/scripts/run-migrations.js | 40 ---- backend/scripts/test-backup-manifest.js | 192 ------------------ backend/scripts/test-cms-formatting.js | 52 ----- backend/scripts/test-german-emails.js | 85 -------- backend/scripts/test-photo-auth.js | 86 -------- backend/scripts/test-security-logging.js | 95 --------- backend/scripts/verify-template-equality.js | 110 ---------- 60 files changed, 158 insertions(+), 2194 deletions(-) create mode 100644 backend/migrations/README.md rename backend/migrations/{ => core}/029_add_backup_service_tables.js (100%) rename backend/migrations/{ => core}/030_add_database_backup_tables.js (100%) rename backend/migrations/{ => core}/031_add_backup_manifest_columns.js (100%) rename backend/migrations/{ => core}/032_add_restore_runs_table.js (100%) rename backend/migrations/{ => core}/033_add_gallery_feedback.js (100%) rename backend/migrations/{ => core}/034_add_version_to_backups.js (100%) rename backend/migrations/{ => core}/035_enhance_backup_system.js (100%) rename backend/migrations/{ => core}/init.js (100%) rename backend/migrations/{ => legacy}/004_add_categories_and_cms.js (100%) rename backend/migrations/{ => legacy}/006_add_photo_counter_to_categories.js (100%) rename backend/migrations/{ => legacy}/007_add_read_at_to_activity_logs.js (100%) rename backend/migrations/{ => legacy}/008_add_language_support_to_email_templates.js (100%) rename backend/migrations/{ => legacy}/009_update_german_email_templates.js (100%) rename backend/migrations/{ => legacy}/010_add_missing_email_templates.js (100%) rename backend/migrations/{ => legacy}/011_add_user_upload_settings.js (100%) rename backend/migrations/{ => legacy}/012_add_hero_photo_id.js (100%) rename backend/migrations/{ => legacy}/013_fix_email_links_and_date_format.js (100%) rename backend/migrations/{ => legacy}/014_add_default_welcome_message.js (100%) rename backend/migrations/{014_add_host_name_to_events.js => legacy/014_add_host_name_to_events_duplicate.js} (100%) rename backend/migrations/{ => legacy}/015_add_login_attempts_table.js (100%) rename backend/migrations/{ => legacy}/016_add_auth_security_columns.js (100%) rename backend/migrations/{ => legacy}/017_add_token_revocation_tables.js (100%) rename backend/migrations/{ => legacy}/018_add_created_at_to_email_queue.js (100%) rename backend/migrations/{ => legacy}/019_fix_email_templates_columns.js (100%) rename backend/migrations/{ => legacy}/020_ensure_default_email_templates.js (100%) rename backend/migrations/{ => legacy}/021_add_default_cms_pages.js (100%) rename backend/migrations/{ => legacy}/022_fix_json_columns.js (100%) rename backend/migrations/{ => legacy}/023_ensure_postgres_compatibility.js (100%) rename backend/migrations/{ => legacy}/024_fix_boolean_compatibility.js (100%) rename backend/migrations/{ => legacy}/025_fix_email_queue_updated_at.js (100%) rename backend/migrations/{ => legacy}/026_fix_german_email_templates.js (100%) rename backend/migrations/{ => legacy}/027_add_language_preferences.js (100%) rename backend/migrations/{027_add_rate_limit_settings.js => legacy/027_add_rate_limit_settings_duplicate.js} (100%) rename backend/migrations/{ => legacy}/028_update_english_templates_to_match_german.js (100%) delete mode 100644 backend/scripts/check-db-issues.js delete mode 100755 backend/scripts/check-db-schema.js delete mode 100644 backend/scripts/check-email-environment.js delete mode 100644 backend/scripts/check-email-processor.js delete mode 100644 backend/scripts/check-email-templates.js delete mode 100644 backend/scripts/check-german-templates.js delete mode 100644 backend/scripts/create-test-event.js delete mode 100644 backend/scripts/debug-500-errors.js delete mode 100644 backend/scripts/debug-email-queue.js delete mode 100755 backend/scripts/diagnose-thumbnails.js delete mode 100755 backend/scripts/fix-email-queue-schema.js delete mode 100644 backend/scripts/fix-final-german-templates.js delete mode 100644 backend/scripts/fix-production-issues.js delete mode 100644 backend/scripts/fix-stuck-emails.js delete mode 100644 backend/scripts/run-email-processor.js delete mode 100644 backend/scripts/run-migrations.js delete mode 100644 backend/scripts/test-backup-manifest.js delete mode 100644 backend/scripts/test-cms-formatting.js delete mode 100644 backend/scripts/test-german-emails.js delete mode 100755 backend/scripts/test-photo-auth.js delete mode 100644 backend/scripts/test-security-logging.js delete mode 100644 backend/scripts/verify-template-equality.js diff --git a/.gitignore b/.gitignore index 0ac1706..d4c0231 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,8 @@ test-archiver/ backend/.swarm/ .claudedocs/ backend/data/ +backend/docs/ +backend/logs/ logs/ storage/ data/ diff --git a/backend/migrations/README.md b/backend/migrations/README.md new file mode 100644 index 0000000..d6bae21 --- /dev/null +++ b/backend/migrations/README.md @@ -0,0 +1,46 @@ +# Database Migrations + +This directory contains database migrations for the Wedding Photo Sharing platform. + +## Directory Structure + +### `/core` +Essential migrations that are always run for new deployments. These include: +- `init.js` - Initial database schema creation +- Backup service tables (029-035) +- Gallery feedback tables (033) + +### `/legacy` +Migrations needed only when upgrading from older versions. New deployments can skip these as the core schema already includes all necessary tables and columns. + +## For New Deployments + +If you're deploying this application for the first time: +1. The `initializeDatabase()` function in `src/database/db.js` will create all necessary tables +2. Only migrations in the `/core` directory will be run +3. This ensures a clean, optimized database schema + +## For Existing Deployments + +If you're upgrading from an older version: +1. All migrations (both core and legacy) will be run in sequence +2. The migration system tracks which migrations have been applied +3. Only new migrations will be executed + +## Running Migrations + +```bash +# Development +npm run migrate + +# Production +npm run migrate:prod +``` + +## Note on Duplicate Migration Numbers + +The legacy directory contains renamed duplicates: +- `014_add_host_name_to_events_duplicate.js` (was duplicate of 014) +- `027_add_rate_limit_settings_duplicate.js` (was duplicate of 027) + +These have been renamed to avoid conflicts while preserving the migration history. \ No newline at end of file diff --git a/backend/migrations/029_add_backup_service_tables.js b/backend/migrations/core/029_add_backup_service_tables.js similarity index 100% rename from backend/migrations/029_add_backup_service_tables.js rename to backend/migrations/core/029_add_backup_service_tables.js diff --git a/backend/migrations/030_add_database_backup_tables.js b/backend/migrations/core/030_add_database_backup_tables.js similarity index 100% rename from backend/migrations/030_add_database_backup_tables.js rename to backend/migrations/core/030_add_database_backup_tables.js diff --git a/backend/migrations/031_add_backup_manifest_columns.js b/backend/migrations/core/031_add_backup_manifest_columns.js similarity index 100% rename from backend/migrations/031_add_backup_manifest_columns.js rename to backend/migrations/core/031_add_backup_manifest_columns.js diff --git a/backend/migrations/032_add_restore_runs_table.js b/backend/migrations/core/032_add_restore_runs_table.js similarity index 100% rename from backend/migrations/032_add_restore_runs_table.js rename to backend/migrations/core/032_add_restore_runs_table.js diff --git a/backend/migrations/033_add_gallery_feedback.js b/backend/migrations/core/033_add_gallery_feedback.js similarity index 100% rename from backend/migrations/033_add_gallery_feedback.js rename to backend/migrations/core/033_add_gallery_feedback.js diff --git a/backend/migrations/034_add_version_to_backups.js b/backend/migrations/core/034_add_version_to_backups.js similarity index 100% rename from backend/migrations/034_add_version_to_backups.js rename to backend/migrations/core/034_add_version_to_backups.js diff --git a/backend/migrations/035_enhance_backup_system.js b/backend/migrations/core/035_enhance_backup_system.js similarity index 100% rename from backend/migrations/035_enhance_backup_system.js rename to backend/migrations/core/035_enhance_backup_system.js diff --git a/backend/migrations/init.js b/backend/migrations/core/init.js similarity index 100% rename from backend/migrations/init.js rename to backend/migrations/core/init.js diff --git a/backend/migrations/004_add_categories_and_cms.js b/backend/migrations/legacy/004_add_categories_and_cms.js similarity index 100% rename from backend/migrations/004_add_categories_and_cms.js rename to backend/migrations/legacy/004_add_categories_and_cms.js diff --git a/backend/migrations/006_add_photo_counter_to_categories.js b/backend/migrations/legacy/006_add_photo_counter_to_categories.js similarity index 100% rename from backend/migrations/006_add_photo_counter_to_categories.js rename to backend/migrations/legacy/006_add_photo_counter_to_categories.js diff --git a/backend/migrations/007_add_read_at_to_activity_logs.js b/backend/migrations/legacy/007_add_read_at_to_activity_logs.js similarity index 100% rename from backend/migrations/007_add_read_at_to_activity_logs.js rename to backend/migrations/legacy/007_add_read_at_to_activity_logs.js diff --git a/backend/migrations/008_add_language_support_to_email_templates.js b/backend/migrations/legacy/008_add_language_support_to_email_templates.js similarity index 100% rename from backend/migrations/008_add_language_support_to_email_templates.js rename to backend/migrations/legacy/008_add_language_support_to_email_templates.js diff --git a/backend/migrations/009_update_german_email_templates.js b/backend/migrations/legacy/009_update_german_email_templates.js similarity index 100% rename from backend/migrations/009_update_german_email_templates.js rename to backend/migrations/legacy/009_update_german_email_templates.js diff --git a/backend/migrations/010_add_missing_email_templates.js b/backend/migrations/legacy/010_add_missing_email_templates.js similarity index 100% rename from backend/migrations/010_add_missing_email_templates.js rename to backend/migrations/legacy/010_add_missing_email_templates.js diff --git a/backend/migrations/011_add_user_upload_settings.js b/backend/migrations/legacy/011_add_user_upload_settings.js similarity index 100% rename from backend/migrations/011_add_user_upload_settings.js rename to backend/migrations/legacy/011_add_user_upload_settings.js diff --git a/backend/migrations/012_add_hero_photo_id.js b/backend/migrations/legacy/012_add_hero_photo_id.js similarity index 100% rename from backend/migrations/012_add_hero_photo_id.js rename to backend/migrations/legacy/012_add_hero_photo_id.js diff --git a/backend/migrations/013_fix_email_links_and_date_format.js b/backend/migrations/legacy/013_fix_email_links_and_date_format.js similarity index 100% rename from backend/migrations/013_fix_email_links_and_date_format.js rename to backend/migrations/legacy/013_fix_email_links_and_date_format.js diff --git a/backend/migrations/014_add_default_welcome_message.js b/backend/migrations/legacy/014_add_default_welcome_message.js similarity index 100% rename from backend/migrations/014_add_default_welcome_message.js rename to backend/migrations/legacy/014_add_default_welcome_message.js diff --git a/backend/migrations/014_add_host_name_to_events.js b/backend/migrations/legacy/014_add_host_name_to_events_duplicate.js similarity index 100% rename from backend/migrations/014_add_host_name_to_events.js rename to backend/migrations/legacy/014_add_host_name_to_events_duplicate.js diff --git a/backend/migrations/015_add_login_attempts_table.js b/backend/migrations/legacy/015_add_login_attempts_table.js similarity index 100% rename from backend/migrations/015_add_login_attempts_table.js rename to backend/migrations/legacy/015_add_login_attempts_table.js diff --git a/backend/migrations/016_add_auth_security_columns.js b/backend/migrations/legacy/016_add_auth_security_columns.js similarity index 100% rename from backend/migrations/016_add_auth_security_columns.js rename to backend/migrations/legacy/016_add_auth_security_columns.js diff --git a/backend/migrations/017_add_token_revocation_tables.js b/backend/migrations/legacy/017_add_token_revocation_tables.js similarity index 100% rename from backend/migrations/017_add_token_revocation_tables.js rename to backend/migrations/legacy/017_add_token_revocation_tables.js diff --git a/backend/migrations/018_add_created_at_to_email_queue.js b/backend/migrations/legacy/018_add_created_at_to_email_queue.js similarity index 100% rename from backend/migrations/018_add_created_at_to_email_queue.js rename to backend/migrations/legacy/018_add_created_at_to_email_queue.js diff --git a/backend/migrations/019_fix_email_templates_columns.js b/backend/migrations/legacy/019_fix_email_templates_columns.js similarity index 100% rename from backend/migrations/019_fix_email_templates_columns.js rename to backend/migrations/legacy/019_fix_email_templates_columns.js diff --git a/backend/migrations/020_ensure_default_email_templates.js b/backend/migrations/legacy/020_ensure_default_email_templates.js similarity index 100% rename from backend/migrations/020_ensure_default_email_templates.js rename to backend/migrations/legacy/020_ensure_default_email_templates.js diff --git a/backend/migrations/021_add_default_cms_pages.js b/backend/migrations/legacy/021_add_default_cms_pages.js similarity index 100% rename from backend/migrations/021_add_default_cms_pages.js rename to backend/migrations/legacy/021_add_default_cms_pages.js diff --git a/backend/migrations/022_fix_json_columns.js b/backend/migrations/legacy/022_fix_json_columns.js similarity index 100% rename from backend/migrations/022_fix_json_columns.js rename to backend/migrations/legacy/022_fix_json_columns.js diff --git a/backend/migrations/023_ensure_postgres_compatibility.js b/backend/migrations/legacy/023_ensure_postgres_compatibility.js similarity index 100% rename from backend/migrations/023_ensure_postgres_compatibility.js rename to backend/migrations/legacy/023_ensure_postgres_compatibility.js diff --git a/backend/migrations/024_fix_boolean_compatibility.js b/backend/migrations/legacy/024_fix_boolean_compatibility.js similarity index 100% rename from backend/migrations/024_fix_boolean_compatibility.js rename to backend/migrations/legacy/024_fix_boolean_compatibility.js diff --git a/backend/migrations/025_fix_email_queue_updated_at.js b/backend/migrations/legacy/025_fix_email_queue_updated_at.js similarity index 100% rename from backend/migrations/025_fix_email_queue_updated_at.js rename to backend/migrations/legacy/025_fix_email_queue_updated_at.js diff --git a/backend/migrations/026_fix_german_email_templates.js b/backend/migrations/legacy/026_fix_german_email_templates.js similarity index 100% rename from backend/migrations/026_fix_german_email_templates.js rename to backend/migrations/legacy/026_fix_german_email_templates.js diff --git a/backend/migrations/027_add_language_preferences.js b/backend/migrations/legacy/027_add_language_preferences.js similarity index 100% rename from backend/migrations/027_add_language_preferences.js rename to backend/migrations/legacy/027_add_language_preferences.js diff --git a/backend/migrations/027_add_rate_limit_settings.js b/backend/migrations/legacy/027_add_rate_limit_settings_duplicate.js similarity index 100% rename from backend/migrations/027_add_rate_limit_settings.js rename to backend/migrations/legacy/027_add_rate_limit_settings_duplicate.js diff --git a/backend/migrations/028_update_english_templates_to_match_german.js b/backend/migrations/legacy/028_update_english_templates_to_match_german.js similarity index 100% rename from backend/migrations/028_update_english_templates_to_match_german.js rename to backend/migrations/legacy/028_update_english_templates_to_match_german.js diff --git a/backend/migrations/run-migrations-safe.js b/backend/migrations/run-migrations-safe.js index cda8bba..5e63ddc 100644 --- a/backend/migrations/run-migrations-safe.js +++ b/backend/migrations/run-migrations-safe.js @@ -42,6 +42,8 @@ async function detectExistingSchema() { { table: 'cms_pages', migration: '004_add_categories_and_cms.js' }, { table: 'login_attempts', migration: '015_add_login_attempts_table.js' }, { table: 'token_blacklist', migration: '017_add_token_revocation_tables.js' }, + { table: 'backup_runs', migration: '029_add_backup_service_tables.js' }, + { table: 'gallery_feedback', migration: '033_add_gallery_feedback.js' }, ]; for (const check of tableChecks) { @@ -56,13 +58,14 @@ async function detectExistingSchema() { } // Run a single migration safely -async function runMigrationSafely(filename) { +async function runMigrationSafely(filepath) { try { - const migrationPath = path.join(__dirname, filename); + const migrationPath = path.join(__dirname, filepath); const migration = require(migrationPath); + const filename = path.basename(filepath); if (migration.up) { - console.log(`Running migration: ${filename}`); + console.log(`Running migration: ${filepath}`); // Run migration in a transaction if possible if (db.client.config.client === 'pg') { @@ -74,14 +77,14 @@ async function runMigrationSafely(filename) { } await db('migrations').insert({ filename }); - console.log(`Migration ${filename} completed successfully`); + console.log(`Migration ${filepath} completed successfully`); } } catch (error) { // Check if error is because schema already exists if (error.code === '42P07' || // PostgreSQL: relation already exists error.code === 'SQLITE_ERROR' && error.message.includes('already exists')) { - console.log(`Migration ${filename} - schema already exists, marking as applied`); - await markMigrationAsApplied(filename); + console.log(`Migration ${filepath} - schema already exists, marking as applied`); + await markMigrationAsApplied(path.basename(filepath)); } else { throw error; } @@ -104,23 +107,64 @@ async function runMigrations() { // Detect and mark existing schema await detectExistingSchema(); - // Get all migration files - const files = await fs.readdir(__dirname); - const migrationFiles = files - .filter(f => f.match(/^\d{3}_.*\.js$/) || f === 'init.js') - .sort((a, b) => { - // Ensure init.js runs first - if (a === 'init.js') return -1; - if (b === 'init.js') return 1; - return a.localeCompare(b); - }); + // Get applied migrations + const appliedMigrations = await db('migrations').select('filename'); + const appliedFilenames = appliedMigrations.map(m => m.filename); + + // Check if this is a new deployment (no migrations have been applied) + const isNewDeployment = appliedFilenames.length === 0; + + // Get migration files from appropriate directories + let migrationFiles = []; + + if (isNewDeployment) { + // For new deployments, only run core migrations + console.log('New deployment detected - running core migrations only'); + const coreDir = path.join(__dirname, 'core'); + const coreFiles = await fs.readdir(coreDir); + migrationFiles = coreFiles + .filter(f => f.match(/^\d{3}_.*\.js$/) || f === 'init.js') + .map(f => path.join('core', f)) + .sort(); + } else { + // For existing deployments, run all migrations (legacy + core) + console.log('Existing deployment detected - checking all migrations'); + + // Get legacy migrations + const legacyDir = path.join(__dirname, 'legacy'); + const legacyFiles = await fs.readdir(legacyDir); + const legacyMigrations = legacyFiles + .filter(f => f.match(/^\d{3}_.*\.js$/)) + .map(f => path.join('legacy', f)); + + // Get core migrations + const coreDir = path.join(__dirname, 'core'); + const coreFiles = await fs.readdir(coreDir); + const coreMigrations = coreFiles + .filter(f => f.match(/^\d{3}_.*\.js$/) || f === 'init.js') + .map(f => path.join('core', f)); + + // Combine and sort by number + migrationFiles = [...legacyMigrations, ...coreMigrations] + .sort((a, b) => { + const baseA = path.basename(a); + const baseB = path.basename(b); + // Ensure init.js runs first + if (baseA === 'init.js') return -1; + if (baseB === 'init.js') return 1; + const numA = parseInt(baseA.split('_')[0]); + const numB = parseInt(baseB.split('_')[0]); + return numA - numB; + }); + } // Run pending migrations let pendingCount = 0; let skippedCount = 0; for (const file of migrationFiles) { - const isApplied = await isMigrationApplied(file); + const filename = path.basename(file); + const isApplied = appliedFilenames.includes(filename); if (!isApplied) { await runMigrationSafely(file); pendingCount++; diff --git a/backend/migrations/run-migrations.js b/backend/migrations/run-migrations.js index b9f965c..9f23b85 100644 --- a/backend/migrations/run-migrations.js +++ b/backend/migrations/run-migrations.js @@ -22,15 +22,16 @@ async function getAppliedMigrations() { } // Run a single migration -async function runMigration(filename) { - const migrationPath = path.join(__dirname, filename); +async function runMigration(filepath) { + const migrationPath = path.join(__dirname, filepath); const migration = require(migrationPath); + const filename = path.basename(filepath); if (migration.up) { - console.log(`Running migration: ${filename}`); + console.log(`Running migration: ${filepath}`); await migration.up(db); await db('migrations').insert({ filename }); - console.log(`Migration ${filename} completed`); + console.log(`Migration ${filepath} completed`); } } @@ -50,19 +51,56 @@ async function runMigrations() { // Create migrations table await createMigrationsTable(); - // Get all migration files - const files = await fs.readdir(__dirname); - const migrationFiles = files - .filter(f => f.match(/^\d{3}_.*\.js$/)) - .sort(); - // Get applied migrations const appliedMigrations = await getAppliedMigrations(); + // Check if this is a new deployment (no migrations have been applied) + const isNewDeployment = appliedMigrations.length === 0; + + // Get migration files from appropriate directories + let migrationFiles = []; + + if (isNewDeployment) { + // For new deployments, only run core migrations + console.log('New deployment detected - running core migrations only'); + const coreDir = path.join(__dirname, 'core'); + const coreFiles = await fs.readdir(coreDir); + migrationFiles = coreFiles + .filter(f => f.match(/^\d{3}_.*\.js$/)) + .map(f => path.join('core', f)) + .sort(); + } else { + // For existing deployments, run all migrations (legacy + core) + console.log('Existing deployment detected - checking all migrations'); + + // Get legacy migrations + const legacyDir = path.join(__dirname, 'legacy'); + const legacyFiles = await fs.readdir(legacyDir); + const legacyMigrations = legacyFiles + .filter(f => f.match(/^\d{3}_.*\.js$/)) + .map(f => path.join('legacy', f)); + + // Get core migrations + const coreDir = path.join(__dirname, 'core'); + const coreFiles = await fs.readdir(coreDir); + const coreMigrations = coreFiles + .filter(f => f.match(/^\d{3}_.*\.js$/)) + .map(f => path.join('core', f)); + + // Combine and sort by number + migrationFiles = [...legacyMigrations, ...coreMigrations] + .sort((a, b) => { + const numA = parseInt(path.basename(a).split('_')[0]); + const numB = parseInt(path.basename(b).split('_')[0]); + return numA - numB; + }); + } + // Run pending migrations let pendingCount = 0; for (const file of migrationFiles) { - if (!appliedMigrations.includes(file)) { + const filename = path.basename(file); + if (!appliedMigrations.includes(filename)) { await runMigration(file); pendingCount++; } diff --git a/backend/scripts/check-db-issues.js b/backend/scripts/check-db-issues.js deleted file mode 100644 index a2b6fec..0000000 --- a/backend/scripts/check-db-issues.js +++ /dev/null @@ -1,60 +0,0 @@ -require('dotenv').config(); -const { db } = require('../src/database/db'); - -async function checkDatabaseIssues() { - console.log('Checking database issues...\n'); - - try { - // Check email_templates table structure - console.log('1. Checking email_templates table structure:'); - const emailTemplateColumns = await db('email_templates').columnInfo(); - console.log('Columns:', Object.keys(emailTemplateColumns)); - - // Check if any templates exist - const templateCount = await db('email_templates').count('* as count'); - console.log('Template count:', templateCount[0].count); - - // Check for specific template - const galleryCreatedTemplate = await db('email_templates') - .where('template_key', 'gallery_created') - .first(); - console.log('gallery_created template exists:', !!galleryCreatedTemplate); - - // Check activity_logs table - console.log('\n2. Checking activity_logs table:'); - const activityLogColumns = await db('activity_logs').columnInfo(); - console.log('Columns:', Object.keys(activityLogColumns)); - - // Check migrations table - console.log('\n3. Checking migrations status:'); - const migrations = await db('migrations') - .orderBy('id', 'desc') - .limit(10); - console.log('Latest migrations:'); - migrations.forEach(m => console.log(` - ${m.filename}`)); - - // Test a simple query from notifications route - console.log('\n4. Testing notifications query:'); - try { - const notifications = await db('activity_logs') - .select( - 'activity_logs.*', - 'events.event_name' - ) - .leftJoin('events', 'activity_logs.event_id', 'events.id') - .orderBy('activity_logs.created_at', 'desc') - .limit(5); - console.log(`Found ${notifications.length} notifications`); - } catch (error) { - console.error('Notifications query failed:', error.message); - } - - } catch (error) { - console.error('Error:', error); - } finally { - await db.destroy(); - process.exit(0); - } -} - -checkDatabaseIssues(); \ No newline at end of file diff --git a/backend/scripts/check-db-schema.js b/backend/scripts/check-db-schema.js deleted file mode 100755 index f508832..0000000 --- a/backend/scripts/check-db-schema.js +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env node - -const sqlite3 = require('sqlite3').verbose(); - -// Connect to the database -const dbPath = '/app/data/photo_sharing.db'; -console.log(`Connecting to database at: ${dbPath}`); - -const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READONLY, (err) => { - if (err) { - console.error('Error opening database:', err.message); - process.exit(1); - } - console.log('Connected to the SQLite database.\n'); -}); - -// Get schema for events table -console.log('=== EVENTS TABLE SCHEMA ==='); -db.all("PRAGMA table_info(events)", [], (err, rows) => { - if (err) { - console.error('Error getting events schema:', err.message); - } else { - rows.forEach(row => { - console.log(`${row.name} (${row.type})`); - }); - } - - console.log('\n=== PHOTOS TABLE SCHEMA ==='); - // Get schema for photos table - db.all("PRAGMA table_info(photos)", [], (err, rows) => { - if (err) { - console.error('Error getting photos schema:', err.message); - } else { - rows.forEach(row => { - console.log(`${row.name} (${row.type})`); - }); - } - - // Close the database - db.close(); - }); -}); \ No newline at end of file diff --git a/backend/scripts/check-email-environment.js b/backend/scripts/check-email-environment.js deleted file mode 100644 index bfac0f0..0000000 --- a/backend/scripts/check-email-environment.js +++ /dev/null @@ -1,131 +0,0 @@ -const { db } = require('../src/database/db'); - -async function checkEmailEnvironment() { - console.log('=== Email Environment Check ===\n'); - - // 1. Check environment variables - console.log('1. Environment Variables:'); - const envVars = [ - 'SMTP_HOST', - 'SMTP_PORT', - 'SMTP_USER', - 'SMTP_PASS', - 'SMTP_FROM', - 'SMTP_SECURE', - 'EMAIL_PROCESSOR_ENABLED', - 'NODE_ENV' - ]; - - envVars.forEach(varName => { - const value = process.env[varName]; - if (varName.includes('PASS')) { - console.log(` ${varName}: ${value ? '***' : 'NOT SET'}`); - } else { - console.log(` ${varName}: ${value || 'NOT SET'}`); - } - }); - - // 2. Check database configuration - console.log('\n2. Database Email Configuration:'); - try { - const emailConfig = await db('email_configs').first(); - if (emailConfig) { - console.log(' Email configuration found in database:'); - console.log(` - SMTP Host: ${emailConfig.smtp_host}`); - console.log(` - SMTP Port: ${emailConfig.smtp_port}`); - console.log(` - SMTP User: ${emailConfig.smtp_user || 'NOT SET'}`); - console.log(` - SMTP Secure: ${emailConfig.smtp_secure}`); - console.log(` - From Address: ${emailConfig.smtp_from}`); - } else { - console.log(' ⚠️ No email configuration found in database!'); - console.log(' This will prevent the email processor from initializing.'); - } - } catch (error) { - console.log(` ❌ Error reading email configuration: ${error.message}`); - } - - // 3. Check if the email processor should be disabled - console.log('\n3. Email Processor Status:'); - const isDisabled = process.env.EMAIL_PROCESSOR_ENABLED === 'false'; - if (isDisabled) { - console.log(' ⚠️ Email processor is DISABLED via EMAIL_PROCESSOR_ENABLED=false'); - } else { - console.log(' ✅ Email processor is enabled (default)'); - } - - // 4. Check pending emails - console.log('\n4. Email Queue Status:'); - try { - const pending = await db('email_queue') - .where('status', 'pending') - .count('* as count') - .first(); - - const failed = await db('email_queue') - .where('status', 'failed') - .where('retry_count', '>=', 3) - .count('* as count') - .first(); - - const sent = await db('email_queue') - .where('status', 'sent') - .count('* as count') - .first(); - - console.log(` - Pending emails: ${pending.count}`); - console.log(` - Failed emails (max retries): ${failed.count}`); - console.log(` - Sent emails: ${sent.count}`); - } catch (error) { - console.log(` ❌ Error querying email queue: ${error.message}`); - } - - // 5. Test database connection - console.log('\n5. Database Connection:'); - try { - await db.raw('SELECT 1'); - console.log(' ✅ Database connection successful'); - } catch (error) { - console.log(` ❌ Database connection failed: ${error.message}`); - } - - // 6. Check for any recent errors - console.log('\n6. Recent Email Errors:'); - try { - const recentErrors = await db('email_queue') - .whereNotNull('error_message') - .orderBy('id', 'desc') - .limit(3) - .select('id', 'email_type', 'error_message', 'retry_count'); - - if (recentErrors.length > 0) { - recentErrors.forEach((email, index) => { - console.log(` ${index + 1}. Email ID ${email.id} (${email.email_type}):`); - console.log(` Retries: ${email.retry_count}`); - console.log(` Error: ${email.error_message}`); - }); - } else { - console.log(' No recent errors found'); - } - } catch (error) { - console.log(` ❌ Error querying recent errors: ${error.message}`); - } - - console.log('\n=== Environment check complete ==='); - console.log('\nRecommendations:'); - - const emailConfig = await db('email_configs').first().catch(() => null); - if (!emailConfig) { - console.log('❗ Configure email settings in the admin panel or add email_configs record'); - } - - if (!process.env.SMTP_HOST && !emailConfig) { - console.log('❗ Set SMTP environment variables or configure in database'); - } - - await db.destroy(); -} - -checkEmailEnvironment().catch(error => { - console.error('Fatal error:', error); - process.exit(1); -}); \ No newline at end of file diff --git a/backend/scripts/check-email-processor.js b/backend/scripts/check-email-processor.js deleted file mode 100644 index 0fe47ed..0000000 --- a/backend/scripts/check-email-processor.js +++ /dev/null @@ -1,157 +0,0 @@ -const { db } = require('../src/database/db'); -const winston = require('winston'); - -// Create a simple console logger -const logger = winston.createLogger({ - format: winston.format.simple(), - transports: [new winston.transports.Console()] -}); - -async function checkEmailProcessor() { - try { - logger.info('=== Email Processor Diagnostic Check ===\n'); - - // 1. Check pending emails - logger.info('1. Checking pending emails in queue...'); - const pendingEmails = await db('email_queue') - .where('status', 'pending') - .where('retry_count', '<', 3) - .orderBy('created_at', 'asc'); - - logger.info(`Found ${pendingEmails.length} pending emails\n`); - - if (pendingEmails.length > 0) { - logger.info('Pending email details:'); - pendingEmails.forEach((email, index) => { - logger.info(`\nEmail ${index + 1}:`); - logger.info(` ID: ${email.id}`); - logger.info(` Type: ${email.email_type}`); - logger.info(` Recipient: ${email.recipient_email}`); - logger.info(` Event ID: ${email.event_id}`); - logger.info(` Status: ${email.status}`); - logger.info(` Retry Count: ${email.retry_count}`); - logger.info(` Scheduled At: ${email.scheduled_at}`); - logger.info(` Created At: ${email.created_at}`); - logger.info(` Error: ${email.error_message || 'None'}`); - - // Check if email_data needs parsing - logger.info(` Email Data Type: ${typeof email.email_data}`); - if (email.email_data) { - try { - const data = typeof email.email_data === 'string' - ? JSON.parse(email.email_data) - : email.email_data; - logger.info(` Email Data Keys: ${Object.keys(data).join(', ')}`); - } catch (e) { - logger.error(` Failed to parse email_data: ${e.message}`); - } - } - }); - } - - // 2. Check failed emails - logger.info('\n\n2. Checking failed emails...'); - const failedEmails = await db('email_queue') - .where('status', 'failed') - .orderBy('created_at', 'desc') - .limit(5); - - logger.info(`Found ${failedEmails.length} failed emails (showing last 5)\n`); - - if (failedEmails.length > 0) { - failedEmails.forEach((email, index) => { - logger.info(`\nFailed Email ${index + 1}:`); - logger.info(` ID: ${email.id}`); - logger.info(` Type: ${email.email_type}`); - logger.info(` Retry Count: ${email.retry_count}`); - logger.info(` Error: ${email.error_message || 'No error message'}`); - logger.info(` Last Attempt: ${email.sent_at || 'Never'}`); - }); - } - - // 3. Check if email processor should be running - logger.info('\n\n3. Checking email processor configuration...'); - - // Check environment variables - const emailConfig = { - SMTP_HOST: process.env.SMTP_HOST, - SMTP_PORT: process.env.SMTP_PORT, - SMTP_USER: process.env.SMTP_USER, - SMTP_FROM: process.env.SMTP_FROM, - SMTP_SECURE: process.env.SMTP_SECURE, - EMAIL_PROCESSOR_ENABLED: process.env.EMAIL_PROCESSOR_ENABLED || 'true' - }; - - logger.info('Email configuration:'); - Object.entries(emailConfig).forEach(([key, value]) => { - if (key === 'SMTP_USER') { - logger.info(` ${key}: ${value ? '***' : 'NOT SET'}`); - } else { - logger.info(` ${key}: ${value || 'NOT SET'}`); - } - }); - - // 4. Test email processor functionality - logger.info('\n\n4. Testing email processor functionality...'); - - // Import the email processor - const { processEmailQueue, testEmailConnection } = require('../src/services/emailProcessor'); - - // Test email connection - logger.info('Testing email connection...'); - try { - const connectionTest = await testEmailConnection(); - logger.info(`Email connection test: ${connectionTest ? 'SUCCESS' : 'FAILED'}`); - } catch (error) { - logger.error(`Email connection test failed: ${error.message}`); - } - - // Try to process queue once manually - if (pendingEmails.length > 0) { - logger.info('\n\n5. Attempting to process email queue manually...'); - try { - await processEmailQueue(); - logger.info('Manual queue processing completed'); - - // Check status after processing - const stillPending = await db('email_queue') - .where('status', 'pending') - .where('retry_count', '<', 3) - .count('* as count') - .first(); - - logger.info(`Emails still pending after processing: ${stillPending.count}`); - } catch (error) { - logger.error(`Error processing queue: ${error.message}`); - logger.error(`Stack trace: ${error.stack}`); - } - } - - // 5. Check for any recent successful emails - logger.info('\n\n6. Checking recent successful emails...'); - const recentSuccess = await db('email_queue') - .where('status', 'sent') - .orderBy('sent_at', 'desc') - .limit(3); - - if (recentSuccess.length > 0) { - logger.info(`Last ${recentSuccess.length} successful emails:`); - recentSuccess.forEach((email, index) => { - logger.info(` ${index + 1}. Type: ${email.email_type}, Sent: ${email.sent_at}`); - }); - } else { - logger.info('No successfully sent emails found'); - } - - logger.info('\n\n=== Diagnostic check complete ==='); - - } catch (error) { - logger.error('Error running diagnostic check:', error); - } finally { - await db.destroy(); - process.exit(0); - } -} - -// Run the check -checkEmailProcessor(); \ No newline at end of file diff --git a/backend/scripts/check-email-templates.js b/backend/scripts/check-email-templates.js deleted file mode 100644 index 8f160cc..0000000 --- a/backend/scripts/check-email-templates.js +++ /dev/null @@ -1,66 +0,0 @@ -const { db } = require('../src/database/db'); - -async function checkEmailTemplates() { - try { - console.log('=== Email Templates Check ===\n'); - - // 1. Check table columns - console.log('1. Checking email_templates table structure...'); - - // Check which columns exist - const columnChecks = [ - 'subject', 'subject_en', 'subject_de', - 'body_html', 'body_html_en', 'body_html_de', - 'body_text', 'body_text_en', 'body_text_de' - ]; - - const existingColumns = []; - for (const col of columnChecks) { - const exists = await db.schema.hasColumn('email_templates', col); - if (exists) existingColumns.push(col); - } - - console.log(' Existing columns:', existingColumns.join(', ')); - - // 2. Get all templates - console.log('\n2. Current email templates:'); - const templates = await db('email_templates').select('*'); - - for (const template of templates) { - console.log(`\n Template: ${template.template_key}`); - console.log(' -------------------'); - - // Check which fields have content - const fields = ['subject', 'subject_en', 'subject_de', - 'body_html', 'body_html_en', 'body_html_de', - 'body_text', 'body_text_en', 'body_text_de']; - - for (const field of fields) { - if (template[field]) { - const preview = template[field].substring(0, 50) + '...'; - console.log(` ${field}: ${preview}`); - } - } - - // Check for German translations - const hasGermanSubject = template.subject_de || template.body_html_de; - console.log(` Has German translation: ${hasGermanSubject ? 'YES' : 'NO'}`); - } - - // 3. Summary - console.log('\n3. Summary:'); - const totalTemplates = templates.length; - const templatesWithGerman = templates.filter(t => t.subject_de || t.body_html_de).length; - console.log(` Total templates: ${totalTemplates}`); - console.log(` Templates with German: ${templatesWithGerman}`); - console.log(` Missing German: ${totalTemplates - templatesWithGerman}`); - - await db.destroy(); - } catch (error) { - console.error('Error:', error); - await db.destroy(); - process.exit(1); - } -} - -checkEmailTemplates(); \ No newline at end of file diff --git a/backend/scripts/check-german-templates.js b/backend/scripts/check-german-templates.js deleted file mode 100644 index 7583db8..0000000 --- a/backend/scripts/check-german-templates.js +++ /dev/null @@ -1,53 +0,0 @@ -const { db } = require('../src/database/db'); - -async function checkGermanTemplates() { - try { - console.log('=== German Email Template Content Check ===\n'); - - const templates = await db('email_templates').select('*'); - - for (const template of templates) { - console.log(`\nTemplate: ${template.template_key}`); - console.log('====================================='); - - // Check German subject - console.log('\nGERMAN SUBJECT:'); - console.log(template.subject_de || 'MISSING'); - - // Check if German HTML body has English content - console.log('\nGERMAN HTML BODY:'); - const germanHtml = template.body_html_de || ''; - - // Check for English phrases in German template - const englishPhrases = [ - 'Dear', 'Gallery', 'has been', 'Your photo', 'successfully', - 'Details:', 'Link:', 'Password:', 'Expires:', 'Event Date:', - 'Thank you', 'Best regards', 'View Gallery', 'days' - ]; - - const foundEnglish = englishPhrases.filter(phrase => - germanHtml.toLowerCase().includes(phrase.toLowerCase()) - ); - - if (foundEnglish.length > 0) { - console.log('⚠️ Found English phrases in German template:', foundEnglish.join(', ')); - } - - // Show first 500 chars of German HTML - console.log(germanHtml.substring(0, 500) + '...\n'); - - // Check German text body - console.log('GERMAN TEXT BODY:'); - const germanText = template.body_text_de || ''; - console.log(germanText.substring(0, 300) + '...\n'); - } - - await db.destroy(); - } catch (error) { - console.error('Error:', error); - await db.destroy(); - process.exit(1); - } -} - -checkGermanTemplates(); \ No newline at end of file diff --git a/backend/scripts/create-test-event.js b/backend/scripts/create-test-event.js deleted file mode 100644 index 292c5ca..0000000 --- a/backend/scripts/create-test-event.js +++ /dev/null @@ -1,58 +0,0 @@ -const path = require('path'); -require('dotenv').config({ path: path.join(__dirname, '../.env') }); -const { db } = require('../src/database/db'); -const bcrypt = require('bcrypt'); -const jwt = require('jsonwebtoken'); -const { v4: uuidv4 } = require('uuid'); - -async function createTestEvent() { - try { - console.log('Creating test event...'); - - // Hash a simple password - const passwordHash = await bcrypt.hash('test123', 10); - - // Generate share token - const shareToken = uuidv4().replace(/-/g, ''); - const shareLink = `http://localhost:3005/gallery/wedding-test123-2025-07-07/${shareToken}`; - - // Create event - const eventData = { - slug: 'wedding-test123-2025-07-07', - event_type: 'wedding', - event_name: 'Test Wedding', - event_date: '2025-07-07', - host_email: 'host@example.com', - admin_email: 'admin@example.com', - password_hash: passwordHash, - welcome_message: 'Welcome to our test wedding gallery!', - color_theme: null, // Use global theme - is_active: 1, - expires_at: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), - share_link: shareLink - }; - - // Delete existing event if it exists - await db('events').where('slug', eventData.slug).delete(); - - // Insert new event - const insertResult = await db('events').insert(eventData).returning('id'); - const eventId = insertResult[0]?.id || insertResult[0]; - console.log('Event created with ID:', eventId); - - console.log('\nTest event created successfully!'); - console.log('Event details:'); - console.log('- Name:', eventData.event_name); - console.log('- Slug:', eventData.slug); - console.log('- Password:', 'test123'); - console.log('- Share link:', shareLink); - console.log('\nYou can now access the gallery at the share link above'); - - process.exit(0); - } catch (error) { - console.error('Error creating test event:', error); - process.exit(1); - } -} - -createTestEvent(); \ No newline at end of file diff --git a/backend/scripts/debug-500-errors.js b/backend/scripts/debug-500-errors.js deleted file mode 100644 index 330017e..0000000 --- a/backend/scripts/debug-500-errors.js +++ /dev/null @@ -1,92 +0,0 @@ -require('dotenv').config(); -const { db } = require('../src/database/db'); - -async function debugEndpoints() { - console.log('Debugging 500 errors...\n'); - - try { - // Test email templates query - console.log('1. Testing email templates query:'); - try { - const templates = await db('email_templates') - .select('*') - .orderBy('template_key'); - - console.log(`Found ${templates.length} templates`); - if (templates.length > 0) { - console.log('First template columns:', Object.keys(templates[0])); - console.log('Template keys:', templates.map(t => t.template_key)); - } - } catch (error) { - console.error('Email templates query failed:', error.message); - console.error('Error code:', error.code); - } - - // Test notifications query - console.log('\n2. Testing notifications query:'); - try { - const notifications = await db('activity_logs') - .select( - 'activity_logs.*', - 'events.event_name' - ) - .leftJoin('events', 'activity_logs.event_id', 'events.id') - .whereNull('activity_logs.read_at') - .orderBy('activity_logs.created_at', 'desc') - .limit(5); - - console.log(`Found ${notifications.length} unread notifications`); - } catch (error) { - console.error('Notifications query failed:', error.message); - console.error('Error code:', error.code); - - // Check if it's a column issue - if (error.message.includes('column')) { - console.log('\nChecking activity_logs columns:'); - const columns = await db('activity_logs').columnInfo(); - console.log('Columns:', Object.keys(columns)); - } - } - - // Test specific template query - console.log('\n3. Testing specific template query (gallery_created):'); - try { - const template = await db('email_templates') - .where('template_key', 'gallery_created') - .first(); - - if (template) { - console.log('Template found:', template.template_key); - console.log('Has subject_en?', template.subject_en !== undefined); - console.log('Has subject?', template.subject !== undefined); - } else { - console.log('Template not found'); - } - } catch (error) { - console.error('Template query failed:', error.message); - } - - // Check CMS pages - console.log('\n4. Checking CMS pages:'); - try { - const pages = await db('cms_pages') - .select('slug', 'title', 'is_published') - .orderBy('slug'); - - console.log(`Found ${pages.length} CMS pages:`); - pages.forEach(page => { - console.log(` - ${page.slug}: ${page.title} (published: ${page.is_published})`); - }); - } catch (error) { - console.error('CMS pages query failed:', error.message); - } - - } catch (error) { - console.error('General error:', error); - } finally { - await db.destroy(); - process.exit(0); - } -} - -debugEndpoints(); \ No newline at end of file diff --git a/backend/scripts/debug-email-queue.js b/backend/scripts/debug-email-queue.js deleted file mode 100644 index ea6d76a..0000000 --- a/backend/scripts/debug-email-queue.js +++ /dev/null @@ -1,146 +0,0 @@ -const { db } = require('../src/database/db'); -const winston = require('winston'); - -// Create a simple console logger -const logger = winston.createLogger({ - format: winston.format.simple(), - transports: [new winston.transports.Console()] -}); - -async function debugEmailQueue() { - try { - logger.info('=== Email Queue Debug Report ===\n'); - - // 1. Count exactly like the admin dashboard does - logger.info('1. Admin Dashboard Query (ALL pending, no retry filter):'); - const [adminCount] = await db('email_queue').where('status', 'pending').count('* as count'); - logger.info(` Pending emails (admin dashboard view): ${adminCount.count}\n`); - - // 2. Count like the email processor does - logger.info('2. Email Processor Query (pending with retry_count < 3):'); - const [processorCount] = await db('email_queue') - .where('status', 'pending') - .where('retry_count', '<', 3) - .count('* as count'); - logger.info(` Pending emails (processor view): ${processorCount.count}\n`); - - // 3. Show the discrepancy - logger.info('3. Discrepancy Analysis:'); - if (adminCount.count !== processorCount.count) { - logger.info(` ⚠️ DISCREPANCY FOUND!`); - logger.info(` Admin shows: ${adminCount.count}`); - logger.info(` Processor will process: ${processorCount.count}`); - logger.info(` Difference: ${adminCount.count - processorCount.count} email(s)\n`); - - // Find the problematic emails - logger.info('4. Emails with retry_count >= 3 (still pending):'); - const stuckEmails = await db('email_queue') - .where('status', 'pending') - .where('retry_count', '>=', 3) - .select('*'); - - if (stuckEmails.length > 0) { - logger.info(` Found ${stuckEmails.length} stuck email(s):\n`); - stuckEmails.forEach((email, index) => { - logger.info(` Email ${index + 1}:`); - logger.info(` ID: ${email.id}`); - logger.info(` Type: ${email.email_type}`); - logger.info(` Recipient: ${email.recipient_email}`); - logger.info(` Status: ${email.status}`); - logger.info(` Retry Count: ${email.retry_count} ⚠️`); - logger.info(` Created: ${email.created_at}`); - logger.info(` Last Error: ${email.error_message || 'None'}\n`); - }); - } - } else { - logger.info(` ✅ No discrepancy - counts match\n`); - } - - // 5. Show ALL pending emails with details - logger.info('5. ALL Pending Emails (regardless of retry count):'); - const allPending = await db('email_queue') - .where('status', 'pending') - .orderBy('retry_count', 'desc') - .orderBy('created_at', 'asc'); - - if (allPending.length > 0) { - allPending.forEach((email, index) => { - const willProcess = email.retry_count < 3; - logger.info(`\n Email ${index + 1}: ${willProcess ? '✅ WILL PROCESS' : '❌ STUCK (max retries)'}`); - logger.info(` ID: ${email.id}`); - logger.info(` Type: ${email.email_type}`); - logger.info(` Recipient: ${email.recipient_email}`); - logger.info(` Event ID: ${email.event_id}`); - logger.info(` Retry Count: ${email.retry_count}/3`); - logger.info(` Created: ${email.created_at}`); - logger.info(` Scheduled: ${email.scheduled_at}`); - if (email.error_message) { - logger.info(` Last Error: ${email.error_message}`); - } - }); - } else { - logger.info(' No pending emails found'); - } - - // 6. Show counts by status - logger.info('\n\n6. Email Queue Summary by Status:'); - const statusCounts = await db('email_queue') - .select('status') - .count('* as count') - .groupBy('status') - .orderBy('status'); - - statusCounts.forEach(row => { - logger.info(` ${row.status}: ${row.count}`); - }); - - // 7. Failed emails summary - logger.info('\n7. Failed Emails Summary:'); - const failedSummary = await db('email_queue') - .where('status', 'failed') - .select('retry_count') - .count('* as count') - .groupBy('retry_count') - .orderBy('retry_count'); - - if (failedSummary.length > 0) { - failedSummary.forEach(row => { - logger.info(` Retry count ${row.retry_count}: ${row.count} email(s)`); - }); - } else { - logger.info(' No failed emails'); - } - - // 8. Recommendations - logger.info('\n\n=== RECOMMENDATIONS ==='); - - if (adminCount.count > processorCount.count) { - logger.info('\n❗ You have emails stuck with retry_count >= 3'); - logger.info(' These emails will NOT be processed automatically.'); - logger.info('\n To fix this, you can:'); - logger.info(' 1. Reset retry count: UPDATE email_queue SET retry_count = 0 WHERE status = \'pending\' AND retry_count >= 3;'); - logger.info(' 2. Mark as failed: UPDATE email_queue SET status = \'failed\' WHERE status = \'pending\' AND retry_count >= 3;'); - logger.info(' 3. Delete them: DELETE FROM email_queue WHERE status = \'pending\' AND retry_count >= 3;'); - } - - const anyPending = adminCount.count > 0; - if (anyPending && processorCount.count === 0) { - logger.info('\n❗ All pending emails have exceeded retry limit'); - logger.info(' The email processor will not attempt to send them.'); - } else if (anyPending && processorCount.count > 0) { - logger.info('\n✅ Email processor should process the pending emails on next run'); - logger.info(' Make sure the email processor service is running.'); - } - - logger.info('\n=== Debug report complete ==='); - - } catch (error) { - logger.error('Error running debug report:', error); - } finally { - await db.destroy(); - process.exit(0); - } -} - -// Run the debug -debugEmailQueue(); \ No newline at end of file diff --git a/backend/scripts/diagnose-thumbnails.js b/backend/scripts/diagnose-thumbnails.js deleted file mode 100755 index afc8f04..0000000 --- a/backend/scripts/diagnose-thumbnails.js +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env node - -/** - * Script to diagnose thumbnail serving issues - * Usage: node scripts/diagnose-thumbnails.js - */ - -const path = require('path'); -const fs = require('fs').promises; -const { db } = require('../src/database/db'); - -const STORAGE_PATH = process.env.STORAGE_PATH || path.join(__dirname, '../../storage'); -const THUMBNAILS_DIR = path.join(STORAGE_PATH, 'thumbnails'); - -async function diagnoseThumbnails(eventId) { - if (!eventId) { - console.error('Usage: node scripts/diagnose-thumbnails.js '); - process.exit(1); - } - - console.log(`Diagnosing thumbnails for event ID: ${eventId}`); - console.log(`Storage path: ${STORAGE_PATH}`); - console.log(`Thumbnails directory: ${THUMBNAILS_DIR}\n`); - - try { - // Get event info - const event = await db('events').where('id', eventId).first(); - if (!event) { - console.error(`Event not found with ID: ${eventId}`); - return; - } - - console.log(`Event: ${event.event_name} (${event.slug})`); - console.log(`Active: ${event.is_active}, Archived: ${event.is_archived}\n`); - - // Get photos for this event - const photos = await db('photos') - .where('event_id', eventId) - .select('id', 'filename', 'path', 'thumbnail_path'); - - console.log(`Found ${photos.length} photos in database\n`); - - let missingThumbnails = 0; - let existingThumbnails = 0; - let pathIssues = []; - - for (const photo of photos.slice(0, 10)) { // Check first 10 photos - console.log(`Photo ID ${photo.id}: ${photo.filename}`); - console.log(` Photo path: ${photo.path}`); - console.log(` Thumbnail path in DB: ${photo.thumbnail_path}`); - - if (photo.thumbnail_path) { - // Expected thumbnail filename - const expectedThumbName = `thumb_${photo.filename}`; - const expectedThumbPath = path.join(THUMBNAILS_DIR, expectedThumbName); - - // Check if thumbnail exists - try { - await fs.access(expectedThumbPath); - console.log(` ✓ Thumbnail exists at: ${expectedThumbName}`); - existingThumbnails++; - - // Check if DB path matches expected path - const dbThumbName = path.basename(photo.thumbnail_path); - if (dbThumbName !== expectedThumbName) { - console.log(` ⚠ Path mismatch! DB has: ${dbThumbName}, Expected: ${expectedThumbName}`); - pathIssues.push({ - photoId: photo.id, - dbPath: photo.thumbnail_path, - expectedPath: `thumbnails/${expectedThumbName}` - }); - } - } catch { - console.log(` ✗ Thumbnail missing: ${expectedThumbName}`); - missingThumbnails++; - } - } else { - console.log(` ✗ No thumbnail path in database`); - missingThumbnails++; - } - console.log(''); - } - - console.log('--- Summary ---'); - console.log(`Existing thumbnails: ${existingThumbnails}`); - console.log(`Missing thumbnails: ${missingThumbnails}`); - console.log(`Path issues: ${pathIssues.length}`); - - if (pathIssues.length > 0) { - console.log('\n--- Path Issues ---'); - console.log('The following photos have incorrect thumbnail paths in the database:'); - for (const issue of pathIssues) { - console.log(`Photo ID ${issue.photoId}:`); - console.log(` Current: ${issue.dbPath}`); - console.log(` Should be: ${issue.expectedPath}`); - } - - console.log('\nTo fix path issues, run:'); - console.log(`UPDATE photos SET thumbnail_path = 'thumbnails/thumb_' || filename WHERE event_id = ${eventId};`); - } - - // Check for any thumbnails in the directory that match this event - const files = await fs.readdir(THUMBNAILS_DIR); - const eventThumbnails = files.filter(f => { - // Try to match thumbnails for this event - for (const photo of photos) { - if (f === `thumb_${photo.filename}`) return true; - } - return false; - }); - - console.log(`\n--- Filesystem Check ---`); - console.log(`Found ${eventThumbnails.length} thumbnails in directory for this event`); - - } catch (error) { - console.error('Error during diagnosis:', error); - process.exit(1); - } -} - -// Parse command line arguments -const eventId = process.argv[2] ? parseInt(process.argv[2]) : null; - -// Run the diagnosis -diagnoseThumbnails(eventId).then(async () => { - await db.destroy(); - console.log('\nDiagnosis complete'); -}).catch(async error => { - console.error('Diagnosis failed:', error); - await db.destroy(); - process.exit(1); -}); \ No newline at end of file diff --git a/backend/scripts/fix-email-queue-schema.js b/backend/scripts/fix-email-queue-schema.js deleted file mode 100755 index 825e4c9..0000000 --- a/backend/scripts/fix-email-queue-schema.js +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env node - -/** - * Script to diagnose and fix email_queue schema issues - * This helps resolve the "column updated_at does not exist" error - */ - -require('dotenv').config(); -const { db } = require('../src/database/db'); - -async function checkAndFixEmailQueueSchema() { - console.log('Checking email_queue table schema...'); - - try { - // Get column information - const columns = await db('email_queue').columnInfo(); - console.log('\nCurrent email_queue columns:', Object.keys(columns)); - - // Check for updated_at column - if (columns.updated_at) { - console.log('\n⚠️ Found unexpected updated_at column in email_queue table!'); - console.log('This column should not exist and is causing errors.'); - - // Ask for confirmation before removing - console.log('\nRemoving updated_at column...'); - await db.schema.table('email_queue', (table) => { - table.dropColumn('updated_at'); - }); - console.log('✅ Removed updated_at column from email_queue table'); - } else { - console.log('✅ No updated_at column found (this is correct)'); - } - - // Verify required columns exist - const requiredColumns = [ - 'id', 'event_id', 'recipient_email', 'email_type', - 'email_data', 'status', 'scheduled_at', 'sent_at', - 'error_message', 'retry_count', 'created_at' - ]; - - const missingColumns = requiredColumns.filter(col => !columns[col]); - if (missingColumns.length > 0) { - console.log('\n⚠️ Missing required columns:', missingColumns); - } else { - console.log('✅ All required columns are present'); - } - - // Check for any database triggers - if (process.env.DATABASE_CLIENT === 'pg') { - console.log('\nChecking for PostgreSQL triggers on email_queue...'); - const triggers = await db.raw(` - SELECT trigger_name, event_manipulation, action_statement - FROM information_schema.triggers - WHERE event_object_table = 'email_queue' - AND trigger_schema = current_schema() - `); - - if (triggers.rows && triggers.rows.length > 0) { - console.log('⚠️ Found triggers on email_queue table:'); - triggers.rows.forEach(trigger => { - console.log(` - ${trigger.trigger_name} (${trigger.event_manipulation})`); - }); - } else { - console.log('✅ No triggers found on email_queue table'); - } - } - - // Test update query - console.log('\nTesting update query...'); - const testEmail = await db('email_queue') - .where('status', 'pending') - .first(); - - if (testEmail) { - try { - await db('email_queue') - .where('id', testEmail.id) - .update({ - retry_count: testEmail.retry_count - }); - console.log('✅ Update query works correctly'); - } catch (error) { - console.log('❌ Update query failed:', error.message); - } - } else { - console.log('ℹ️ No pending emails to test with'); - } - - console.log('\nSchema check complete!'); - - } catch (error) { - console.error('Error checking schema:', error); - } finally { - await db.destroy(); - } -} - -// Run the check -checkAndFixEmailQueueSchema(); \ No newline at end of file diff --git a/backend/scripts/fix-final-german-templates.js b/backend/scripts/fix-final-german-templates.js deleted file mode 100644 index 6473a86..0000000 --- a/backend/scripts/fix-final-german-templates.js +++ /dev/null @@ -1,88 +0,0 @@ -const { db } = require('../src/database/db'); - -async function fixFinalGermanTemplates() { - try { - console.log('Fixing remaining English words in German templates...\n'); - - // Get all templates - const templates = await db('email_templates').select('*'); - - for (const template of templates) { - let updated = false; - let updates = {}; - - // Fix subject_de - if (template.subject_de) { - updates.subject_de = template.subject_de; - } - - // Fix body_html_de - if (template.body_html_de) { - let html = template.body_html_de; - - // Replace English words with German - html = html.replace(/Gallery-Details:/g, 'Galerie-Details:'); - html = html.replace(/Galerie-Details:/g, 'Galerie-Details:'); - html = html.replace(/Details:/g, 'Details:'); - html = html.replace(/Link:/g, 'Link:'); - html = html.replace(/Gallery-Link:/g, 'Galerie-Link:'); - html = html.replace(/Galerie-Link:/g, 'Galerie-Link:'); - html = html.replace(/Archive-Details:/g, 'Archiv-Details:'); - html = html.replace(/Archiv-Details:/g, 'Archiv-Details:'); - - if (html !== template.body_html_de) { - updates.body_html_de = html; - updated = true; - } - } - - // Fix body_text_de - if (template.body_text_de) { - let text = template.body_text_de; - - text = text.replace(/Gallery-Details:/g, 'Galerie-Details:'); - text = text.replace(/Galerie-Details:/g, 'Galerie-Details:'); - text = text.replace(/Details:/g, 'Details:'); - text = text.replace(/Link:/g, 'Link:'); - text = text.replace(/Gallery-Link:/g, 'Galerie-Link:'); - text = text.replace(/Galerie-Link:/g, 'Galerie-Link:'); - text = text.replace(/Archive-Details:/g, 'Archiv-Details:'); - text = text.replace(/Archiv-Details:/g, 'Archiv-Details:'); - - if (text !== template.body_text_de) { - updates.body_text_de = text; - updated = true; - } - } - - // Also update the non-language-specific fields to match German - if (template.body_html_de) { - updates.body_html = template.body_html_de; - } - if (template.body_text_de) { - updates.body_text = template.body_text_de; - } - if (template.subject_de) { - updates.subject = template.subject_de; - } - - if (updated || Object.keys(updates).length > 0) { - await db('email_templates') - .where('template_key', template.template_key) - .update(updates); - console.log(`✅ Updated ${template.template_key}`); - } else { - console.log(`⏭️ No changes needed for ${template.template_key}`); - } - } - - console.log('\nDone!'); - await db.destroy(); - } catch (error) { - console.error('Error:', error); - await db.destroy(); - process.exit(1); - } -} - -fixFinalGermanTemplates(); \ No newline at end of file diff --git a/backend/scripts/fix-production-issues.js b/backend/scripts/fix-production-issues.js deleted file mode 100644 index 5fc0c13..0000000 --- a/backend/scripts/fix-production-issues.js +++ /dev/null @@ -1,145 +0,0 @@ -require('dotenv').config(); -const { db } = require('../src/database/db'); - -async function fixProductionIssues() { - console.log('Fixing production database issues...\n'); - - try { - // 1. Check and fix email_templates structure - console.log('1. Checking email_templates structure:'); - const emailColumns = await db('email_templates').columnInfo(); - console.log('Current columns:', Object.keys(emailColumns)); - - // Check if we need to add basic columns back - const hasSubject = 'subject' in emailColumns; - const hasSubjectEn = 'subject_en' in emailColumns; - - if (hasSubjectEn && !hasSubject) { - console.log('Adding basic columns back to email_templates...'); - await db.schema.alterTable('email_templates', (table) => { - table.string('subject'); - table.text('body_html'); - table.text('body_text'); - }); - - // Copy values from _en columns - await db('email_templates').update({ - subject: db.raw('subject_en'), - body_html: db.raw('body_html_en'), - body_text: db.raw('body_text_en') - }); - console.log('Basic columns added successfully'); - } - - // 2. Ensure default templates exist - console.log('\n2. Checking email templates:'); - const templateCount = await db('email_templates').count('* as count'); - console.log('Template count:', templateCount[0].count); - - if (templateCount[0].count === 0) { - console.log('No templates found, inserting defaults...'); - const defaultTemplates = [ - { - template_key: 'gallery_created', - subject: 'Your Photo Gallery is Ready!', - body_html: '

Gallery Created Successfully

...', - body_text: 'Gallery Created Successfully...', - variables: JSON.stringify(['host_name', 'event_name', 'event_date', 'gallery_link', 'gallery_password', 'expiry_date']) - }, - { - template_key: 'expiration_warning', - subject: 'Your Photo Gallery Expires Soon', - body_html: '

Gallery Expiring Soon

...', - body_text: 'Gallery Expiring Soon...', - variables: JSON.stringify(['host_name', 'event_name', 'days_remaining', 'gallery_link']) - }, - { - template_key: 'gallery_expired', - subject: 'Your Photo Gallery Has Expired', - body_html: '

Gallery Expired

...', - body_text: 'Gallery Expired...', - variables: JSON.stringify(['host_name', 'event_name']) - }, - { - template_key: 'archive_complete', - subject: 'Gallery Archive Complete', - body_html: '

Archive Complete

...', - body_text: 'Archive Complete...', - variables: JSON.stringify(['host_name', 'event_name', 'archive_size']) - } - ]; - - for (const template of defaultTemplates) { - // Add language columns if they exist - if (hasSubjectEn) { - template.subject_en = template.subject; - template.body_html_en = template.body_html; - template.body_text_en = template.body_text; - template.subject_de = template.subject; - template.body_html_de = template.body_html; - template.body_text_de = template.body_text; - } - - await db('email_templates').insert(template); - } - console.log('Default templates inserted'); - } - - // 3. Check activity_logs structure - console.log('\n3. Checking activity_logs structure:'); - const activityColumns = await db('activity_logs').columnInfo(); - console.log('Columns:', Object.keys(activityColumns)); - - // Check if read_at exists - if (!('read_at' in activityColumns)) { - console.log('Adding read_at column to activity_logs...'); - await db.schema.alterTable('activity_logs', (table) => { - table.datetime('read_at').nullable(); - }); - console.log('read_at column added'); - } - - // 4. Check and add CMS pages - console.log('\n4. Checking CMS pages:'); - const cmsColumns = await db('cms_pages').columnInfo(); - console.log('CMS columns:', Object.keys(cmsColumns)); - - const impressum = await db('cms_pages').where('slug', 'impressum').first(); - const datenschutz = await db('cms_pages').where('slug', 'datenschutz').first(); - - if (!impressum) { - console.log('Adding Impressum page...'); - await db('cms_pages').insert({ - slug: 'impressum', - title_en: 'Legal Notice', - title_de: 'Impressum', - content_en: '

Legal Notice

Your legal information here...

', - content_de: '

Impressum

Ihre rechtlichen Informationen hier...

', - updated_at: new Date() - }); - } - - if (!datenschutz) { - console.log('Adding Datenschutz page...'); - await db('cms_pages').insert({ - slug: 'datenschutz', - title_en: 'Privacy Policy', - title_de: 'Datenschutzerklärung', - content_en: '

Privacy Policy

Your privacy policy here...

', - content_de: '

Datenschutzerklärung

Ihre Datenschutzerklärung hier...

', - updated_at: new Date() - }); - } - - console.log('\n✅ All fixes applied successfully!'); - - } catch (error) { - console.error('Error fixing issues:', error); - console.error('Stack:', error.stack); - } finally { - await db.destroy(); - process.exit(0); - } -} - -fixProductionIssues(); \ No newline at end of file diff --git a/backend/scripts/fix-stuck-emails.js b/backend/scripts/fix-stuck-emails.js deleted file mode 100644 index 812486c..0000000 --- a/backend/scripts/fix-stuck-emails.js +++ /dev/null @@ -1,126 +0,0 @@ -const { db } = require('../src/database/db'); -const winston = require('winston'); - -// Create a simple console logger -const logger = winston.createLogger({ - format: winston.format.simple(), - transports: [new winston.transports.Console()] -}); - -async function fixStuckEmails() { - try { - logger.info('=== Fix Stuck Emails Script ===\n'); - - // 1. Find stuck emails - logger.info('1. Finding stuck emails (pending with retry_count >= 3)...'); - const stuckEmails = await db('email_queue') - .where('status', 'pending') - .where('retry_count', '>=', 3) - .select('*'); - - if (stuckEmails.length === 0) { - logger.info(' ✅ No stuck emails found!'); - logger.info('\n=== Script complete ==='); - await db.destroy(); - process.exit(0); - } - - logger.info(` Found ${stuckEmails.length} stuck email(s)\n`); - - // 2. Show details - logger.info('2. Stuck email details:'); - stuckEmails.forEach((email, index) => { - logger.info(`\n Email ${index + 1}:`); - logger.info(` ID: ${email.id}`); - logger.info(` Type: ${email.email_type}`); - logger.info(` Recipient: ${email.recipient_email}`); - logger.info(` Retry Count: ${email.retry_count}`); - logger.info(` Last Error: ${email.error_message || 'None'}`); - }); - - // 3. Ask for action - logger.info('\n\n3. Choose an action:'); - logger.info(' 1. Reset retry count to 0 (emails will be retried)'); - logger.info(' 2. Mark as failed (emails will not be retried)'); - logger.info(' 3. Delete these emails'); - logger.info(' 4. Cancel (do nothing)'); - - // Get command line argument - const action = process.argv[2]; - - if (!action || !['reset', 'fail', 'delete'].includes(action)) { - logger.info('\n❗ No valid action specified'); - logger.info('\nUsage:'); - logger.info(' node fix-stuck-emails.js reset - Reset retry count to 0'); - logger.info(' node fix-stuck-emails.js fail - Mark as failed'); - logger.info(' node fix-stuck-emails.js delete - Delete stuck emails'); - await db.destroy(); - process.exit(1); - } - - // 4. Execute action - logger.info(`\n4. Executing action: ${action.toUpperCase()}`); - - const emailIds = stuckEmails.map(e => e.id); - - switch (action) { - case 'reset': - await db('email_queue') - .whereIn('id', emailIds) - .update({ - retry_count: 0, - error_message: null - }); - logger.info(` ✅ Reset retry count for ${emailIds.length} email(s)`); - logger.info(' These emails will be processed on the next run'); - break; - - case 'fail': - await db('email_queue') - .whereIn('id', emailIds) - .update({ - status: 'failed' - }); - logger.info(` ✅ Marked ${emailIds.length} email(s) as failed`); - logger.info(' These emails will not be retried'); - break; - - case 'delete': - await db('email_queue') - .whereIn('id', emailIds) - .delete(); - logger.info(` ✅ Deleted ${emailIds.length} email(s)`); - break; - } - - // 5. Show updated counts - logger.info('\n5. Updated email queue status:'); - const [pendingCount] = await db('email_queue') - .where('status', 'pending') - .count('* as count'); - const [processableCount] = await db('email_queue') - .where('status', 'pending') - .where('retry_count', '<', 3) - .count('* as count'); - - logger.info(` Total pending: ${pendingCount.count}`); - logger.info(` Processable (retry < 3): ${processableCount.count}`); - - if (pendingCount.count !== processableCount.count) { - logger.info(` ⚠️ Still have ${pendingCount.count - processableCount.count} stuck email(s)`); - } else { - logger.info(' ✅ No stuck emails remaining'); - } - - logger.info('\n=== Script complete ==='); - - } catch (error) { - logger.error('Error:', error); - } finally { - await db.destroy(); - process.exit(0); - } -} - -// Run the fix -fixStuckEmails(); \ No newline at end of file diff --git a/backend/scripts/run-email-processor.js b/backend/scripts/run-email-processor.js deleted file mode 100644 index e229a4e..0000000 --- a/backend/scripts/run-email-processor.js +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env node - -const { db } = require('../src/database/db'); -const { - initializeTransporter, - processEmailQueue, - testEmailConnection -} = require('../src/services/emailProcessor'); -const winston = require('winston'); - -// Create a simple console logger -const logger = winston.createLogger({ - format: winston.format.simple(), - transports: [new winston.transports.Console()] -}); - -async function runEmailProcessor(runOnce = false) { - try { - logger.info('=== Starting Email Processor ===\n'); - - // Initialize transporter - logger.info('Initializing email transporter...'); - await initializeTransporter(); - - // Test connection - logger.info('Testing email connection...'); - const connectionOk = await testEmailConnection(); - - if (!connectionOk) { - logger.error('Email connection test failed! Check your SMTP configuration.'); - logger.info('\nRequired environment variables:'); - logger.info('- SMTP_HOST'); - logger.info('- SMTP_PORT'); - logger.info('- SMTP_USER'); - logger.info('- SMTP_PASS'); - logger.info('- SMTP_FROM'); - process.exit(1); - } - - logger.info('Email connection test successful!\n'); - - if (runOnce) { - // Process queue once - logger.info('Processing email queue once...'); - await processEmailQueue(); - logger.info('Email processing complete'); - - // Show final status - const pendingCount = await db('email_queue') - .where('status', 'pending') - .where('retry_count', '<', 3) - .count('* as count') - .first(); - - logger.info(`\nEmails still pending: ${pendingCount.count}`); - - await db.destroy(); - process.exit(0); - } else { - // Run continuously - logger.info('Starting continuous email processor...'); - logger.info('Processing emails every 60 seconds. Press Ctrl+C to stop.\n'); - - // Process immediately - await processEmailQueue(); - - // Then every minute - setInterval(async () => { - try { - await processEmailQueue(); - } catch (error) { - logger.error('Error processing email queue:', error); - } - }, 60000); - } - - } catch (error) { - logger.error('Fatal error:', error); - await db.destroy(); - process.exit(1); - } -} - -// Handle graceful shutdown -process.on('SIGINT', async () => { - logger.info('\n\nShutting down email processor...'); - await db.destroy(); - process.exit(0); -}); - -// Check command line arguments -const args = process.argv.slice(2); -const runOnce = args.includes('--once') || args.includes('-o'); - -if (args.includes('--help') || args.includes('-h')) { - console.log(` -Email Processor Runner - -Usage: node run-email-processor.js [options] - -Options: - --once, -o Process the email queue once and exit - --help, -h Show this help message - -By default, the processor runs continuously, checking for emails every 60 seconds. - `); - process.exit(0); -} - -// Run the processor -runEmailProcessor(runOnce); \ No newline at end of file diff --git a/backend/scripts/run-migrations.js b/backend/scripts/run-migrations.js deleted file mode 100644 index 92019fc..0000000 --- a/backend/scripts/run-migrations.js +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env node - -/** - * Run database migrations using existing db connection - */ - -const { db } = require('../src/database/db'); - -async function runMigrations() { - console.log('Running database migrations...\n'); - - try { - // Run all pending migrations - const result = await db.migrate.latest({ - directory: './migrations' - }); - - if (result[1].length === 0) { - console.log('✓ Database is already up to date'); - } else { - console.log(`✓ Ran ${result[1].length} migrations:`); - result[1].forEach(migration => { - console.log(` - ${migration}`); - }); - } - - // Show current migration status - const list = await db.migrate.list(); - console.log(`\nCurrent status: ${list[0].length} completed migrations`); - - await db.destroy(); - process.exit(0); - } catch (error) { - console.error('Migration error:', error); - await db.destroy(); - process.exit(1); - } -} - -runMigrations(); \ No newline at end of file diff --git a/backend/scripts/test-backup-manifest.js b/backend/scripts/test-backup-manifest.js deleted file mode 100644 index ca9669e..0000000 --- a/backend/scripts/test-backup-manifest.js +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/env node - -/** - * Test script for backup manifest generator - * Demonstrates all features of the manifest generator - */ - -const path = require('path'); -const fs = require('fs').promises; -const backupManifest = require('../src/services/backupManifest'); -const logger = require('../src/utils/logger'); - -async function testManifestGeneration() { - console.log('=== Testing Backup Manifest Generator ===\n'); - - try { - // 1. Generate a full backup manifest - console.log('1. Generating full backup manifest...'); - - const fullManifestOptions = { - backupType: 'full', - backupPath: '/backup/full/2025-01-21', - files: [ - { - path: '/storage/events/active/wedding-smith-2025/DSC_001.jpg', - relativePath: 'events/active/wedding-smith-2025/DSC_001.jpg', - size: 2456789, - modified: new Date('2025-01-20T10:30:00Z'), - checksum: 'a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890', - permissions: '644' - }, - { - path: '/storage/events/active/wedding-smith-2025/DSC_002.jpg', - relativePath: 'events/active/wedding-smith-2025/DSC_002.jpg', - size: 2156789, - modified: new Date('2025-01-20T10:31:00Z'), - checksum: 'b2c3d4e5f67890123456789012345678901234567890123456789012345678901', - permissions: '644' - }, - { - path: '/storage/thumbnails/wedding-smith-2025/thumb_DSC_001.jpg', - relativePath: 'thumbnails/wedding-smith-2025/thumb_DSC_001.jpg', - size: 45678, - modified: new Date('2025-01-20T10:35:00Z'), - checksum: 'c3d4e5f678901234567890123456789012345678901234567890123456789012', - permissions: '644' - } - ], - databaseInfo: { - type: 'sqlite', - backupFile: 'database-backup-20250121-103000.sql.gz', - size: 1048576, - checksum: 'd4e5f6789012345678901234567890123456789012345678901234567890123', - tables: { - events: 156, - photos: 4523, - access_logs: 12456, - admin_users: 3 - }, - rowCounts: { - events: 156, - photos: 4523, - access_logs: 12456, - admin_users: 3 - } - }, - format: 'json', - customMetadata: { - operator: 'admin@example.com', - reason: 'Scheduled daily backup', - retentionDays: 30, - compressionType: 'gzip' - } - }; - - const fullManifest = await backupManifest.generateManifest(fullManifestOptions); - - // Save in both formats - const jsonPath = path.join(__dirname, 'test-manifest-full.json'); - const yamlPath = path.join(__dirname, 'test-manifest-full.yaml'); - - await backupManifest.saveManifest(fullManifest, jsonPath, 'json'); - await backupManifest.saveManifest(fullManifest, yamlPath, 'yaml'); - - console.log('✓ Full backup manifest generated and saved\n'); - - // 2. Generate summary report - console.log('2. Generating summary report...'); - const summaryReport = backupManifest.generateSummaryReport(fullManifest); - console.log(summaryReport); - console.log('\n'); - - // 3. Load and validate manifest - console.log('3. Loading and validating manifest...'); - const loadedManifest = await backupManifest.loadManifest(jsonPath); - console.log('✓ Manifest loaded and validated successfully\n'); - - // 4. Generate incremental backup manifest - console.log('4. Generating incremental backup manifest...'); - - const incrementalOptions = { - backupType: 'incremental', - backupPath: '/backup/incremental/2025-01-22', - parentBackupId: fullManifest.backup.id, - files: [ - // Original files with same checksums (unchanged) - fullManifestOptions.files[0], - fullManifestOptions.files[2], - // Modified file - { - ...fullManifestOptions.files[1], - size: 2256789, - modified: new Date('2025-01-21T14:00:00Z'), - checksum: 'e5f678901234567890123456789012345678901234567890123456789012345' - }, - // New file - { - path: '/storage/events/active/wedding-smith-2025/DSC_003.jpg', - relativePath: 'events/active/wedding-smith-2025/DSC_003.jpg', - size: 2356789, - modified: new Date('2025-01-21T14:30:00Z'), - checksum: 'f6789012345678901234567890123456789012345678901234567890123456', - permissions: '644' - } - ], - databaseInfo: { - ...fullManifestOptions.databaseInfo, - size: 1148576, - checksum: 'g7890123456789012345678901234567890123456789012345678901234567', - rowCounts: { - events: 158, - photos: 4567, - access_logs: 12789, - admin_users: 3 - } - } - }; - - const incrementalManifest = await backupManifest.generateIncrementalManifest( - incrementalOptions, - fullManifest - ); - - const incrementalJsonPath = path.join(__dirname, 'test-manifest-incremental.json'); - await backupManifest.saveManifest(incrementalManifest, incrementalJsonPath, 'json'); - - console.log('✓ Incremental backup manifest generated'); - console.log(` - Added files: ${incrementalManifest.incremental.changes.added_files_count}`); - console.log(` - Modified files: ${incrementalManifest.incremental.changes.modified_files_count}`); - console.log(` - Deleted files: ${incrementalManifest.incremental.changes.deleted_files_count}`); - console.log(` - Size difference: ${(incrementalManifest.incremental.changes.size_difference / 1024).toFixed(2)} KB\n`); - - // 5. Compare manifests - console.log('5. Comparing manifests...'); - const comparison = backupManifest.compareManifests(incrementalManifest, fullManifest); - console.log('Comparison results:'); - console.log(` - Added: ${comparison.added_files.length} files`); - console.log(` - Modified: ${comparison.modified_files.length} files`); - console.log(` - Deleted: ${comparison.deleted_files.length} files`); - console.log(` - Unchanged: ${comparison.unchanged_files.length} files`); - console.log(` - Database changed: ${comparison.database_changes.checksum_changed ? 'Yes' : 'No'}\n`); - - // 6. Test manifest integrity - console.log('6. Testing manifest integrity...'); - - // Corrupt the manifest - const corruptedManifest = JSON.parse(JSON.stringify(incrementalManifest)); - corruptedManifest.files.manifest[0].size = 9999999; // Change a file size - - try { - backupManifest.validateManifest(corruptedManifest); - console.log('✗ Validation should have failed for corrupted manifest'); - } catch (error) { - console.log('✓ Correctly detected corrupted manifest:', error.message); - } - - console.log('\n=== All tests completed successfully! ==='); - - // Clean up test files - await fs.unlink(jsonPath).catch(() => {}); - await fs.unlink(yamlPath).catch(() => {}); - await fs.unlink(incrementalJsonPath).catch(() => {}); - - } catch (error) { - console.error('Test failed:', error); - logger.error('Manifest test failed:', error); - process.exit(1); - } -} - -// Run tests -testManifestGeneration().catch(console.error); \ No newline at end of file diff --git a/backend/scripts/test-cms-formatting.js b/backend/scripts/test-cms-formatting.js deleted file mode 100644 index b650c77..0000000 --- a/backend/scripts/test-cms-formatting.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Test script to verify CMS and email formatting improvements - */ - -const { formatWelcomeMessage, nl2br } = require('../src/utils/formatters'); - -console.log('Testing CMS and Email Formatting Improvements\n'); - -// Test 1: Basic line break conversion -console.log('Test 1: Basic line break conversion'); -const basicText = `Hello, -This is line 1. -This is line 2. - -This is line 4 with an extra break.`; - -console.log('Input:'); -console.log(basicText); -console.log('\nOutput (nl2br):'); -console.log(nl2br(basicText)); -console.log('\n---\n'); - -// Test 2: Welcome message formatting -console.log('Test 2: Welcome message formatting'); -const welcomeMessage = `Dear guests, - -We're so excited to share these special moments with you! - -Please note: -- Download your photos before the expiration date -- The password is case-sensitive -- Contact us if you have any issues - -Thank you for being part of our special day! - -Best regards, -Sarah & John`; - -console.log('Input:'); -console.log(welcomeMessage); -console.log('\nOutput (formatWelcomeMessage):'); -console.log(formatWelcomeMessage(welcomeMessage)); -console.log('\n---\n'); - -// Test 3: Empty and edge cases -console.log('Test 3: Edge cases'); -console.log('Empty string:', formatWelcomeMessage('')); -console.log('Null:', formatWelcomeMessage(null)); -console.log('Only spaces:', formatWelcomeMessage(' \n \n ')); -console.log('Single line:', formatWelcomeMessage('This is a single line message')); - -console.log('\nAll tests completed!'); \ No newline at end of file diff --git a/backend/scripts/test-german-emails.js b/backend/scripts/test-german-emails.js deleted file mode 100644 index 8b34f5b..0000000 --- a/backend/scripts/test-german-emails.js +++ /dev/null @@ -1,85 +0,0 @@ -const { db } = require('../src/database/db'); -const { processTemplate } = require('../src/services/emailProcessor'); - -async function testGermanEmails() { - try { - console.log('=== Testing German Email Templates ===\n'); - - // Test variables - const testVars = { - host_name: 'Max Mustermann', - event_name: 'Hochzeit Schmidt', - event_date: '15.07.2024', - gallery_link: 'https://example.com/gallery/test', - gallery_password: 'test1234', - expiry_date: '15.08.2024', - days_remaining: '7', - welcome_message: 'Herzlich willkommen zu unserer Hochzeitsgalerie!', - archive_size: '250 MB', - archive_date: '16.08.2024', - photo_count: '347', - admin_email: 'support@example.com', - eventId: 1 - }; - - const templates = await db('email_templates').select('*'); - - for (const template of templates) { - console.log(`\n========== ${template.template_key.toUpperCase()} ==========`); - - // Process German version - const germanResult = await processGermanTemplate(template, testVars); - - console.log('\n--- GERMAN VERSION ---'); - console.log('Subject:', germanResult.subject); - console.log('\nHTML Preview (first 500 chars):'); - console.log(germanResult.htmlBody.substring(0, 500) + '...\n'); - - // Check for any remaining English text - const englishWords = ['Dear', 'Gallery', 'Details:', 'Link:', 'Password:', 'days', 'Thank you']; - const foundEnglish = englishWords.filter(word => - germanResult.htmlBody.includes(word) || germanResult.subject.includes(word) - ); - - if (foundEnglish.length > 0) { - console.log('⚠️ WARNING: Found English words:', foundEnglish.join(', ')); - } else { - console.log('✅ No English words found in German template'); - } - } - - await db.destroy(); - } catch (error) { - console.error('Error:', error); - await db.destroy(); - process.exit(1); - } -} - -async function processGermanTemplate(template, variables) { - // Process template as German - const subjectField = 'subject_de'; - const htmlField = 'body_html_de'; - const textField = 'body_text_de'; - - let subject = template[subjectField] || template.subject || ''; - let htmlBody = template[htmlField] || template.body_html || ''; - let textBody = template[textField] || template.body_text || ''; - - // Replace variables - Object.keys(variables).forEach(key => { - const regex = new RegExp(`{{${key}}}`, 'g'); - subject = subject.replace(regex, variables[key]); - htmlBody = htmlBody.replace(regex, variables[key]); - textBody = textBody.replace(regex, variables[key]); - }); - - // Handle conditionals (simplified) - htmlBody = htmlBody.replace(/{{#if welcome_message}}[\s\S]*?{{\/if}}/g, (match) => { - return variables.welcome_message ? match.replace(/{{#if welcome_message}}|{{\/if}}/g, '') : ''; - }); - - return { subject, htmlBody, textBody }; -} - -testGermanEmails(); \ No newline at end of file diff --git a/backend/scripts/test-photo-auth.js b/backend/scripts/test-photo-auth.js deleted file mode 100755 index 955f269..0000000 --- a/backend/scripts/test-photo-auth.js +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env node - -/** - * Script to test photo authentication - * Usage: node scripts/test-photo-auth.js - */ - -const axios = require('axios'); - -async function testPhotoAuth(token) { - if (!token) { - console.error('Usage: node scripts/test-photo-auth.js '); - console.error('\nTo get a token, login to a gallery and check localStorage for gallery_token_'); - process.exit(1); - } - - const baseUrl = process.env.API_URL || 'http://localhost:3001'; - - console.log(`Testing photo authentication with token: ${token.substring(0, 20)}...`); - console.log(`Base URL: ${baseUrl}\n`); - - // Test URLs - const tests = [ - { - name: 'Thumbnail via static route', - url: `${baseUrl}/thumbnails/thumb_Test_Gallery_uncategorized_5210.jpg`, - headers: { 'Authorization': `Bearer ${token}` } - }, - { - name: 'Photo via static route', - url: `${baseUrl}/photos/wedding-test-gallery-2025-07-14-1/Test_Gallery_uncategorized_5210.jpg`, - headers: { 'Authorization': `Bearer ${token}` } - }, - { - name: 'Gallery photos API', - url: `${baseUrl}/api/gallery/wedding-test-gallery-2025-07-14-1/photos`, - headers: { 'Authorization': `Bearer ${token}` } - } - ]; - - for (const test of tests) { - console.log(`Testing: ${test.name}`); - console.log(`URL: ${test.url}`); - - try { - const response = await axios.get(test.url, { - headers: test.headers, - validateStatus: () => true // Don't throw on any status - }); - - console.log(`Status: ${response.status}`); - console.log(`Headers:`, response.headers['content-type']); - - if (response.status === 200) { - if (test.name.includes('API')) { - console.log(`Photos count: ${response.data.photos?.length || 0}`); - } else { - console.log(`Content length: ${response.headers['content-length']} bytes`); - } - } else { - console.log(`Error:`, response.data); - } - } catch (error) { - console.log(`Network error:`, error.message); - } - - console.log('---\n'); - } - - // Decode token to show info - try { - const parts = token.split('.'); - const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString()); - console.log('Token payload:', payload); - } catch (error) { - console.log('Failed to decode token'); - } -} - -// Get token from command line -const token = process.argv[2]; - -testPhotoAuth(token).catch(error => { - console.error('Test failed:', error); - process.exit(1); -}); \ No newline at end of file diff --git a/backend/scripts/test-security-logging.js b/backend/scripts/test-security-logging.js deleted file mode 100644 index 407c74d..0000000 --- a/backend/scripts/test-security-logging.js +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env node - -/** - * Test script to verify security logging is working correctly - * Run with: node scripts/test-security-logging.js - */ - -require('dotenv').config({ path: '../.env' }); -const logger = require('../src/utils/logger'); - -console.log('Testing Security Logging...\n'); - -// Test 1: Basic logging -console.log('1. Testing basic logging levels:'); -logger.info('Test info message', { test: true }); -logger.warn('Test warning message', { test: true }); -logger.error('Test error message', { test: true }); - -// Test 2: Security event logging -console.log('\n2. Testing security event logging:'); - -// Rate limit exceeded -logger.warn('Rate limit exceeded', { - ip: '192.168.1.100', - path: '/api/admin/login', - method: 'POST', - authenticated: false, - userAgent: 'Mozilla/5.0 Test', - timestamp: new Date().toISOString(), - rateLimitInfo: { - limit: 5, - current: 6, - remaining: 0, - resetTime: new Date(Date.now() + 900000).toISOString() - } -}); - -// Auth rate limit -logger.warn('Auth rate limit exceeded', { - ip: '192.168.1.101', - path: '/api/auth/admin/login', - method: 'POST', - userAgent: 'Mozilla/5.0 Test', - authType: 'admin', - timestamp: new Date().toISOString() -}); - -// Failed login -logger.warn('Failed login attempt', { - username: 'testuser', - ip: '192.168.1.102', - userAgent: 'Mozilla/5.0 Test', - reason: 'invalid_credentials', - timestamp: new Date().toISOString() -}); - -// JWT validation failure -logger.warn('JWT validation failed', { - ip: '192.168.1.103', - path: '/api/admin/events', - method: 'GET', - userAgent: 'Mozilla/5.0 Test', - error: 'TokenExpiredError', - message: 'jwt expired', - timestamp: new Date().toISOString() -}); - -// Account lockout -logger.warn('Login attempt on locked account', { - username: 'lockeduser', - ip: '192.168.1.104', - remainingLockTime: 1200, - timestamp: new Date().toISOString() -}); - -// Suspicious activity -logger.warn('Suspicious login activity detected', { - username: 'suspicioususer', - ips: ['192.168.1.105', '192.168.1.106', '192.168.1.107'], - timeWindow: '15 minutes', - timestamp: new Date().toISOString() -}); - -console.log('\n3. Check log files:'); -console.log('- logs/security.log - Should contain all security warnings'); -console.log('- logs/error.log - Should contain error messages'); -console.log('- logs/combined.log - Should contain all messages'); - -console.log('\n✅ Security logging test complete!'); -console.log('Review the log files to ensure all events are properly captured.'); - -// Give logger time to flush -setTimeout(() => { - process.exit(0); -}, 1000); \ No newline at end of file diff --git a/backend/scripts/verify-template-equality.js b/backend/scripts/verify-template-equality.js deleted file mode 100644 index c922797..0000000 --- a/backend/scripts/verify-template-equality.js +++ /dev/null @@ -1,110 +0,0 @@ -const { db } = require('../src/database/db'); - -async function verifyTemplateEquality() { - try { - console.log('Verifying template equality between German and English versions...\n'); - - const templates = await db('email_templates').select('*'); - - for (const template of templates) { - console.log(`\n=== ${template.template_key.toUpperCase()} ===`); - - // Check subject length similarity - const subjectEnLength = template.subject_en?.length || 0; - const subjectDeLength = template.subject_de?.length || 0; - console.log(`Subject length - EN: ${subjectEnLength}, DE: ${subjectDeLength}`); - - // Check HTML content features - const htmlEn = template.body_html_en || ''; - const htmlDe = template.body_html_de || ''; - - // Check for key features in both versions - const features = [ - { name: 'Handlebars conditionals', pattern: /{{#if/g }, - { name: 'Styled divs', pattern: /style="/g }, - { name: 'Background colors', pattern: /background-color:/g }, - { name: 'Buttons/CTAs', pattern: //g }, - { name: 'Icons/Emojis', pattern: /[📧📞✅⚠️]/g }, - { name: 'Lists', pattern: /
    /g } - ]; - - console.log('\nFeature comparison:'); - for (const feature of features) { - const enCount = (htmlEn.match(feature.pattern) || []).length; - const deCount = (htmlDe.match(feature.pattern) || []).length; - const status = enCount === deCount ? '✅' : '❌'; - console.log(`${status} ${feature.name}: EN=${enCount}, DE=${deCount}`); - } - - // Check text content length - const textEn = template.body_text_en || ''; - const textDe = template.body_text_de || ''; - console.log(`\nText content length - EN: ${textEn.length}, DE: ${textDe.length}`); - - // Check for specific variables usage - const variables = [ - 'host_name', 'event_name', 'event_date', 'gallery_link', - 'gallery_password', 'expiry_date', 'welcome_message', - 'days_remaining', 'support_email', 'support_phone', - 'archive_date', 'photo_count', 'archive_size' - ]; - - const missingInEn = []; - const missingInDe = []; - - for (const variable of variables) { - const varPattern = new RegExp(`{{${variable}}}`, 'g'); - const inEn = varPattern.test(htmlEn) || varPattern.test(textEn); - const inDe = varPattern.test(htmlDe) || varPattern.test(textDe); - - if (inDe && !inEn) missingInEn.push(variable); - if (inEn && !inDe) missingInDe.push(variable); - } - - if (missingInEn.length > 0) { - console.log(`\n⚠️ Variables in DE but missing in EN: ${missingInEn.join(', ')}`); - } - if (missingInDe.length > 0) { - console.log(`\n⚠️ Variables in EN but missing in DE: ${missingInDe.join(', ')}`); - } - - // Overall quality score - const enScore = [ - htmlEn.includes('style='), - htmlEn.includes('{{#if'), - htmlEn.includes('background-color'), - htmlEn.includes(''), - htmlEn.includes('margin:'), - htmlEn.includes('padding:') - ].filter(Boolean).length; - - const deScore = [ - htmlDe.includes('style='), - htmlDe.includes('{{#if'), - htmlDe.includes('background-color'), - htmlDe.includes(''), - htmlDe.includes('margin:'), - htmlDe.includes('padding:') - ].filter(Boolean).length; - - console.log(`\nQuality score (out of 6) - EN: ${enScore}, DE: ${deScore}`); - console.log(enScore === deScore ? '✅ Templates have equal quality!' : '❌ Quality mismatch'); - } - - console.log('\n\nSummary:'); - console.log('The English templates have been updated to match the German templates in:'); - console.log('- HTML styling and structure'); - console.log('- Conditional content blocks'); - console.log('- Visual elements (buttons, alerts, icons)'); - console.log('- Information completeness'); - console.log('- Professional formatting'); - - } catch (error) { - console.error('Error:', error); - } finally { - await db.destroy(); - } -} - -verifyTemplateEquality(); \ No newline at end of file