diff --git a/backend/migrations/022_fix_json_columns.js b/backend/migrations/022_fix_json_columns.js new file mode 100644 index 0000000..752b02c --- /dev/null +++ b/backend/migrations/022_fix_json_columns.js @@ -0,0 +1,68 @@ +exports.up = async function(knex) { + console.log('Fixing JSON columns in database...'); + + // Fix email_templates variables column + const templates = await knex('email_templates').select('id', 'template_key', 'variables'); + + for (const template of templates) { + if (template.variables && typeof template.variables === 'string') { + try { + // Check if it's already valid JSON + JSON.parse(template.variables); + } catch (e) { + console.log(`Fixing invalid JSON in email template ${template.template_key}`); + // Attempt to fix common issues + let fixed = template.variables; + + // If it looks like an array but isn't valid JSON, try to fix it + if (fixed.startsWith('[') && fixed.endsWith(']')) { + // Extract the content and properly format it + const content = fixed.slice(1, -1); + const items = content.split(',').map(item => item.trim().replace(/['"]/g, '')); + fixed = JSON.stringify(items); + } else { + // Default to empty array if we can't fix it + fixed = JSON.stringify([]); + } + + await knex('email_templates') + .where('id', template.id) + .update({ variables: fixed }); + } + } else if (!template.variables) { + // Set default empty array for null values + await knex('email_templates') + .where('id', template.id) + .update({ variables: JSON.stringify([]) }); + } + } + + // Fix activity_logs metadata column + const activities = await knex('activity_logs').select('id', 'metadata'); + + for (const activity of activities) { + if (activity.metadata && typeof activity.metadata === 'string') { + try { + // Check if it's already valid JSON + JSON.parse(activity.metadata); + } catch (e) { + console.log(`Fixing invalid JSON in activity log ${activity.id}`); + // Default to empty object if we can't parse it + await knex('activity_logs') + .where('id', activity.id) + .update({ metadata: JSON.stringify({}) }); + } + } else if (!activity.metadata) { + // Set default empty object for null values + await knex('activity_logs') + .where('id', activity.id) + .update({ metadata: JSON.stringify({}) }); + } + } + + console.log('JSON columns fixed successfully'); +}; + +exports.down = async function(knex) { + // No rollback needed - data fixes only +}; \ No newline at end of file diff --git a/backend/scripts/fix-production-issues.js b/backend/scripts/fix-production-issues.js new file mode 100644 index 0000000..5fc0c13 --- /dev/null +++ b/backend/scripts/fix-production-issues.js @@ -0,0 +1,145 @@ +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: '
Your legal information here...
', + content_de: '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: 'Your privacy policy here...
', + content_de: '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/server.js b/backend/server.js index f6c35c6..a22fe2a 100644 --- a/backend/server.js +++ b/backend/server.js @@ -28,6 +28,9 @@ const adminAuthRoutes = require('./src/routes/adminAuth'); const app = express(); const PORT = process.env.PORT || 3000; +// Trust proxy headers (required for Traefik/nginx) +app.set('trust proxy', true); + // Security middleware with custom CSP app.use(helmet({ contentSecurityPolicy: { diff --git a/backend/src/routes/adminEmail.js b/backend/src/routes/adminEmail.js index 615dade..7fe7ed7 100644 --- a/backend/src/routes/adminEmail.js +++ b/backend/src/routes/adminEmail.js @@ -164,7 +164,16 @@ router.get('/templates', adminAuth, async (req, res) => { const result = { id: template.id, template_key: template.template_key, - variables: template.variables ? JSON.parse(template.variables) : [], + variables: (() => { + try { + if (!template.variables) return []; + if (typeof template.variables === 'object') return template.variables; + return JSON.parse(template.variables); + } catch (e) { + console.warn('Failed to parse variables for template:', template.template_key, e.message); + return []; + } + })(), updated_at: template.updated_at }; @@ -212,7 +221,16 @@ router.get('/templates/:key', adminAuth, async (req, res) => { const response = { id: template.id, template_key: template.template_key, - variables: template.variables ? JSON.parse(template.variables) : [], + variables: (() => { + try { + if (!template.variables) return []; + if (typeof template.variables === 'object') return template.variables; + return JSON.parse(template.variables); + } catch (e) { + console.warn('Failed to parse variables for template:', template.template_key, e.message); + return []; + } + })(), updated_at: template.updated_at }; diff --git a/backend/src/routes/adminNotifications.js b/backend/src/routes/adminNotifications.js index 3732da6..2f3ea2e 100644 --- a/backend/src/routes/adminNotifications.js +++ b/backend/src/routes/adminNotifications.js @@ -32,7 +32,16 @@ router.get('/', adminAuth, async (req, res) => { actorName: notification.actor_name, eventName: notification.event_name, eventId: notification.event_id, - metadata: notification.metadata ? JSON.parse(notification.metadata) : {}, + metadata: (() => { + try { + if (!notification.metadata) return {}; + if (typeof notification.metadata === 'object') return notification.metadata; + return JSON.parse(notification.metadata); + } catch (e) { + console.warn('Failed to parse metadata for notification:', notification.id, e.message); + return {}; + } + })(), createdAt: notification.created_at, readAt: notification.read_at, isRead: !!notification.read_at