fix: critical boolean compatibility for PostgreSQL/SQLite

Fixed boolean value handling differences between databases:
- SQLite stores booleans as 0/1, PostgreSQL as true/false
- Add formatBoolean() calls to critical queries that were failing

Files fixed:
- adminEvents.js: Fixed status filters and archive queries
- adminDashboard.js: Fixed active/archived event counts
- expirationChecker.js: Fixed expiration checking queries
- dbCompat.js: Updated to avoid circular dependency

Added migration 024 to:
- Enable foreign keys for SQLite (PRAGMA foreign_keys = ON)
- Document boolean compatibility requirements

This fixes queries returning 0 results in SQLite when checking
boolean columns like is_active, is_archived. Critical for
proper event management and expiration handling.

Note: 23 more boolean comparisons remain to be fixed in other files.
This commit is contained in:
2025-07-14 20:44:58 +02:00
parent 5e43fc9cd9
commit 88919fa0d3
5 changed files with 53 additions and 18 deletions
@@ -0,0 +1,29 @@
/**
* Fix boolean compatibility issues between PostgreSQL and SQLite
* This migration updates the database configuration and existing data
*/
exports.up = async function(knex) {
const isPostgres = knex.client.config.client === 'pg';
if (!isPostgres) {
// Enable foreign keys for SQLite
await knex.raw('PRAGMA foreign_keys = ON');
// Note: SQLite stores booleans as 0/1
// No data migration needed as Knex handles this automatically
// But queries must use formatBoolean() helper
console.log('SQLite boolean compatibility check:');
console.log('- SQLite stores booleans as 0/1');
console.log('- All boolean comparisons should use formatBoolean() helper');
console.log('- Foreign keys enabled');
}
return Promise.resolve();
};
exports.down = async function(knex) {
// No rollback needed
return Promise.resolve();
};
+6 -5
View File
@@ -2,6 +2,7 @@ const express = require('express');
const { db } = require('../database/db'); const { db } = require('../database/db');
const { adminAuth } = require('../middleware/auth-enhanced-v2'); const { adminAuth } = require('../middleware/auth-enhanced-v2');
const { sanitizeDays, addDateRangeCondition } = require('../utils/sqlSecurity'); const { sanitizeDays, addDateRangeCondition } = require('../utils/sqlSecurity');
const { formatBoolean } = require('../utils/dbCompat');
const router = express.Router(); const router = express.Router();
// Get dashboard statistics // Get dashboard statistics
@@ -9,8 +10,8 @@ router.get('/stats', adminAuth, async (req, res) => {
try { try {
// Get active events count // Get active events count
const activeEvents = await db('events') const activeEvents = await db('events')
.where('is_active', true) .where('is_active', formatBoolean(true))
.where('is_archived', false) .where('is_archived', formatBoolean(false))
.count('id as count') .count('id as count')
.first(); .first();
@@ -20,8 +21,8 @@ router.get('/stats', adminAuth, async (req, res) => {
const now = new Date(); const now = new Date();
const expiringEvents = await db('events') const expiringEvents = await db('events')
.where('is_active', true) .where('is_active', formatBoolean(true))
.where('is_archived', false) .where('is_archived', formatBoolean(false))
.where('expires_at', '<=', sevenDaysFromNow.toISOString()) .where('expires_at', '<=', sevenDaysFromNow.toISOString())
.where('expires_at', '>', now.toISOString()) .where('expires_at', '>', now.toISOString())
.count('id as count') .count('id as count')
@@ -56,7 +57,7 @@ router.get('/stats', adminAuth, async (req, res) => {
// Get archived events count // Get archived events count
const archivedEvents = await db('events') const archivedEvents = await db('events')
.where('is_archived', true) .where('is_archived', formatBoolean(true))
.count('id as count') .count('id as count')
.first(); .first();
+7 -6
View File
@@ -11,6 +11,7 @@ const { archiveEvent } = require('../services/archiveService');
const { escapeLikePattern } = require('../utils/sqlSecurity'); const { escapeLikePattern } = require('../utils/sqlSecurity');
const { formatDate } = require('../utils/dateFormatter'); const { formatDate } = require('../utils/dateFormatter');
const { validatePasswordInContext, getBcryptRounds } = require('../utils/passwordValidation'); const { validatePasswordInContext, getBcryptRounds } = require('../utils/passwordValidation');
const { formatBoolean } = require('../utils/dbCompat');
// Create new event // Create new event
router.post('/', adminAuth, [ router.post('/', adminAuth, [
@@ -183,17 +184,17 @@ router.get('/', adminAuth, async (req, res) => {
// Apply status filter // Apply status filter
if (status === 'active') { if (status === 'active') {
query = query.where('is_active', true).where('is_archived', false); query = query.where('is_active', formatBoolean(true)).where('is_archived', formatBoolean(false));
} else if (status === 'archived') { } else if (status === 'archived') {
query = query.where('is_archived', true); query = query.where('is_archived', formatBoolean(true));
} else if (status === 'inactive') { } else if (status === 'inactive') {
query = query.where('is_active', false).where('is_archived', false); query = query.where('is_active', formatBoolean(false)).where('is_archived', formatBoolean(false));
} else if (status === 'expiring') { } else if (status === 'expiring') {
const sevenDaysFromNow = new Date(); const sevenDaysFromNow = new Date();
sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 7); sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 7);
query = query query = query
.where('is_active', true) .where('is_active', formatBoolean(true))
.where('is_archived', false) .where('is_archived', formatBoolean(false))
.where('expires_at', '<=', sevenDaysFromNow.toISOString()) .where('expires_at', '<=', sevenDaysFromNow.toISOString())
.where('expires_at', '>', new Date().toISOString()); .where('expires_at', '>', new Date().toISOString());
} }
@@ -554,7 +555,7 @@ router.post('/bulk-archive', adminAuth, [
// Get all events to archive // Get all events to archive
const events = await db('events') const events = await db('events')
.whereIn('id', eventIds) .whereIn('id', eventIds)
.where('is_archived', false); .where('is_archived', formatBoolean(false));
if (events.length === 0) { if (events.length === 0) {
return res.status(400).json({ error: 'No valid events found to archive' }); return res.status(400).json({ error: 'No valid events found to archive' });
+5 -4
View File
@@ -4,6 +4,7 @@ const { archiveEvent } = require('./archiveService');
const { queueEmail } = require('./emailProcessor'); const { queueEmail } = require('./emailProcessor');
const logger = require('../utils/logger'); const logger = require('../utils/logger');
const { formatDate } = require('../utils/dateFormatter'); const { formatDate } = require('../utils/dateFormatter');
const { formatBoolean } = require('../utils/dbCompat');
function startExpirationChecker() { function startExpirationChecker() {
// Check every hour for expired events and warnings // Check every hour for expired events and warnings
@@ -21,8 +22,8 @@ async function checkExpirations() {
// Check for events needing warning emails // Check for events needing warning emails
const eventsNeedingWarning = await db('events') const eventsNeedingWarning = await db('events')
.where('is_active', true) .where('is_active', formatBoolean(true))
.where('is_archived', false) .where('is_archived', formatBoolean(false))
.where('expires_at', '<=', warningDate) .where('expires_at', '<=', warningDate)
.where('expires_at', '>', now); .where('expires_at', '>', now);
@@ -40,8 +41,8 @@ async function checkExpirations() {
// Check for expired events // Check for expired events
const expiredEvents = await db('events') const expiredEvents = await db('events')
.where('is_active', true) .where('is_active', formatBoolean(true))
.where('is_archived', false) .where('is_archived', formatBoolean(false))
.where('expires_at', '<=', now); .where('expires_at', '<=', now);
for (const event of expiredEvents) { for (const event of expiredEvents) {
+6 -3
View File
@@ -3,7 +3,8 @@
* Handles differences between PostgreSQL and SQLite * Handles differences between PostgreSQL and SQLite
*/ */
const { db } = require('../database/db'); // Note: Requiring db here creates circular dependency
// db should be passed as parameter or required where needed
/** /**
* Get database client type * Get database client type
@@ -58,10 +59,11 @@ function addDays(date, days) {
/** /**
* Get date extraction SQL that works on both databases * Get date extraction SQL that works on both databases
* @param {object} db - Knex database instance
* @param {string} column - Column name * @param {string} column - Column name
* @returns {object} Knex raw query * @returns {object} Knex raw query
*/ */
function dateExtractSQL(column) { function dateExtractSQL(db, column) {
if (isPostgreSQL()) { if (isPostgreSQL()) {
return db.raw(`DATE(${column})`); return db.raw(`DATE(${column})`);
} else { } else {
@@ -72,10 +74,11 @@ function dateExtractSQL(column) {
/** /**
* Get database size query * Get database size query
* @param {object} db - Knex database instance
* @param {string} dbName - Database name * @param {string} dbName - Database name
* @returns {Promise<number>} Size in bytes * @returns {Promise<number>} Size in bytes
*/ */
async function getDatabaseSize(dbName) { async function getDatabaseSize(db, dbName) {
if (isPostgreSQL()) { if (isPostgreSQL()) {
const result = await db.raw('SELECT pg_database_size(?) as size', [dbName]); const result = await db.raw('SELECT pg_database_size(?) as size', [dbName]);
return result.rows[0]?.size || 0; return result.rows[0]?.size || 0;