From 097ce2c2059aac821f5713e2451d67557ec00d27 Mon Sep 17 00:00:00 2001 From: paul Date: Thu, 18 Sep 2025 12:40:52 +0200 Subject: [PATCH] Fix native install schema gaps (closes #20) --- README.md | 1 + SIMPLE_SETUP.md | 1 + .../core/042_backfill_event_upload_columns.js | 51 +++++++++++++++++++ backend/src/database/db.js | 40 ++++++++++++++- 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 backend/migrations/core/042_backfill_event_upload_columns.js diff --git a/README.md b/README.md index 43ca288..5237753 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,7 @@ These features are currently in beta testing and may have limited functionality | **Gallery Feedback** | Allow guests to like, rate, and comment on photos with admin notifications and moderation | Medium | ✅ Implemented | | **Video Support** | Upload and display videos alongside photos in galleries with streaming support | Low | 🔄 Open | | **Multiple Administrators** | Support for multiple admin accounts with role-based permissions and activity tracking | Low | 📋 Planned | +| **Filtering & Export Options** | Add filters to show only rated, liked, or marked photos and export filtered selections for Capture One or Lightroom workflows | Low | 🔄 Open | **Status Legend:** ✅ Implemented | 🚧 In Progress | 🔄 Open | 📋 Planned diff --git a/SIMPLE_SETUP.md b/SIMPLE_SETUP.md index 8802ed4..7ea7df4 100644 --- a/SIMPLE_SETUP.md +++ b/SIMPLE_SETUP.md @@ -292,6 +292,7 @@ sudo systemctl stop picpeak-backend picpeak-workers sudo systemctl restart picpeak-backend picpeak-workers # Update PicPeak +# (reruns migrations to pick up schema fixes for native installs) sudo ./setup.sh --update ``` diff --git a/backend/migrations/core/042_backfill_event_upload_columns.js b/backend/migrations/core/042_backfill_event_upload_columns.js new file mode 100644 index 0000000..47b3375 --- /dev/null +++ b/backend/migrations/core/042_backfill_event_upload_columns.js @@ -0,0 +1,51 @@ +const logger = require('../../src/utils/logger'); + +async function ensureColumn(knex, tableName, columnName, alterFn) { + const exists = await knex.schema.hasColumn(tableName, columnName); + if (!exists) { + logger.info(`Adding column ${tableName}.${columnName}`); + await knex.schema.table(tableName, alterFn); + } +} + +exports.up = async function(knex) { + await ensureColumn(knex, 'events', 'host_name', (table) => { + table.string('host_name'); + }); + + await ensureColumn(knex, 'events', 'allow_user_uploads', (table) => { + table.boolean('allow_user_uploads').defaultTo(false); + }); + + await ensureColumn(knex, 'events', 'upload_category_id', (table) => { + table.integer('upload_category_id'); + }); + + await ensureColumn(knex, 'events', 'allow_downloads', (table) => { + table.boolean('allow_downloads').defaultTo(true); + }); + + await ensureColumn(knex, 'events', 'disable_right_click', (table) => { + table.boolean('disable_right_click').defaultTo(false); + }); + + await ensureColumn(knex, 'events', 'watermark_downloads', (table) => { + table.boolean('watermark_downloads').defaultTo(false); + }); + + await ensureColumn(knex, 'events', 'watermark_text', (table) => { + table.text('watermark_text'); + }); + + await ensureColumn(knex, 'events', 'hero_photo_id', (table) => { + table.integer('hero_photo_id').references('id').inTable('photos').onDelete('SET NULL'); + }); + + await ensureColumn(knex, 'photos', 'uploaded_by', (table) => { + table.string('uploaded_by').defaultTo('admin'); + }); +}; + +exports.down = async function() { + // Non destructive migration; no rollback +}; diff --git a/backend/src/database/db.js b/backend/src/database/db.js index 324712d..67d3612 100644 --- a/backend/src/database/db.js +++ b/backend/src/database/db.js @@ -75,6 +75,13 @@ async function initializeDatabase() { table.boolean('is_archived').defaultTo(false); table.string('archive_path'); table.datetime('archived_at'); + table.boolean('allow_user_uploads').defaultTo(false); + table.integer('upload_category_id'); + table.boolean('allow_downloads').defaultTo(true); + table.boolean('disable_right_click').defaultTo(false); + table.boolean('watermark_downloads').defaultTo(false); + table.text('watermark_text'); + table.integer('hero_photo_id').references('id').inTable('photos').onDelete('SET NULL'); }); } else { // Check if color_theme needs to be updated to TEXT type @@ -104,11 +111,39 @@ async function initializeDatabase() { archive_path TEXT, archived_at DATETIME, allow_user_uploads BOOLEAN DEFAULT 0, - upload_category_id INTEGER + upload_category_id INTEGER, + allow_downloads BOOLEAN DEFAULT 1, + disable_right_click BOOLEAN DEFAULT 0, + watermark_downloads BOOLEAN DEFAULT 0, + watermark_text TEXT, + hero_photo_id INTEGER ) `); - await db.raw('INSERT INTO events_new SELECT * FROM events'); + const pragmaRows = await db.raw("PRAGMA table_info('events')"); + const existingColumns = pragmaRows.map(row => row.name); + const selectColumns = existingColumns.map((col) => { + switch (col) { + case 'allow_user_uploads': + return "COALESCE(allow_user_uploads, 0) as allow_user_uploads"; + case 'upload_category_id': + return "upload_category_id"; + case 'allow_downloads': + return "COALESCE(allow_downloads, 1) as allow_downloads"; + case 'disable_right_click': + return "COALESCE(disable_right_click, 0) as disable_right_click"; + case 'watermark_downloads': + return "COALESCE(watermark_downloads, 0) as watermark_downloads"; + case 'watermark_text': + return 'watermark_text'; + case 'hero_photo_id': + return 'hero_photo_id'; + default: + return col; + } + }); + + await db.raw(`INSERT INTO events_new (${existingColumns.join(', ')}) SELECT ${selectColumns.join(', ')} FROM events`); await db.raw('DROP TABLE events'); await db.raw('ALTER TABLE events_new RENAME TO events'); } catch (error) { @@ -129,6 +164,7 @@ async function initializeDatabase() { table.string('thumbnail_path'); table.string('type').notNullable(); // 'collage' or 'individual' table.integer('size_bytes'); + table.string('uploaded_by').defaultTo('admin'); table.datetime('uploaded_at').defaultTo(db.fn.now()); table.integer('view_count').defaultTo(0); table.integer('download_count').defaultTo(0);