grocery-app/backend/models/available-item.model.js

274 lines
7.7 KiB
JavaScript

const pool = require("../db/pool");
function normalizeItemName(itemName) {
return String(itemName || "").trim().toLowerCase();
}
async function getHouseholdStoreItemRecord(householdId, storeId, itemId) {
const result = await pool.query(
`WITH latest_list_items AS (
SELECT DISTINCT ON (hl.household_store_item_id)
hl.household_store_item_id,
hl.custom_image,
hl.custom_image_mime_type,
hl.modified_on,
hl.id
FROM household_lists hl
WHERE hl.household_id = $1
AND hl.store_id = $2
ORDER BY hl.household_store_item_id, hl.modified_on DESC NULLS LAST, hl.id DESC
)
SELECT
hsi.id AS item_id,
hsi.name AS item_name,
ENCODE(COALESCE(hsi.custom_image, lli.custom_image), 'base64') AS item_image,
COALESCE(hsi.custom_image_mime_type, lli.custom_image_mime_type) AS image_mime_type,
hic.item_type,
hic.item_group,
hic.zone
FROM household_store_items hsi
LEFT JOIN latest_list_items lli ON lli.household_store_item_id = hsi.id
LEFT JOIN household_item_classifications hic
ON hic.household_id = hsi.household_id
AND hic.store_id = hsi.store_id
AND hic.household_store_item_id = hsi.id
WHERE hsi.household_id = $1
AND hsi.store_id = $2
AND hsi.id = $3`,
[householdId, storeId, itemId]
);
return result.rows[0] || null;
}
async function findOrCreateHouseholdStoreItem(householdId, storeId, itemName) {
const normalizedName = normalizeItemName(itemName);
const existing = await pool.query(
`SELECT id, name
FROM household_store_items
WHERE household_id = $1
AND store_id = $2
AND normalized_name = $3`,
[householdId, storeId, normalizedName]
);
if (existing.rowCount > 0) {
return {
itemId: existing.rows[0].id,
itemName: existing.rows[0].name,
};
}
const created = await pool.query(
`INSERT INTO household_store_items
(household_id, store_id, name, normalized_name, updated_at)
VALUES ($1, $2, $3, $4, NOW())
RETURNING id, name`,
[householdId, storeId, normalizedName, normalizedName]
);
return {
itemId: created.rows[0].id,
itemName: created.rows[0].name,
};
}
exports.listAvailableItems = async (householdId, storeId, query = "") => {
const trimmedQuery = String(query || "").trim();
const values = [householdId, storeId];
let filterClause = "";
if (trimmedQuery) {
values.push(`%${trimmedQuery}%`);
filterClause = "AND hsi.name ILIKE $3";
}
const result = await pool.query(
`WITH latest_list_items AS (
SELECT DISTINCT ON (hl.household_store_item_id)
hl.household_store_item_id,
hl.custom_image,
hl.custom_image_mime_type,
hl.modified_on,
hl.id
FROM household_lists hl
WHERE hl.household_id = $1
AND hl.store_id = $2
ORDER BY hl.household_store_item_id, hl.modified_on DESC NULLS LAST, hl.id DESC
)
SELECT
hsi.id AS item_id,
hsi.name AS item_name,
ENCODE(COALESCE(hsi.custom_image, lli.custom_image), 'base64') AS item_image,
COALESCE(hsi.custom_image_mime_type, lli.custom_image_mime_type) AS image_mime_type,
hic.item_type,
hic.item_group,
hic.zone,
(
hsi.custom_image IS NOT NULL
OR hic.household_store_item_id IS NOT NULL
) AS has_managed_settings
FROM household_store_items hsi
LEFT JOIN latest_list_items lli ON lli.household_store_item_id = hsi.id
LEFT JOIN household_item_classifications hic
ON hic.household_id = hsi.household_id
AND hic.store_id = hsi.store_id
AND hic.household_store_item_id = hsi.id
WHERE hsi.household_id = $1
AND hsi.store_id = $2
${filterClause}
ORDER BY hsi.name ASC
LIMIT 100`,
values
);
return result.rows;
};
exports.getAvailableItemById = async (householdId, storeId, itemId) =>
getHouseholdStoreItemRecord(householdId, storeId, itemId);
exports.getAvailableItemImageByName = async (householdId, storeId, itemName) => {
const normalizedName = normalizeItemName(itemName);
const result = await pool.query(
`SELECT
id AS item_id,
name AS item_name,
custom_image,
custom_image_mime_type
FROM household_store_items
WHERE household_id = $1
AND store_id = $2
AND normalized_name = $3`,
[householdId, storeId, normalizedName]
);
return result.rows[0] || null;
};
exports.createAvailableItem = async (
householdId,
storeId,
itemName,
imageBuffer = null,
mimeType = null
) => {
const { itemId } = await findOrCreateHouseholdStoreItem(householdId, storeId, itemName);
if (imageBuffer && mimeType) {
await pool.query(
`UPDATE household_store_items
SET custom_image = $1,
custom_image_mime_type = $2,
updated_at = NOW()
WHERE id = $3
AND household_id = $4
AND store_id = $5`,
[imageBuffer, mimeType, itemId, householdId, storeId]
);
}
return getHouseholdStoreItemRecord(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 normalizedName = normalizeItemName(itemName);
parameterIndex += 1;
assignments.push(`name = $${parameterIndex}`);
values.push(normalizedName);
parameterIndex += 1;
assignments.push(`normalized_name = $${parameterIndex}`);
values.push(normalizedName);
}
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_items
SET ${assignments.join(", ")}
WHERE household_id = $1
AND store_id = $2
AND id = $3
RETURNING id`,
values
);
if (result.rowCount === 0) {
return null;
}
return getHouseholdStoreItemRecord(householdId, storeId, result.rows[0].id);
};
exports.deleteAvailableItem = async (householdId, storeId, itemId) => {
const result = await pool.query(
`DELETE FROM household_store_items
WHERE household_id = $1
AND store_id = $2
AND id = $3`,
[householdId, storeId, itemId]
);
return result.rowCount > 0;
};
exports.importCurrentListItems = async (householdId, storeId) => {
const result = await pool.query(
`INSERT INTO household_store_items
(household_id, store_id, name, normalized_name, custom_image, custom_image_mime_type, updated_at)
SELECT DISTINCT ON (hl.household_store_item_id)
hl.household_id,
hl.store_id,
hsi.name,
hsi.normalized_name,
hsi.custom_image,
hsi.custom_image_mime_type,
NOW()
FROM household_lists hl
JOIN household_store_items hsi ON hsi.id = hl.household_store_item_id
WHERE hl.household_id = $1
AND hl.store_id = $2
ON CONFLICT (household_id, store_id, normalized_name) DO NOTHING
RETURNING id`,
[householdId, storeId]
);
return result.rowCount;
};
exports.hasAvailableItems = async (householdId, storeId) => {
const result = await pool.query(
`SELECT 1
FROM household_store_items
WHERE household_id = $1
AND store_id = $2
LIMIT 1`,
[householdId, storeId]
);
return result.rowCount > 0;
};