const pool = require("../db/pool"); function normalizeItemName(itemName) { return String(itemName || "").trim().toLowerCase(); } async function findOrCreateItem(itemName) { const normalizedName = normalizeItemName(itemName); const existing = await pool.query( "SELECT id, name FROM items WHERE name ILIKE $1", [normalizedName] ); if (existing.rowCount > 0) { return { itemId: existing.rows[0].id, itemName: existing.rows[0].name, }; } const created = await pool.query( "INSERT INTO items (name) VALUES ($1) RETURNING id, name", [normalizedName] ); return { itemId: created.rows[0].id, itemName: created.rows[0].name, }; } async function getAvailableItemRecord(householdId, storeId, itemId) { const result = await pool.query( `SELECT hsai.item_id, i.name AS item_name, ENCODE(hsai.custom_image, 'base64') AS item_image, hsai.custom_image_mime_type AS image_mime_type, hic.item_type, hic.item_group, hic.zone, hsai.created_at, hsai.updated_at FROM household_store_available_items hsai JOIN items i ON i.id = hsai.item_id LEFT JOIN household_item_classifications hic ON hic.household_id = hsai.household_id AND hic.store_id = hsai.store_id AND hic.item_id = hsai.item_id WHERE hsai.household_id = $1 AND hsai.store_id = $2 AND hsai.item_id = $3`, [householdId, storeId, itemId] ); return result.rows[0] || null; } exports.listAvailableItems = async (householdId, storeId, query = "") => { const trimmedQuery = String(query || "").trim(); const values = [householdId, storeId]; let filterClause = ""; if (trimmedQuery) { values.push(`%${trimmedQuery}%`); filterClause = "AND i.name ILIKE $3"; } const result = await pool.query( `SELECT hsai.item_id, i.name AS item_name, ENCODE(hsai.custom_image, 'base64') AS item_image, hsai.custom_image_mime_type AS image_mime_type, hic.item_type, hic.item_group, hic.zone, hsai.created_at, hsai.updated_at FROM household_store_available_items hsai JOIN items i ON i.id = hsai.item_id LEFT JOIN household_item_classifications hic ON hic.household_id = hsai.household_id AND hic.store_id = hsai.store_id AND hic.item_id = hsai.item_id WHERE hsai.household_id = $1 AND hsai.store_id = $2 ${filterClause} ORDER BY i.name ASC LIMIT 100`, values ); return result.rows; }; exports.getAvailableItemById = async (householdId, storeId, itemId) => getAvailableItemRecord(householdId, storeId, itemId); exports.getAvailableItemImageByName = async (householdId, storeId, itemName) => { const normalizedName = normalizeItemName(itemName); const result = await pool.query( `SELECT hsai.item_id, i.name AS item_name, hsai.custom_image, hsai.custom_image_mime_type FROM household_store_available_items hsai JOIN items i ON i.id = hsai.item_id WHERE hsai.household_id = $1 AND hsai.store_id = $2 AND i.name ILIKE $3`, [householdId, storeId, normalizedName] ); return result.rows[0] || null; }; exports.createAvailableItem = async ( householdId, storeId, itemName, imageBuffer = null, mimeType = null ) => { const { itemId } = await findOrCreateItem(itemName); await pool.query( `INSERT INTO household_store_available_items (household_id, store_id, item_id, custom_image, custom_image_mime_type, updated_at) VALUES ($1, $2, $3, $4, $5, NOW())`, [householdId, storeId, itemId, imageBuffer, mimeType] ); return getAvailableItemRecord(householdId, storeId, itemId); }; exports.updateAvailableItem = async (householdId, storeId, itemId, updates = {}) => { const { itemName, imageBuffer, mimeType, removeImage = false, } = updates; const assignments = ["updated_at = NOW()"]; const values = [householdId, storeId, itemId]; let parameterIndex = values.length; if (itemName !== undefined && String(itemName).trim() !== "") { const { itemId: nextItemId } = await findOrCreateItem(itemName); parameterIndex += 1; assignments.push(`item_id = $${parameterIndex}`); values.push(nextItemId); } if (removeImage) { assignments.push("custom_image = NULL", "custom_image_mime_type = NULL"); } else if (imageBuffer && mimeType) { parameterIndex += 1; assignments.push(`custom_image = $${parameterIndex}`); values.push(imageBuffer); parameterIndex += 1; assignments.push(`custom_image_mime_type = $${parameterIndex}`); values.push(mimeType); } const result = await pool.query( `UPDATE household_store_available_items SET ${assignments.join(", ")} WHERE household_id = $1 AND store_id = $2 AND item_id = $3 RETURNING item_id`, values ); if (result.rowCount === 0) { return null; } return getAvailableItemRecord(householdId, storeId, result.rows[0].item_id); }; exports.deleteAvailableItem = async (householdId, storeId, itemId) => { const result = await pool.query( `DELETE FROM household_store_available_items WHERE household_id = $1 AND store_id = $2 AND item_id = $3`, [householdId, storeId, itemId] ); return result.rowCount > 0; }; exports.importCurrentListItems = async (householdId, storeId) => { const result = await pool.query( `INSERT INTO household_store_available_items (household_id, store_id, item_id, custom_image, custom_image_mime_type, updated_at) SELECT hl.household_id, hl.store_id, hl.item_id, hl.custom_image, hl.custom_image_mime_type, NOW() FROM household_lists hl WHERE hl.household_id = $1 AND hl.store_id = $2 ON CONFLICT (household_id, store_id, item_id) DO NOTHING RETURNING item_id`, [householdId, storeId] ); return result.rowCount; }; exports.hasAvailableItems = async (householdId, storeId) => { const result = await pool.query( `SELECT 1 FROM household_store_available_items WHERE household_id = $1 AND store_id = $2 LIMIT 1`, [householdId, storeId] ); return result.rowCount > 0; };