const { query } = require("../config/database"); // Whitelist of allowed table names to prevent SQL injection const ALLOWED_TABLES = [ "products", "product_images", "portfolioprojects", "blogposts", "pages", "adminusers", "roles", "uploads", "media_folders", "team_members", "site_settings", "session", ]; // Validate table name against whitelist const validateTableName = (table) => { if (!ALLOWED_TABLES.includes(table)) { throw new Error(`Invalid table name: ${table}`); } return table; }; const buildSelectQuery = ( table, conditions = [], orderBy = "createdat DESC" ) => { validateTableName(table); const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; return `SELECT * FROM ${table} ${whereClause} ORDER BY ${orderBy}`; }; const getById = async (table, id) => { validateTableName(table); const result = await query(`SELECT * FROM ${table} WHERE id = $1`, [id]); return result.rows[0] || null; }; /** * Get product with images by ID * @param {string} productId - Product ID * @returns {Promise} Product with images or null */ const getProductWithImages = async (productId) => { const result = await query( `SELECT p.*, COALESCE( json_agg( json_build_object( 'id', pi.id, 'image_url', pi.image_url, 'color_variant', pi.color_variant, 'color_code', pi.color_code, 'alt_text', pi.alt_text, 'display_order', pi.display_order, 'is_primary', pi.is_primary, 'variant_price', pi.variant_price, 'variant_stock', pi.variant_stock ) ORDER BY pi.display_order ) FILTER (WHERE pi.id IS NOT NULL), '[]'::json ) as images FROM products p LEFT JOIN product_images pi ON pi.product_id = p.id WHERE p.id = $1 GROUP BY p.id`, [productId] ); return result.rows[0] || null; }; const getAllActive = async (table, orderBy = "createdat DESC") => { validateTableName(table); const result = await query( `SELECT * FROM ${table} WHERE isactive = true ORDER BY ${orderBy}` ); return result.rows; }; const deleteById = async (table, id) => { validateTableName(table); const result = await query( `DELETE FROM ${table} WHERE id = $1 RETURNING id`, [id] ); return result.rowCount > 0; }; const countRecords = async (table, condition = "") => { validateTableName(table); const whereClause = condition ? `WHERE ${condition}` : ""; const result = await query(`SELECT COUNT(*) FROM ${table} ${whereClause}`); return parseInt(result.rows[0].count); }; /** * Check if record exists * @param {string} table - Table name * @param {string} field - Field name * @param {any} value - Field value * @returns {Promise} True if exists */ const exists = async (table, field, value) => { validateTableName(table); const result = await query( `SELECT EXISTS(SELECT 1 FROM ${table} WHERE ${field} = $1) as exists`, [value] ); return result.rows[0].exists; }; /** * Batch insert records * @param {string} table - Table name * @param {Array} records - Array of records * @param {Array} fields - Field names (must match for all records) * @returns {Promise} Inserted records */ const batchInsert = async (table, records, fields) => { if (!records || records.length === 0) return []; validateTableName(table); const values = []; const placeholders = []; let paramIndex = 1; records.forEach((record) => { const rowPlaceholders = fields.map(() => `$${paramIndex++}`); placeholders.push(`(${rowPlaceholders.join(", ")})`); fields.forEach((field) => values.push(record[field])); }); const sql = ` INSERT INTO ${table} (${fields.join(", ")}) VALUES ${placeholders.join(", ")} RETURNING * `; const result = await query(sql, values); return result.rows; }; /** * Update multiple records by IDs * @param {string} table - Table name * @param {Array} ids - Array of record IDs * @param {Object} updates - Fields to update * @returns {Promise} Updated records */ const batchUpdate = async (table, ids, updates) => { if (!ids || ids.length === 0) return []; validateTableName(table); const updateFields = Object.keys(updates); const setClause = updateFields .map((field, i) => `${field} = $${i + 1}`) .join(", "); const values = [...Object.values(updates), ids]; const sql = ` UPDATE ${table} SET ${setClause}, updatedat = NOW() WHERE id = ANY($${updateFields.length + 1}) RETURNING * `; const result = await query(sql, values); return result.rows; }; /** * Execute query with transaction * @param {Function} callback - Callback function that receives client * @returns {Promise} Result from callback */ const withTransaction = async (callback) => { const { pool } = require("../config/database"); const client = await pool.connect(); try { await client.query("BEGIN"); const result = await callback(client); await client.query("COMMIT"); return result; } catch (error) { await client.query("ROLLBACK"); throw error; } finally { client.release(); } }; module.exports = { buildSelectQuery, getById, getProductWithImages, getAllActive, deleteById, countRecords, exists, batchInsert, batchUpdate, withTransaction, validateTableName, };