From 97589a7c5f0882bc746f78682306432086d42ce9 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Nov 2025 21:59:27 +0000 Subject: [PATCH] Fix issues #49 and #50: Migration errors and missing worker manager - Fix #49: Add column existence checks to migration 011_add_user_upload_settings.js to prevent "column already exists" errors during deployment - Fix #50: Create missing workerManager.js file that starts background services (file watcher and expiration checker) for native installations --- .../legacy/011_add_user_upload_settings.js | 67 ++++++++++++----- backend/src/services/workerManager.js | 72 +++++++++++++++++++ 2 files changed, 122 insertions(+), 17 deletions(-) create mode 100644 backend/src/services/workerManager.js diff --git a/backend/migrations/legacy/011_add_user_upload_settings.js b/backend/migrations/legacy/011_add_user_upload_settings.js index f347cca..a796fa7 100644 --- a/backend/migrations/legacy/011_add_user_upload_settings.js +++ b/backend/migrations/legacy/011_add_user_upload_settings.js @@ -1,23 +1,56 @@ exports.up = async function(knex) { - // Add user upload settings to events table - await knex.schema.alterTable('events', function(table) { - table.boolean('allow_user_uploads').defaultTo(false); - table.integer('upload_category_id').references('id').inTable('photo_categories').onDelete('SET NULL'); - }); - + // Add user upload settings to events table (check if columns exist first) + const hasAllowUserUploads = await knex.schema.hasColumn('events', 'allow_user_uploads'); + if (!hasAllowUserUploads) { + console.log('Adding allow_user_uploads column to events table...'); + await knex.schema.alterTable('events', function(table) { + table.boolean('allow_user_uploads').defaultTo(false); + }); + } else { + console.log('Column allow_user_uploads already exists in events table, skipping...'); + } + + const hasUploadCategoryId = await knex.schema.hasColumn('events', 'upload_category_id'); + if (!hasUploadCategoryId) { + console.log('Adding upload_category_id column to events table...'); + await knex.schema.alterTable('events', function(table) { + table.integer('upload_category_id').references('id').inTable('photo_categories').onDelete('SET NULL'); + }); + } else { + console.log('Column upload_category_id already exists in events table, skipping...'); + } + // Add uploaded_by field to photos table to track who uploaded - await knex.schema.alterTable('photos', function(table) { - table.string('uploaded_by').defaultTo('admin'); // 'admin' or guest identifier - }); + const hasUploadedBy = await knex.schema.hasColumn('photos', 'uploaded_by'); + if (!hasUploadedBy) { + console.log('Adding uploaded_by column to photos table...'); + await knex.schema.alterTable('photos', function(table) { + table.string('uploaded_by').defaultTo('admin'); // 'admin' or guest identifier + }); + } else { + console.log('Column uploaded_by already exists in photos table, skipping...'); + } }; exports.down = async function(knex) { - await knex.schema.alterTable('events', function(table) { - table.dropColumn('allow_user_uploads'); - table.dropColumn('upload_category_id'); - }); - - await knex.schema.alterTable('photos', function(table) { - table.dropColumn('uploaded_by'); - }); + const hasAllowUserUploads = await knex.schema.hasColumn('events', 'allow_user_uploads'); + if (hasAllowUserUploads) { + await knex.schema.alterTable('events', function(table) { + table.dropColumn('allow_user_uploads'); + }); + } + + const hasUploadCategoryId = await knex.schema.hasColumn('events', 'upload_category_id'); + if (hasUploadCategoryId) { + await knex.schema.alterTable('events', function(table) { + table.dropColumn('upload_category_id'); + }); + } + + const hasUploadedBy = await knex.schema.hasColumn('photos', 'uploaded_by'); + if (hasUploadedBy) { + await knex.schema.alterTable('photos', function(table) { + table.dropColumn('uploaded_by'); + }); + } }; \ No newline at end of file diff --git a/backend/src/services/workerManager.js b/backend/src/services/workerManager.js new file mode 100644 index 0000000..3e87e16 --- /dev/null +++ b/backend/src/services/workerManager.js @@ -0,0 +1,72 @@ +/** + * Worker Manager - Background service for PicPeak + * + * This service runs as a separate process to handle: + * - File watching for new photos + * - Expiration checking for events + * - Other background tasks + */ + +const path = require('path'); +const logger = require('../utils/logger'); + +// Load environment variables +require('dotenv').config({ path: path.join(__dirname, '../../.env') }); + +// Import services +const { startFileWatcher } = require('./fileWatcher'); +const { startExpirationChecker } = require('./expirationChecker'); + +let isShuttingDown = false; + +async function startWorkers() { + logger.info('Starting PicPeak background workers...'); + + try { + // Start file watcher for automatic photo processing + startFileWatcher(); + logger.info('File watcher started successfully'); + + // Start expiration checker for event lifecycle management + startExpirationChecker(); + logger.info('Expiration checker started successfully'); + + logger.info('All background workers started successfully'); + } catch (error) { + logger.error('Failed to start background workers:', error); + process.exit(1); + } +} + +function handleShutdown(signal) { + if (isShuttingDown) { + logger.info('Shutdown already in progress...'); + return; + } + + isShuttingDown = true; + logger.info(`Received ${signal}. Shutting down gracefully...`); + + // Give time for cleanup + setTimeout(() => { + logger.info('Worker manager shutdown complete'); + process.exit(0); + }, 1000); +} + +// Handle shutdown signals +process.on('SIGTERM', () => handleShutdown('SIGTERM')); +process.on('SIGINT', () => handleShutdown('SIGINT')); + +// Handle uncaught errors +process.on('uncaughtException', (error) => { + logger.error('Uncaught exception in worker manager:', error); + process.exit(1); +}); + +process.on('unhandledRejection', (reason, promise) => { + logger.error('Unhandled rejection in worker manager:', reason); +}); + +// Start workers +startWorkers();