fix: handle email templates schema variations in production
Test and Lint / backend-test (push) Successful in 1m11s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m20s
Version and Release / version-bump (push) Successful in 34s
Version and Release / trigger-drone (push) Successful in 3s

- Update migration to detect and handle both old and new email template schemas
- Fix migration to insert into correct columns based on existing schema
- Update adminEmail routes to handle both schema formats gracefully
- Add proper fallbacks for German language columns

This ensures the application works whether the language migration has been
applied or not, preventing null constraint violations.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-14 09:56:32 +02:00
parent f22e3c133f
commit 0b0e3e22d2
2 changed files with 92 additions and 29 deletions
@@ -3,11 +3,20 @@ exports.up = async function(knex) {
const templates = await knex('email_templates').select('template_key');
const existingKeys = templates.map(t => t.template_key);
// Check which columns exist in the table
const hasSubjectEn = await knex.schema.hasColumn('email_templates', 'subject_en');
const hasSubject = await knex.schema.hasColumn('email_templates', 'subject');
// Determine which columns to use based on schema
const subjectCol = hasSubjectEn ? 'subject_en' : 'subject';
const bodyHtmlCol = hasSubjectEn ? 'body_html_en' : 'body_html';
const bodyTextCol = hasSubjectEn ? 'body_text_en' : 'body_text';
const defaultTemplates = [
{
template_key: 'gallery_created',
subject: 'Your Photo Gallery is Ready!',
body_html: `<h2>Gallery Created Successfully</h2>
[subjectCol]: 'Your Photo Gallery is Ready!',
[bodyHtmlCol]: `<h2>Gallery Created Successfully</h2>
<p>Dear {{host_name}},</p>
<p>Your photo gallery "{{event_name}}" has been created successfully!</p>
<p><strong>Gallery Details:</strong></p>
@@ -18,39 +27,39 @@ exports.up = async function(knex) {
<li>Expires: {{expiry_date}}</li>
</ul>
<p>Share this link and password with your guests to allow them to view and download photos.</p>`,
body_text: 'Gallery Created Successfully\n\nDear {{host_name}},\n\nYour photo gallery "{{event_name}}" has been created successfully!',
[bodyTextCol]: 'Gallery Created Successfully\n\nDear {{host_name}},\n\nYour photo gallery "{{event_name}}" has been 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: `<h2>Gallery Expiring Soon</h2>
[subjectCol]: 'Your Photo Gallery Expires Soon',
[bodyHtmlCol]: `<h2>Gallery Expiring Soon</h2>
<p>Dear {{host_name}},</p>
<p>Your photo gallery "{{event_name}}" will expire in {{days_remaining}} days.</p>
<p>After expiration, the gallery will be archived and no longer accessible to guests.</p>
<p><a href="{{gallery_link}}">Visit Gallery</a></p>`,
body_text: 'Gallery Expiring Soon\n\nDear {{host_name}},\n\nYour photo gallery "{{event_name}}" will expire in {{days_remaining}} days.',
[bodyTextCol]: 'Gallery Expiring Soon\n\nDear {{host_name}},\n\nYour photo gallery "{{event_name}}" will expire in {{days_remaining}} days.',
variables: JSON.stringify(['host_name', 'event_name', 'days_remaining', 'gallery_link'])
},
{
template_key: 'gallery_expired',
subject: 'Your Photo Gallery Has Expired',
body_html: `<h2>Gallery Expired</h2>
[subjectCol]: 'Your Photo Gallery Has Expired',
[bodyHtmlCol]: `<h2>Gallery Expired</h2>
<p>Dear {{host_name}},</p>
<p>Your photo gallery "{{event_name}}" has expired and been archived.</p>
<p>The photos are safely stored in our archive system. If you need access to the archived photos, please contact support.</p>`,
body_text: 'Gallery Expired\n\nDear {{host_name}},\n\nYour photo gallery "{{event_name}}" has expired and been archived.',
[bodyTextCol]: 'Gallery Expired\n\nDear {{host_name}},\n\nYour photo gallery "{{event_name}}" has expired and been archived.',
variables: JSON.stringify(['host_name', 'event_name'])
},
{
template_key: 'archive_complete',
subject: 'Gallery Archive Complete',
body_html: `<h2>Archive Complete</h2>
[subjectCol]: 'Gallery Archive Complete',
[bodyHtmlCol]: `<h2>Archive Complete</h2>
<p>Dear {{host_name}},</p>
<p>Your photo gallery "{{event_name}}" has been successfully archived.</p>
<p>Archive size: {{archive_size}}</p>
<p>The archive is stored securely and can be retrieved if needed.</p>`,
body_text: 'Archive Complete\n\nDear {{host_name}},\n\nYour photo gallery "{{event_name}}" has been successfully archived.',
[bodyTextCol]: 'Archive Complete\n\nDear {{host_name}},\n\nYour photo gallery "{{event_name}}" has been successfully archived.',
variables: JSON.stringify(['host_name', 'event_name', 'archive_size'])
}
];
@@ -58,6 +67,20 @@ exports.up = async function(knex) {
// Insert missing templates
for (const template of defaultTemplates) {
if (!existingKeys.includes(template.template_key)) {
// If we have language columns, also set German versions with same content
if (hasSubjectEn) {
template.subject_de = template[subjectCol];
template.body_html_de = template[bodyHtmlCol];
template.body_text_de = template[bodyTextCol];
// Also ensure we have the basic columns if they exist
if (hasSubject) {
template.subject = template[subjectCol];
template.body_html = template[bodyHtmlCol];
template.body_text = template[bodyTextCol];
}
}
await knex('email_templates').insert(template);
}
}
+57 -17
View File
@@ -193,20 +193,34 @@ router.get('/templates/:key', adminAuth, async (req, res) => {
return res.status(404).json({ error: 'Template not found' });
}
res.json({
// Handle both old and new schema formats
const response = {
id: template.id,
template_key: template.template_key,
// English versions
subject_en: template.subject_en || template.subject,
body_html_en: template.body_html_en || template.body_html,
body_text_en: template.body_text_en || template.body_text,
// German versions
subject_de: template.subject_de || template.subject_en || template.subject,
body_html_de: template.body_html_de || template.body_html_en || template.body_html,
body_text_de: template.body_text_de || template.body_text_en || template.body_text,
variables: template.variables ? JSON.parse(template.variables) : [],
updated_at: template.updated_at
});
};
// Check which columns exist and use them appropriately
if (template.subject_en !== undefined) {
// New schema with language columns
response.subject_en = template.subject_en;
response.body_html_en = template.body_html_en;
response.body_text_en = template.body_text_en;
response.subject_de = template.subject_de;
response.body_html_de = template.body_html_de;
response.body_text_de = template.body_text_de;
} else {
// Old schema - use basic columns for both languages
response.subject_en = template.subject;
response.body_html_en = template.body_html;
response.body_text_en = template.body_text;
response.subject_de = template.subject;
response.body_html_de = template.body_html;
response.body_text_de = template.body_text;
}
res.json(response);
} catch (error) {
console.error('Email template fetch error:', error);
res.status(500).json({ error: 'Failed to fetch email template' });
@@ -237,13 +251,39 @@ router.put('/templates/:key', [
updated_at: new Date()
};
// Only update provided fields
if (subject_en !== undefined) updateData.subject_en = subject_en;
if (subject_de !== undefined) updateData.subject_de = subject_de;
if (body_html_en !== undefined) updateData.body_html_en = body_html_en;
if (body_html_de !== undefined) updateData.body_html_de = body_html_de;
if (body_text_en !== undefined) updateData.body_text_en = body_text_en || '';
if (body_text_de !== undefined) updateData.body_text_de = body_text_de || '';
// Check which columns exist in the database
const template = await db('email_templates')
.where('template_key', req.params.key)
.first();
if (!template) {
return res.status(404).json({ error: 'Template not found' });
}
// Determine schema type and update accordingly
if (template.subject_en !== undefined) {
// New schema with language columns
if (subject_en !== undefined) updateData.subject_en = subject_en;
if (subject_de !== undefined) updateData.subject_de = subject_de;
if (body_html_en !== undefined) updateData.body_html_en = body_html_en;
if (body_html_de !== undefined) updateData.body_html_de = body_html_de;
if (body_text_en !== undefined) updateData.body_text_en = body_text_en || '';
if (body_text_de !== undefined) updateData.body_text_de = body_text_de || '';
// Also update basic columns if they exist
if (template.subject !== undefined) {
updateData.subject = subject_en || updateData.subject_en;
updateData.body_html = body_html_en || updateData.body_html_en;
updateData.body_text = body_text_en || updateData.body_text_en || '';
}
} else {
// Old schema - only update basic columns
if (subject_en !== undefined) {
updateData.subject = subject_en;
updateData.body_html = body_html_en;
updateData.body_text = body_text_en || '';
}
}
const updated = await db('email_templates')
.where('template_key', req.params.key)