grocery-app/backend/models/available-item.model.js
2026-03-28 22:35:34 -07:00

232 lines
6.1 KiB
JavaScript

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;
};