const pool = require("../db/pool"); exports.getUnboughtItems = async () => { const result = await pool.query( `SELECT gl.id, LOWER(gl.item_name) AS item_name, gl.quantity, gl.bought, ENCODE(gl.item_image, 'base64') as item_image, gl.image_mime_type, ( SELECT ARRAY_AGG(DISTINCT u.name) FROM ( SELECT DISTINCT gh.added_by FROM grocery_history gh WHERE gh.list_item_id = gl.id ORDER BY gh.added_by ) gh JOIN users u ON gh.added_by = u.id ) as added_by_users, gl.modified_on as last_added_on, ic.item_type, ic.item_group, ic.zone FROM grocery_list gl LEFT JOIN item_classification ic ON gl.id = ic.id WHERE gl.bought = FALSE ORDER BY gl.id ASC` ); return result.rows; }; exports.getItemByName = async (itemName) => { const result = await pool.query( `SELECT gl.id, LOWER(gl.item_name) AS item_name, gl.quantity, gl.bought, ENCODE(gl.item_image, 'base64') as item_image, gl.image_mime_type, ( SELECT ARRAY_AGG(DISTINCT u.name) FROM ( SELECT DISTINCT gh.added_by FROM grocery_history gh WHERE gh.list_item_id = gl.id ORDER BY gh.added_by ) gh JOIN users u ON gh.added_by = u.id ) as added_by_users, gl.modified_on as last_added_on, ic.item_type, ic.item_group, ic.zone FROM grocery_list gl LEFT JOIN item_classification ic ON gl.id = ic.id WHERE gl.item_name ILIKE $1`, [itemName] ); return result.rows[0] || null; }; exports.addOrUpdateItem = async (itemName, quantity, userId, imageBuffer = null, mimeType = null) => { const lowerItemName = itemName.toLowerCase(); const result = await pool.query( "SELECT id, bought FROM grocery_list WHERE item_name ILIKE $1", [lowerItemName] ); if (result.rowCount > 0) { // Update existing item if (imageBuffer && mimeType) { await pool.query( `UPDATE grocery_list SET quantity = $1, bought = FALSE, item_image = $3, image_mime_type = $4, modified_on = NOW() WHERE id = $2`, [quantity, result.rows[0].id, imageBuffer, mimeType] ); } else { await pool.query( `UPDATE grocery_list SET quantity = $1, bought = FALSE, modified_on = NOW() WHERE id = $2`, [quantity, result.rows[0].id] ); } return result.rows[0].id; } else { // Insert new item const insert = await pool.query( `INSERT INTO grocery_list (item_name, quantity, added_by, item_image, image_mime_type) VALUES ($1, $2, $3, $4, $5) RETURNING id`, [lowerItemName, quantity, userId, imageBuffer, mimeType] ); return insert.rows[0].id; } }; exports.setBought = async (id, userId, quantityBought) => { // Get current item const item = await pool.query( "SELECT quantity FROM grocery_list WHERE id = $1", [id] ); if (!item.rows[0]) return; const currentQuantity = item.rows[0].quantity; const remainingQuantity = currentQuantity - quantityBought; if (remainingQuantity <= 0) { // Mark as bought if all quantity is purchased await pool.query( "UPDATE grocery_list SET bought = TRUE, modified_on = NOW() WHERE id = $1", [id] ); } else { // Reduce quantity if partial purchase await pool.query( "UPDATE grocery_list SET quantity = $1, modified_on = NOW() WHERE id = $2", [remainingQuantity, id] ); } }; exports.addHistoryRecord = async (itemId, quantity, userId) => { await pool.query( `INSERT INTO grocery_history (list_item_id, quantity, added_by, added_on) VALUES ($1, $2, $3, NOW())`, [itemId, quantity, userId] ); }; exports.getSuggestions = async (query) => { const result = await pool.query( `SELECT DISTINCT LOWER(item_name) as item_name FROM grocery_list WHERE item_name ILIKE $1 LIMIT 10`, [`%${query}%`] ); return result.rows; }; exports.getRecentlyBoughtItems = async () => { const result = await pool.query( `SELECT gl.id, LOWER(gl.item_name) AS item_name, gl.quantity, gl.bought, ENCODE(gl.item_image, 'base64') as item_image, gl.image_mime_type, ( SELECT ARRAY_AGG(DISTINCT u.name) FROM ( SELECT DISTINCT gh.added_by FROM grocery_history gh WHERE gh.list_item_id = gl.id ORDER BY gh.added_by ) gh JOIN users u ON gh.added_by = u.id ) as added_by_users, gl.modified_on as last_added_on FROM grocery_list gl WHERE gl.bought = TRUE AND gl.modified_on >= NOW() - INTERVAL '24 hours' ORDER BY gl.modified_on DESC` ); return result.rows; }; // Classification methods exports.getClassification = async (itemId) => { const result = await pool.query( `SELECT item_type, item_group, zone, confidence, source FROM item_classification WHERE id = $1`, [itemId] ); return result.rows[0] || null; }; exports.upsertClassification = async (itemId, classification) => { const { item_type, item_group, zone, confidence, source } = classification; const result = await pool.query( `INSERT INTO item_classification (id, item_type, item_group, zone, confidence, source) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (id) DO UPDATE SET item_type = EXCLUDED.item_type, item_group = EXCLUDED.item_group, zone = EXCLUDED.zone, confidence = EXCLUDED.confidence, source = EXCLUDED.source RETURNING *`, [itemId, item_type, item_group, zone, confidence, source] ); return result.rows[0]; }; exports.updateItem = async (id, itemName, quantity) => { const result = await pool.query( `UPDATE grocery_list SET item_name = $2, quantity = $3, modified_on = NOW() WHERE id = $1 RETURNING *`, [id, itemName, quantity] ); return result.rows[0]; };