fix: resolve PostgreSQL migration issues for development environment
Mirror to GitHub / mirror (push) Successful in 25s
Test and Lint / backend-test (push) Successful in 1m29s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m0s
Version and Release / version-bump (push) Successful in 40s
Version and Release / trigger-drone (push) Successful in 3s
Mirror to GitHub / mirror (push) Successful in 25s
Test and Lint / backend-test (push) Successful in 1m29s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m0s
Version and Release / version-bump (push) Successful in 40s
Version and Release / trigger-drone (push) Successful in 3s
- Added DATABASE_CLIENT=pg to docker-compose.dev.yml for PostgreSQL connection - Fixed migration 032 to check if tables exist before creating - Removed language-specific email template columns (use standard columns) - Added conditional checks for app_settings and email_templates inserts - Created helper scripts for migration state management - Added .env.dev with PostgreSQL configuration for development 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
# Development Environment with PostgreSQL
|
||||||
|
# Copy this to .env for PostgreSQL development with Docker Compose
|
||||||
|
|
||||||
|
# JWT Secret (development only)
|
||||||
|
JWT_SECRET=dev-secret-key-do-not-use-in-production
|
||||||
|
|
||||||
|
# Database Configuration (PostgreSQL)
|
||||||
|
DATABASE_CLIENT=pg
|
||||||
|
DB_USER=picpeak_dev
|
||||||
|
DB_PASSWORD=dev_password_123
|
||||||
|
DB_NAME=picpeak_dev
|
||||||
|
|
||||||
|
# Redis Configuration
|
||||||
|
REDIS_PASSWORD=dev_redis_pass
|
||||||
|
|
||||||
|
# Admin Account (initial setup)
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_EMAIL=admin@localhost
|
||||||
|
|
||||||
|
# Email Configuration (Mailhog for development)
|
||||||
|
SMTP_HOST=mailhog
|
||||||
|
SMTP_PORT=1025
|
||||||
|
SMTP_SECURE=false
|
||||||
|
SMTP_USER=
|
||||||
|
SMTP_PASS=
|
||||||
|
EMAIL_FROM=noreply@picpeak.local
|
||||||
|
|
||||||
|
# Application URLs
|
||||||
|
FRONTEND_URL=http://localhost:3000
|
||||||
|
ADMIN_URL=http://localhost:3001
|
||||||
|
VITE_API_URL=http://localhost:3001/api
|
||||||
|
|
||||||
|
# Timezone
|
||||||
|
TZ=UTC
|
||||||
|
|
||||||
|
# Analytics (Optional - leave empty for development)
|
||||||
|
VITE_UMAMI_URL=
|
||||||
|
VITE_UMAMI_WEBSITE_ID=
|
||||||
|
VITE_UMAMI_SHARE_URL=
|
||||||
@@ -5,7 +5,9 @@
|
|||||||
*/
|
*/
|
||||||
exports.up = async function(knex) {
|
exports.up = async function(knex) {
|
||||||
// Create restore_runs table
|
// Create restore_runs table
|
||||||
await knex.schema.createTable('restore_runs', table => {
|
const hasRestoreRunsTable = await knex.schema.hasTable('restore_runs');
|
||||||
|
if (!hasRestoreRunsTable) {
|
||||||
|
await knex.schema.createTable('restore_runs', table => {
|
||||||
table.increments('id').primary();
|
table.increments('id').primary();
|
||||||
|
|
||||||
// Timing
|
// Timing
|
||||||
@@ -44,10 +46,13 @@ exports.up = async function(knex) {
|
|||||||
|
|
||||||
table.index(['status', 'started_at']);
|
table.index(['status', 'started_at']);
|
||||||
table.index(['restore_type', 'started_at']);
|
table.index(['restore_type', 'started_at']);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Create restore_file_operations table for tracking individual file operations
|
// Create restore_file_operations table for tracking individual file operations
|
||||||
await knex.schema.createTable('restore_file_operations', table => {
|
const hasRestoreFileOperationsTable = await knex.schema.hasTable('restore_file_operations');
|
||||||
|
if (!hasRestoreFileOperationsTable) {
|
||||||
|
await knex.schema.createTable('restore_file_operations', table => {
|
||||||
table.increments('id').primary();
|
table.increments('id').primary();
|
||||||
|
|
||||||
table.integer('restore_run_id').notNullable()
|
table.integer('restore_run_id').notNullable()
|
||||||
@@ -67,10 +72,13 @@ exports.up = async function(knex) {
|
|||||||
|
|
||||||
table.index(['restore_run_id', 'status']);
|
table.index(['restore_run_id', 'status']);
|
||||||
table.index(['file_path']);
|
table.index(['file_path']);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Create restore_validation_results table
|
// Create restore_validation_results table
|
||||||
await knex.schema.createTable('restore_validation_results', table => {
|
const hasRestoreValidationResultsTable = await knex.schema.hasTable('restore_validation_results');
|
||||||
|
if (!hasRestoreValidationResultsTable) {
|
||||||
|
await knex.schema.createTable('restore_validation_results', table => {
|
||||||
table.increments('id').primary();
|
table.increments('id').primary();
|
||||||
|
|
||||||
table.integer('restore_run_id').notNullable()
|
table.integer('restore_run_id').notNullable()
|
||||||
@@ -86,10 +94,11 @@ exports.up = async function(knex) {
|
|||||||
table.timestamp('validated_at').notNullable().defaultTo(knex.fn.now());
|
table.timestamp('validated_at').notNullable().defaultTo(knex.fn.now());
|
||||||
|
|
||||||
table.index(['restore_run_id', 'validation_type']);
|
table.index(['restore_run_id', 'validation_type']);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Add restore-related settings to app_settings
|
// Add restore-related settings to app_settings
|
||||||
await knex('app_settings').insert([
|
const restoreSettings = [
|
||||||
{
|
{
|
||||||
setting_key: 'restore_allow_force',
|
setting_key: 'restore_allow_force',
|
||||||
setting_value: JSON.stringify(false),
|
setting_value: JSON.stringify(false),
|
||||||
@@ -120,15 +129,24 @@ exports.up = async function(knex) {
|
|||||||
setting_value: '30',
|
setting_value: '30',
|
||||||
setting_type: 'restore'
|
setting_type: 'restore'
|
||||||
}
|
}
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
for (const setting of restoreSettings) {
|
||||||
|
const exists = await knex('app_settings')
|
||||||
|
.where('setting_key', setting.setting_key)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
await knex('app_settings').insert(setting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add new email templates for restore notifications
|
// Add new email templates for restore notifications
|
||||||
const emailTemplates = [
|
const emailTemplates = [
|
||||||
{
|
{
|
||||||
template_key: 'restore_completed',
|
template_key: 'restore_completed',
|
||||||
subject_en: '✅ Restore Completed Successfully',
|
subject: '✅ Restore Completed Successfully',
|
||||||
subject_de: '✅ Wiederherstellung erfolgreich abgeschlossen',
|
body_html: `<h2>Restore Operation Completed</h2>
|
||||||
body_html_en: `<h2>Restore Operation Completed</h2>
|
|
||||||
<p>A restore operation has completed successfully.</p>
|
<p>A restore operation has completed successfully.</p>
|
||||||
|
|
||||||
<h3>Details:</h3>
|
<h3>Details:</h3>
|
||||||
@@ -141,20 +159,7 @@ exports.up = async function(knex) {
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>Please verify that all systems are functioning correctly after the restore.</p>`,
|
<p>Please verify that all systems are functioning correctly after the restore.</p>`,
|
||||||
body_html_de: `<h2>Wiederherstellungsvorgang abgeschlossen</h2>
|
body_text: `Restore Operation Completed
|
||||||
<p>Ein Wiederherstellungsvorgang wurde erfolgreich abgeschlossen.</p>
|
|
||||||
|
|
||||||
<h3>Details:</h3>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Wiederherstellungstyp:</strong> {{restore_type}}</li>
|
|
||||||
<li><strong>Dauer:</strong> {{duration}}</li>
|
|
||||||
<li><strong>Wiederhergestellte Dateien:</strong> {{files_restored}}</li>
|
|
||||||
<li><strong>Backup-ID:</strong> {{backup_id}}</li>
|
|
||||||
<li><strong>Zeitstempel:</strong> {{timestamp}}</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Bitte überprüfen Sie, ob alle Systeme nach der Wiederherstellung ordnungsgemäß funktionieren.</p>`,
|
|
||||||
body_text_en: `Restore Operation Completed
|
|
||||||
|
|
||||||
A restore operation has completed successfully.
|
A restore operation has completed successfully.
|
||||||
|
|
||||||
@@ -166,25 +171,12 @@ Details:
|
|||||||
- Timestamp: {{timestamp}}
|
- Timestamp: {{timestamp}}
|
||||||
|
|
||||||
Please verify that all systems are functioning correctly after the restore.`,
|
Please verify that all systems are functioning correctly after the restore.`,
|
||||||
body_text_de: `Wiederherstellungsvorgang abgeschlossen
|
|
||||||
|
|
||||||
Ein Wiederherstellungsvorgang wurde erfolgreich abgeschlossen.
|
|
||||||
|
|
||||||
Details:
|
|
||||||
- Wiederherstellungstyp: {{restore_type}}
|
|
||||||
- Dauer: {{duration}}
|
|
||||||
- Wiederhergestellte Dateien: {{files_restored}}
|
|
||||||
- Backup-ID: {{backup_id}}
|
|
||||||
- Zeitstempel: {{timestamp}}
|
|
||||||
|
|
||||||
Bitte überprüfen Sie, ob alle Systeme nach der Wiederherstellung ordnungsgemäß funktionieren.`,
|
|
||||||
variables: JSON.stringify(['restore_type', 'duration', 'files_restored', 'backup_id', 'timestamp'])
|
variables: JSON.stringify(['restore_type', 'duration', 'files_restored', 'backup_id', 'timestamp'])
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
template_key: 'restore_failed',
|
template_key: 'restore_failed',
|
||||||
subject_en: '❌ Restore Operation Failed',
|
subject: '❌ Restore Operation Failed',
|
||||||
subject_de: '❌ Wiederherstellungsvorgang fehlgeschlagen',
|
body_html: `<h2>Restore Operation Failed</h2>
|
||||||
body_html_en: `<h2>Restore Operation Failed</h2>
|
|
||||||
<p>A restore operation has failed and requires attention.</p>
|
<p>A restore operation has failed and requires attention.</p>
|
||||||
|
|
||||||
<h3>Details:</h3>
|
<h3>Details:</h3>
|
||||||
@@ -197,20 +189,7 @@ Bitte überprüfen Sie, ob alle Systeme nach der Wiederherstellung ordnungsgemä
|
|||||||
<p>Please check the system logs for more details and take appropriate action.</p>
|
<p>Please check the system logs for more details and take appropriate action.</p>
|
||||||
|
|
||||||
<p><strong>Important:</strong> If a pre-restore backup was created, it may be used for recovery.</p>`,
|
<p><strong>Important:</strong> If a pre-restore backup was created, it may be used for recovery.</p>`,
|
||||||
body_html_de: `<h2>Wiederherstellungsvorgang fehlgeschlagen</h2>
|
body_text: `Restore Operation Failed
|
||||||
<p>Ein Wiederherstellungsvorgang ist fehlgeschlagen und erfordert Ihre Aufmerksamkeit.</p>
|
|
||||||
|
|
||||||
<h3>Details:</h3>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Wiederherstellungstyp:</strong> {{restore_type}}</li>
|
|
||||||
<li><strong>Fehler:</strong> {{error_message}}</li>
|
|
||||||
<li><strong>Zeitstempel:</strong> {{timestamp}}</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Bitte überprüfen Sie die Systemprotokolle für weitere Details und ergreifen Sie entsprechende Maßnahmen.</p>
|
|
||||||
|
|
||||||
<p><strong>Wichtig:</strong> Falls ein Backup vor der Wiederherstellung erstellt wurde, kann es zur Wiederherstellung verwendet werden.</p>`,
|
|
||||||
body_text_en: `Restore Operation Failed
|
|
||||||
|
|
||||||
A restore operation has failed and requires attention.
|
A restore operation has failed and requires attention.
|
||||||
|
|
||||||
@@ -222,23 +201,19 @@ Details:
|
|||||||
Please check the system logs for more details and take appropriate action.
|
Please check the system logs for more details and take appropriate action.
|
||||||
|
|
||||||
Important: If a pre-restore backup was created, it may be used for recovery.`,
|
Important: If a pre-restore backup was created, it may be used for recovery.`,
|
||||||
body_text_de: `Wiederherstellungsvorgang fehlgeschlagen
|
|
||||||
|
|
||||||
Ein Wiederherstellungsvorgang ist fehlgeschlagen und erfordert Ihre Aufmerksamkeit.
|
|
||||||
|
|
||||||
Details:
|
|
||||||
- Wiederherstellungstyp: {{restore_type}}
|
|
||||||
- Fehler: {{error_message}}
|
|
||||||
- Zeitstempel: {{timestamp}}
|
|
||||||
|
|
||||||
Bitte überprüfen Sie die Systemprotokolle für weitere Details und ergreifen Sie entsprechende Maßnahmen.
|
|
||||||
|
|
||||||
Wichtig: Falls ein Backup vor der Wiederherstellung erstellt wurde, kann es zur Wiederherstellung verwendet werden.`,
|
|
||||||
variables: JSON.stringify(['restore_type', 'error_message', 'timestamp'])
|
variables: JSON.stringify(['restore_type', 'error_message', 'timestamp'])
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
await knex('email_templates').insert(emailTemplates);
|
for (const template of emailTemplates) {
|
||||||
|
const exists = await knex('email_templates')
|
||||||
|
.where('template_key', template.template_key)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
await knex('email_templates').insert(template);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.down = async function(knex) {
|
exports.down = async function(knex) {
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix migration state by marking migrations as applied if their tables already exist
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { db } = require('../src/database/db');
|
||||||
|
|
||||||
|
async function fixMigrationState() {
|
||||||
|
try {
|
||||||
|
console.log('Checking migration state...');
|
||||||
|
|
||||||
|
// Ensure migrations table exists
|
||||||
|
const hasMigrationsTable = await db.schema.hasTable('migrations');
|
||||||
|
if (!hasMigrationsTable) {
|
||||||
|
await db.schema.createTable('migrations', (table) => {
|
||||||
|
table.increments('id').primary();
|
||||||
|
table.string('filename').unique().notNullable();
|
||||||
|
table.timestamp('applied_at').defaultTo(db.fn.now());
|
||||||
|
});
|
||||||
|
console.log('Created migrations tracking table');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for specific tables and mark their migrations as applied
|
||||||
|
const tableChecks = [
|
||||||
|
{ table: 'restore_runs', migration: '032_add_restore_runs_table.js' },
|
||||||
|
{ table: 'restore_file_operations', migration: '032_add_restore_runs_table.js' },
|
||||||
|
{ table: 'restore_validation_results', migration: '032_add_restore_runs_table.js' },
|
||||||
|
{ table: 'gallery_feedback', migration: '033_add_gallery_feedback.js' },
|
||||||
|
{ table: 'feedback_photos', migration: '033_add_gallery_feedback.js' },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const check of tableChecks) {
|
||||||
|
const tableExists = await db.schema.hasTable(check.table);
|
||||||
|
if (tableExists) {
|
||||||
|
const migrationApplied = await db('migrations')
|
||||||
|
.where('filename', check.migration)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (!migrationApplied) {
|
||||||
|
await db('migrations').insert({
|
||||||
|
filename: check.migration,
|
||||||
|
applied_at: new Date()
|
||||||
|
});
|
||||||
|
console.log(`✅ Marked ${check.migration} as applied (table ${check.table} exists)`);
|
||||||
|
} else {
|
||||||
|
console.log(`ℹ️ ${check.migration} already marked as applied`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\nMigration state fixed successfully!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fixing migration state:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
} finally {
|
||||||
|
await db.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fixMigrationState();
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark a specific migration as applied without running it
|
||||||
|
* Usage: node scripts/mark-migration-applied.js <migration-filename>
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { db } = require('../src/database/db');
|
||||||
|
|
||||||
|
async function markMigrationAsApplied(filename) {
|
||||||
|
try {
|
||||||
|
// Check if migration is already marked
|
||||||
|
const existing = await db('migrations')
|
||||||
|
.where('filename', filename)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
console.log(`Migration ${filename} is already marked as applied`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as applied
|
||||||
|
await db('migrations').insert({
|
||||||
|
filename,
|
||||||
|
applied_at: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Migration ${filename} marked as applied`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error marking migration:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
} finally {
|
||||||
|
await db.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get migration filename from command line
|
||||||
|
const migrationFile = process.argv[2];
|
||||||
|
|
||||||
|
if (!migrationFile) {
|
||||||
|
console.error('Usage: node scripts/mark-migration-applied.js <migration-filename>');
|
||||||
|
console.error('Example: node scripts/mark-migration-applied.js 032_add_restore_runs_table.js');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
markMigrationAsApplied(migrationFile);
|
||||||
@@ -13,6 +13,7 @@ services:
|
|||||||
- JWT_SECRET=${JWT_SECRET}
|
- JWT_SECRET=${JWT_SECRET}
|
||||||
- ADMIN_USERNAME=${ADMIN_USERNAME:-admin}
|
- ADMIN_USERNAME=${ADMIN_USERNAME:-admin}
|
||||||
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example.com}
|
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example.com}
|
||||||
|
- DATABASE_CLIENT=pg
|
||||||
- DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
|
- DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
|
||||||
- DB_TYPE=postgresql
|
- DB_TYPE=postgresql
|
||||||
- DB_HOST=postgres
|
- DB_HOST=postgres
|
||||||
|
|||||||
Reference in New Issue
Block a user