diff --git a/.gitea/workflows/mirror-github-archive.yml b/.gitea/workflows/mirror-github-archive.yml deleted file mode 100644 index 73c5bbf..0000000 --- a/.gitea/workflows/mirror-github-archive.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Mirror to GitHub (Archive Method) - -on: - push: - branches: - - main - -jobs: - mirror: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Mirror using git archive - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - # Configure git - git config --global user.name "Gitea Mirror Bot" - git config --global user.email "bot@noreply.gitea.local" - - # Copy gitattributes - cp .gitattributes-github .gitattributes - - # Create archive excluding files - git archive --format=tar HEAD | tar -x -C /tmp/export - - # Initialize new repo in export directory - cd /tmp/export - git init - git add . - git commit -m "Mirror from Gitea: $(date '+%Y-%m-%d %H:%M:%S')" - - # Push to GitHub - git remote add origin https://x-access-token:${GITHUB_TOKEN}@github.com/YOUR_GITHUB_USERNAME/YOUR_REPO_NAME.git - git push -f origin main \ No newline at end of file diff --git a/.gitea/workflows/mirror-github-rsync.yml b/.gitea/workflows/mirror-github-rsync.yml deleted file mode 100644 index d8f0848..0000000 --- a/.gitea/workflows/mirror-github-rsync.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Mirror to GitHub (Rsync Method) - -on: - push: - branches: - - main - -jobs: - mirror: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Prepare mirror directory - run: | - # Create mirror directory - mkdir -p /tmp/github-mirror - - # Use rsync to copy files, excluding sensitive ones - rsync -av --exclude-from='.github-mirror-exclude' ./ /tmp/github-mirror/ - - - name: Push to GitHub - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd /tmp/github-mirror - - # Initialize git repo - git init - git config user.name "Gitea Mirror Bot" - git config user.email "bot@noreply.gitea.local" - - # Add all files and commit - git add . - git commit -m "Mirror from Gitea: $(git --git-dir=$GITHUB_WORKSPACE/.git log -1 --format='%h %s')" - - # Push to GitHub - git remote add origin https://x-access-token:${GITHUB_TOKEN}@github.com/YOUR_GITHUB_USERNAME/YOUR_REPO_NAME.git - git push -f origin main \ No newline at end of file diff --git a/.gitea/workflows/mirror-to-github.yml b/.gitea/workflows/mirror-to-github.yml index ac4ef95..e1b74a4 100644 --- a/.gitea/workflows/mirror-to-github.yml +++ b/.gitea/workflows/mirror-to-github.yml @@ -49,7 +49,7 @@ jobs: - name: Push to GitHub env: - GITHUB_TOKEN: ${{ secrets.GITHUBTOKEN }} + GITHUBTOKEN: ${{ secrets.GITHUBTOKEN }} run: | # Add GitHub remote git remote add github https://x-access-token:${GITHUBTOKEN}@github.com/the-luap/picpeak.git diff --git a/backend/migrations/023_ensure_postgres_compatibility.js b/backend/migrations/023_ensure_postgres_compatibility.js new file mode 100644 index 0000000..40e3037 --- /dev/null +++ b/backend/migrations/023_ensure_postgres_compatibility.js @@ -0,0 +1,22 @@ +/** + * Ensure PostgreSQL compatibility for all insert operations + * This migration doesn't change the schema but ensures all tables + * are compatible with .returning() syntax + */ + +exports.up = async function(knex) { + // This migration is informational only + // All insert operations should use .returning('id') going forward + + console.log('PostgreSQL compatibility check:'); + console.log('- All INSERT operations should use .returning("id")'); + console.log('- All date operations should use ISO strings'); + console.log('- Boolean values are handled automatically by Knex'); + + return Promise.resolve(); +}; + +exports.down = async function(knex) { + // No rollback needed + return Promise.resolve(); +}; \ No newline at end of file diff --git a/backend/scripts/create-test-event.js b/backend/scripts/create-test-event.js index df022eb..292c5ca 100644 --- a/backend/scripts/create-test-event.js +++ b/backend/scripts/create-test-event.js @@ -36,7 +36,8 @@ async function createTestEvent() { await db('events').where('slug', eventData.slug).delete(); // Insert new event - const [eventId] = await db('events').insert(eventData); + const insertResult = await db('events').insert(eventData).returning('id'); + const eventId = insertResult[0]?.id || insertResult[0]; console.log('Event created with ID:', eventId); console.log('\nTest event created successfully!'); diff --git a/backend/src/routes/adminArchives.js b/backend/src/routes/adminArchives.js index 5df863c..bc5505a 100644 --- a/backend/src/routes/adminArchives.js +++ b/backend/src/routes/adminArchives.js @@ -211,12 +211,14 @@ router.post('/:id/restore', adminAuth, async (req, res) => { categoriesMap.set(categoryName, existingCategory.id); } else { // Create the category if it doesn't exist - const [newCategoryId] = await db('photo_categories').insert({ + const insertResult = await db('photo_categories').insert({ event_id: archive.id, name: categoryName, slug: categoryName.toLowerCase().replace(/[^a-z0-9]/g, '-'), created_at: new Date() - }); + }).returning('id'); + + const newCategoryId = insertResult[0]?.id || insertResult[0]; categoriesMap.set(categoryName, newCategoryId); } } @@ -266,6 +268,9 @@ router.post('/:id/restore', adminAuth, async (req, res) => { } // Update event status + const thirtyDaysFromNow = new Date(); + thirtyDaysFromNow.setDate(thirtyDaysFromNow.getDate() + 30); + await db('events') .where('id', req.params.id) .update({ @@ -273,7 +278,7 @@ router.post('/:id/restore', adminAuth, async (req, res) => { is_active: true, archive_path: null, archived_at: null, - expires_at: db.raw("datetime('now', '+30 days')") // Reset expiration + expires_at: thirtyDaysFromNow.toISOString() // Reset expiration - works on both DBs }); // Log activity diff --git a/backend/src/routes/adminEvents-enhanced.js b/backend/src/routes/adminEvents-enhanced.js index 705733e..740dd16 100644 --- a/backend/src/routes/adminEvents-enhanced.js +++ b/backend/src/routes/adminEvents-enhanced.js @@ -83,7 +83,7 @@ router.post('/', adminAuth, [ await fs.mkdir(path.join(eventPath, 'individual'), { recursive: true }); // Insert into database - const [eventId] = await db('events').insert({ + const insertResult = await db('events').insert({ slug, event_type, event_name, @@ -99,7 +99,10 @@ router.post('/', adminAuth, [ created_at: new Date().toISOString(), allow_user_uploads, upload_category_id - }); + }).returning('id'); + + // Handle both PostgreSQL (returns array of objects) and SQLite (returns array of IDs) + const eventId = insertResult[0]?.id || insertResult[0]; // Log activity await logActivity('event_created', diff --git a/backend/src/routes/events.js b/backend/src/routes/events.js index 41b2f8d..7901350 100644 --- a/backend/src/routes/events.js +++ b/backend/src/routes/events.js @@ -64,7 +64,7 @@ router.post('/', adminAuth, [ await fs.mkdir(path.join(eventPath, 'individual'), { recursive: true }); // Insert into database - const [eventId] = await db('events').insert({ + const insertResult = await db('events').insert({ slug, event_type, event_name, @@ -76,7 +76,10 @@ router.post('/', adminAuth, [ color_theme, share_link: shareLink, expires_at - }); + }).returning('id'); + + // Handle both PostgreSQL (returns array of objects) and SQLite (returns array of IDs) + const eventId = insertResult[0]?.id || insertResult[0]; // Queue creation email const { queueEmail } = require('../services/emailProcessor'); diff --git a/backend/src/utils/dbCompat.js b/backend/src/utils/dbCompat.js new file mode 100644 index 0000000..2bf28f8 --- /dev/null +++ b/backend/src/utils/dbCompat.js @@ -0,0 +1,130 @@ +/** + * Database Compatibility Utilities + * Handles differences between PostgreSQL and SQLite + */ + +const { db } = require('../database/db'); + +/** + * Get database client type + * @returns {string} 'pg' or 'sqlite3' + */ +function getDbClient() { + return process.env.DATABASE_CLIENT || 'sqlite3'; +} + +/** + * Check if using PostgreSQL + * @returns {boolean} + */ +function isPostgreSQL() { + return getDbClient() === 'pg'; +} + +/** + * Handle insert operations that return IDs + * Works with both PostgreSQL and SQLite + * @param {object} query - Knex query builder + * @returns {Promise} The inserted ID + */ +async function insertAndGetId(query) { + const result = await query.returning('id'); + + // PostgreSQL returns array of objects [{id: 1}] + // SQLite returns array of IDs [1] + return result[0]?.id || result[0]; +} + +/** + * Format date for database compatibility + * @param {Date} date - JavaScript Date object + * @returns {string} ISO string format that works on both databases + */ +function formatDateForDB(date) { + return date.toISOString(); +} + +/** + * Add days to a date (database agnostic) + * @param {Date} date - Starting date + * @param {number} days - Number of days to add + * @returns {Date} New date + */ +function addDays(date, days) { + const result = new Date(date); + result.setDate(result.getDate() + days); + return result; +} + +/** + * Get date extraction SQL that works on both databases + * @param {string} column - Column name + * @returns {object} Knex raw query + */ +function dateExtractSQL(column) { + if (isPostgreSQL()) { + return db.raw(`DATE(${column})`); + } else { + // SQLite uses date() function + return db.raw(`date(${column})`); + } +} + +/** + * Get database size query + * @param {string} dbName - Database name + * @returns {Promise} Size in bytes + */ +async function getDatabaseSize(dbName) { + if (isPostgreSQL()) { + const result = await db.raw('SELECT pg_database_size(?) as size', [dbName]); + return result.rows[0]?.size || 0; + } else { + // For SQLite, check file size + const fs = require('fs').promises; + const path = require('path'); + const dbPath = process.env.DATABASE_PATH || path.join(__dirname, '../../data/photo_sharing.db'); + try { + const stats = await fs.stat(dbPath); + return stats.size; + } catch (error) { + console.error('Error getting SQLite database size:', error); + return 0; + } + } +} + +/** + * Handle boolean values for database compatibility + * @param {boolean} value - Boolean value + * @returns {any} Database-appropriate boolean representation + */ +function formatBoolean(value) { + if (isPostgreSQL()) { + return value; + } else { + // SQLite stores booleans as 0/1 + return value ? 1 : 0; + } +} + +/** + * Parse boolean from database + * @param {any} value - Database boolean value + * @returns {boolean} JavaScript boolean + */ +function parseBoolean(value) { + return Boolean(value); +} + +module.exports = { + getDbClient, + isPostgreSQL, + insertAndGetId, + formatDateForDB, + addDays, + dateExtractSQL, + getDatabaseSize, + formatBoolean, + parseBoolean +}; \ No newline at end of file