costco-grocery-list/backend/controllers/lists.controller.v2.js

343 lines
9.6 KiB
JavaScript

const List = require("../models/list.model.v2");
const householdModel = require("../models/household.model");
const { isValidItemType, isValidItemGroup, isValidZone } = require("../constants/classifications");
const { sendError } = require("../utils/http");
const { logError } = require("../utils/logger");
/**
* Get list items for household and store
* GET /households/:householdId/stores/:storeId/list
*/
exports.getList = async (req, res) => {
try {
const { householdId, storeId } = req.params;
const items = await List.getHouseholdStoreList(householdId, storeId);
res.json({ items });
} catch (error) {
logError(req, "listsV2.getList", error);
sendError(res, 500, "Failed to get list");
}
};
/**
* Get specific item by name
* GET /households/:householdId/stores/:storeId/list/item
*/
exports.getItemByName = async (req, res) => {
try {
const { householdId, storeId } = req.params;
const { item_name } = req.query;
if (!item_name) {
return sendError(res, 400, "Item name is required");
}
const item = await List.getItemByName(householdId, storeId, item_name);
if (!item) {
return sendError(res, 404, "Item not found");
}
res.json(item);
} catch (error) {
logError(req, "listsV2.getItemByName", error);
sendError(res, 500, "Failed to get item");
}
};
/**
* Add or update item in household store list
* POST /households/:householdId/stores/:storeId/list/add
*/
exports.addItem = async (req, res) => {
try {
const { householdId, storeId } = req.params;
const { item_name, quantity, notes, added_for_user_id } = req.body;
const userId = req.user.id;
let historyUserId = userId;
if (!item_name || item_name.trim() === "") {
return sendError(res, 400, "Item name is required");
}
if (added_for_user_id !== undefined && added_for_user_id !== null && String(added_for_user_id).trim() !== "") {
const parsedUserId = Number.parseInt(String(added_for_user_id), 10);
if (!Number.isInteger(parsedUserId) || parsedUserId <= 0) {
return sendError(res, 400, "Added-for user ID must be a positive integer");
}
const isMember = await householdModel.isHouseholdMember(householdId, parsedUserId);
if (!isMember) {
return sendError(res, 400, "Selected user is not a member of this household");
}
historyUserId = parsedUserId;
}
// Get processed image if uploaded
const imageBuffer = req.processedImage?.buffer || null;
const mimeType = req.processedImage?.mimeType || null;
const result = await List.addOrUpdateItem(
householdId,
storeId,
item_name,
quantity || "1",
userId,
imageBuffer,
mimeType,
notes
);
// Add history record
await List.addHistoryRecord(result.listId, quantity || "1", historyUserId);
res.json({
message: result.isNew ? "Item added" : "Item updated",
item: {
id: result.listId,
item_name: result.itemName,
quantity: quantity || "1",
bought: false
}
});
} catch (error) {
logError(req, "listsV2.addItem", error);
sendError(res, 500, "Failed to add item");
}
};
/**
* Mark item as bought or unbought
* PATCH /households/:householdId/stores/:storeId/list/item
*/
exports.markBought = async (req, res) => {
try {
const { householdId, storeId } = req.params;
const { item_name, bought, quantity_bought } = req.body;
if (!item_name) return sendError(res, 400, "Item name is required");
const item = await List.getItemByName(householdId, storeId, item_name);
if (!item) return sendError(res, 404, "Item not found");
// Update bought status (with optional partial purchase)
await List.setBought(item.id, bought, quantity_bought);
res.json({ message: bought ? "Item marked as bought" : "Item unmarked" });
} catch (error) {
logError(req, "listsV2.markBought", error);
sendError(res, 500, "Failed to update item");
}
};
/**
* Update item details (quantity, notes)
* PUT /households/:householdId/stores/:storeId/list/item
*/
exports.updateItem = async (req, res) => {
try {
const { householdId, storeId } = req.params;
const { item_name, quantity, notes } = req.body;
if (!item_name) {
return sendError(res, 400, "Item name is required");
}
// Get the list item
const item = await List.getItemByName(householdId, storeId, item_name);
if (!item) {
return sendError(res, 404, "Item not found");
}
// Update item
await List.updateItem(item.id, item_name, quantity, notes);
res.json({
message: "Item updated",
item: {
id: item.id,
item_name,
quantity,
notes
}
});
} catch (error) {
logError(req, "listsV2.updateItem", error);
sendError(res, 500, "Failed to update item");
}
};
/**
* Delete item from list
* DELETE /households/:householdId/stores/:storeId/list/item
*/
exports.deleteItem = async (req, res) => {
try {
const { householdId, storeId } = req.params;
const { item_name } = req.body;
if (!item_name) {
return sendError(res, 400, "Item name is required");
}
// Get the list item
const item = await List.getItemByName(householdId, storeId, item_name);
if (!item) {
return sendError(res, 404, "Item not found");
}
await List.deleteItem(item.id);
res.json({ message: "Item deleted" });
} catch (error) {
logError(req, "listsV2.deleteItem", error);
sendError(res, 500, "Failed to delete item");
}
};
/**
* Get item suggestions based on query
* GET /households/:householdId/stores/:storeId/list/suggestions
*/
exports.getSuggestions = async (req, res) => {
try {
const { householdId, storeId } = req.params;
const { query } = req.query;
const suggestions = await List.getSuggestions(query || "", householdId, storeId);
res.json(suggestions);
} catch (error) {
logError(req, "listsV2.getSuggestions", error);
sendError(res, 500, "Failed to get suggestions");
}
};
/**
* Get recently bought items
* GET /households/:householdId/stores/:storeId/list/recent
*/
exports.getRecentlyBought = async (req, res) => {
try {
const { householdId, storeId } = req.params;
const items = await List.getRecentlyBoughtItems(householdId, storeId);
res.json(items);
} catch (error) {
logError(req, "listsV2.getRecentlyBought", error);
sendError(res, 500, "Failed to get recent items");
}
};
/**
* Get item classification
* GET /households/:householdId/stores/:storeId/list/classification
*/
exports.getClassification = async (req, res) => {
try {
const { householdId, storeId } = req.params;
const { item_name } = req.query;
if (!item_name) {
return sendError(res, 400, "Item name is required");
}
// Get item ID from name
const item = await List.getItemByName(householdId, storeId, item_name);
if (!item) {
return res.json({ classification: null });
}
const classification = await List.getClassification(householdId, item.item_id);
res.json({ classification });
} catch (error) {
logError(req, "listsV2.getClassification", error);
sendError(res, 500, "Failed to get classification");
}
};
/**
* Set/update item classification
* POST /households/:householdId/stores/:storeId/list/classification
*/
exports.setClassification = async (req, res) => {
try {
const { householdId, storeId } = req.params;
const { item_name, classification } = req.body;
if (!item_name) {
return sendError(res, 400, "Item name is required");
}
if (!classification) {
return sendError(res, 400, "Classification is required");
}
// Validate classification
const validClassifications = ['produce', 'dairy', 'meat', 'bakery', 'frozen', 'pantry', 'snacks', 'beverages', 'household', 'other'];
if (!validClassifications.includes(classification)) {
return sendError(res, 400, "Invalid classification value");
}
// Get item - add to master items if not exists
const item = await List.getItemByName(householdId, storeId, item_name);
let itemId;
if (!item) {
// Item doesn't exist in list, need to get from items table or create
const itemResult = await List.addOrUpdateItem(
householdId,
storeId,
item_name,
"1",
req.user.id,
null,
null
);
itemId = itemResult.itemId;
} else {
itemId = item.item_id;
}
// Set classification (using item_type field for simplicity)
await List.upsertClassification(householdId, itemId, {
item_type: classification,
item_group: null,
zone: null
});
res.json({ message: "Classification set", classification });
} catch (error) {
logError(req, "listsV2.setClassification", error);
sendError(res, 500, "Failed to set classification");
}
};
/**
* Update item image
* POST /households/:householdId/stores/:storeId/list/update-image
*/
exports.updateItemImage = async (req, res) => {
try {
const { householdId, storeId } = req.params;
const { item_name, quantity } = req.body;
const userId = req.user.id;
// Get processed image
const imageBuffer = req.processedImage?.buffer || null;
const mimeType = req.processedImage?.mimeType || null;
if (!imageBuffer) {
return sendError(res, 400, "No image provided");
}
// Update the item with new image
await List.addOrUpdateItem(householdId, storeId, item_name, quantity, userId, imageBuffer, mimeType);
res.json({ message: "Image updated successfully" });
} catch (error) {
logError(req, "listsV2.updateItemImage", error);
sendError(res, 500, "Failed to update image");
}
};