From 9a73cea27d852c75f32cb19aa9dac96ec8ffbe16 Mon Sep 17 00:00:00 2001 From: Nico Date: Mon, 16 Feb 2026 01:26:18 -0800 Subject: [PATCH] refactor: adopt sendError helper across core controllers --- backend/controllers/households.controller.js | 43 ++++--- backend/controllers/lists.controller.v2.js | 51 ++++---- backend/controllers/stores.controller.js | 29 ++--- backend/controllers/users.controller.js | 123 ++++++++++--------- 4 files changed, 124 insertions(+), 122 deletions(-) diff --git a/backend/controllers/households.controller.js b/backend/controllers/households.controller.js index 828fd8a..9c3076a 100644 --- a/backend/controllers/households.controller.js +++ b/backend/controllers/households.controller.js @@ -1,4 +1,5 @@ const householdModel = require("../models/household.model"); +const { sendError } = require("../utils/http"); // Get all households user belongs to exports.getUserHouseholds = async (req, res) => { @@ -7,7 +8,7 @@ exports.getUserHouseholds = async (req, res) => { res.json(households); } catch (error) { console.error("Get user households error:", error); - res.status(500).json({ error: "Failed to fetch households" }); + sendError(res, 500, "Failed to fetch households"); } }; @@ -20,13 +21,13 @@ exports.getHousehold = async (req, res) => { ); if (!household) { - return res.status(404).json({ error: "Household not found" }); + return sendError(res, 404, "Household not found"); } res.json(household); } catch (error) { console.error("Get household error:", error); - res.status(500).json({ error: "Failed to fetch household" }); + sendError(res, 500, "Failed to fetch household"); } }; @@ -36,11 +37,11 @@ exports.createHousehold = async (req, res) => { const { name } = req.body; if (!name || name.trim().length === 0) { - return res.status(400).json({ error: "Household name is required" }); + return sendError(res, 400, "Household name is required"); } if (name.length > 100) { - return res.status(400).json({ error: "Household name must be 100 characters or less" }); + return sendError(res, 400, "Household name must be 100 characters or less"); } const household = await householdModel.createHousehold( @@ -54,7 +55,7 @@ exports.createHousehold = async (req, res) => { }); } catch (error) { console.error("Create household error:", error); - res.status(500).json({ error: "Failed to create household" }); + sendError(res, 500, "Failed to create household"); } }; @@ -64,11 +65,11 @@ exports.updateHousehold = async (req, res) => { const { name } = req.body; if (!name || name.trim().length === 0) { - return res.status(400).json({ error: "Household name is required" }); + return sendError(res, 400, "Household name is required"); } if (name.length > 100) { - return res.status(400).json({ error: "Household name must be 100 characters or less" }); + return sendError(res, 400, "Household name must be 100 characters or less"); } const household = await householdModel.updateHousehold( @@ -82,7 +83,7 @@ exports.updateHousehold = async (req, res) => { }); } catch (error) { console.error("Update household error:", error); - res.status(500).json({ error: "Failed to update household" }); + sendError(res, 500, "Failed to update household"); } }; @@ -93,7 +94,7 @@ exports.deleteHousehold = async (req, res) => { res.json({ message: "Household deleted successfully" }); } catch (error) { console.error("Delete household error:", error); - res.status(500).json({ error: "Failed to delete household" }); + sendError(res, 500, "Failed to delete household"); } }; @@ -107,7 +108,7 @@ exports.refreshInviteCode = async (req, res) => { }); } catch (error) { console.error("Refresh invite code error:", error); - res.status(500).json({ error: "Failed to refresh invite code" }); + sendError(res, 500, "Failed to refresh invite code"); } }; @@ -115,14 +116,14 @@ exports.refreshInviteCode = async (req, res) => { exports.joinHousehold = async (req, res) => { try { const { inviteCode } = req.params; - if (!inviteCode) return res.status(400).json({ error: "Invite code is required" }); + if (!inviteCode) return sendError(res, 400, "Invite code is required"); const result = await householdModel.joinHousehold( inviteCode.toUpperCase(), req.user.id ); - if (!result) return res.status(404).json({ error: "Invalid or expired invite code" }); + if (!result) return sendError(res, 404, "Invalid or expired invite code"); if (result.alreadyMember) { @@ -138,7 +139,7 @@ exports.joinHousehold = async (req, res) => { }); } catch (error) { console.error("Join household error:", error); - res.status(500).json({ error: "Failed to join household" }); + sendError(res, 500, "Failed to join household"); } }; @@ -149,7 +150,7 @@ exports.getMembers = async (req, res) => { res.json(members); } catch (error) { console.error("Get members error:", error); - res.status(500).json({ error: "Failed to fetch members" }); + sendError(res, 500, "Failed to fetch members"); } }; @@ -160,12 +161,12 @@ exports.updateMemberRole = async (req, res) => { const { role } = req.body; if (!role || !['admin', 'user'].includes(role)) { - return res.status(400).json({ error: "Invalid role. Must be 'admin' or 'user'" }); + return sendError(res, 400, "Invalid role. Must be 'admin' or 'user'"); } // Can't change own role if (parseInt(userId) === req.user.id) { - return res.status(400).json({ error: "Cannot change your own role" }); + return sendError(res, 400, "Cannot change your own role"); } const updated = await householdModel.updateMemberRole( @@ -180,7 +181,7 @@ exports.updateMemberRole = async (req, res) => { }); } catch (error) { console.error("Update member role error:", error); - res.status(500).json({ error: "Failed to update member role" }); + sendError(res, 500, "Failed to update member role"); } }; @@ -192,9 +193,7 @@ exports.removeMember = async (req, res) => { // Allow users to remove themselves, or admins to remove others if (targetUserId !== req.user.id && req.household.role !== 'admin') { - return res.status(403).json({ - error: "Only admins can remove other members" - }); + return sendError(res, 403, "Only admins can remove other members"); } await householdModel.removeMember(req.params.householdId, userId); @@ -202,6 +201,6 @@ exports.removeMember = async (req, res) => { res.json({ message: "Member removed successfully" }); } catch (error) { console.error("Remove member error:", error); - res.status(500).json({ error: "Failed to remove member" }); + sendError(res, 500, "Failed to remove member"); } }; diff --git a/backend/controllers/lists.controller.v2.js b/backend/controllers/lists.controller.v2.js index 174c96a..f232057 100644 --- a/backend/controllers/lists.controller.v2.js +++ b/backend/controllers/lists.controller.v2.js @@ -1,5 +1,6 @@ const List = require("../models/list.model.v2"); const { isValidItemType, isValidItemGroup, isValidZone } = require("../constants/classifications"); +const { sendError } = require("../utils/http"); /** * Get list items for household and store @@ -12,7 +13,7 @@ exports.getList = async (req, res) => { res.json({ items }); } catch (error) { console.error("Error getting list:", error); - res.status(500).json({ message: "Failed to get list" }); + sendError(res, 500, "Failed to get list"); } }; @@ -26,18 +27,18 @@ exports.getItemByName = async (req, res) => { const { item_name } = req.query; if (!item_name) { - return res.status(400).json({ message: "Item name is required" }); + return sendError(res, 400, "Item name is required"); } const item = await List.getItemByName(householdId, storeId, item_name); if (!item) { - return res.status(404).json({ message: "Item not found" }); + return sendError(res, 404, "Item not found"); } res.json(item); } catch (error) { console.error("Error getting item:", error); - res.status(500).json({ message: "Failed to get item" }); + sendError(res, 500, "Failed to get item"); } }; @@ -52,7 +53,7 @@ exports.addItem = async (req, res) => { const userId = req.user.id; if (!item_name || item_name.trim() === "") { - return res.status(400).json({ message: "Item name is required" }); + return sendError(res, 400, "Item name is required"); } // Get processed image if uploaded @@ -84,7 +85,7 @@ exports.addItem = async (req, res) => { }); } catch (error) { console.error("Error adding item:", error); - res.status(500).json({ message: "Failed to add item" }); + sendError(res, 500, "Failed to add item"); } }; @@ -97,10 +98,10 @@ exports.markBought = async (req, res) => { const { householdId, storeId } = req.params; const { item_name, bought, quantity_bought } = req.body; - if (!item_name) return res.status(400).json({ message: "Item name is required" }); + if (!item_name) return sendError(res, 400, "Item name is required"); const item = await List.getItemByName(householdId, storeId, item_name); - if (!item) return res.status(404).json({ message: "Item not found" }); + if (!item) return sendError(res, 404, "Item not found"); // Update bought status (with optional partial purchase) @@ -109,7 +110,7 @@ exports.markBought = async (req, res) => { res.json({ message: bought ? "Item marked as bought" : "Item unmarked" }); } catch (error) { console.error("Error marking bought:", error); - res.status(500).json({ message: "Failed to update item" }); + sendError(res, 500, "Failed to update item"); } }; @@ -123,13 +124,13 @@ exports.updateItem = async (req, res) => { const { item_name, quantity, notes } = req.body; if (!item_name) { - return res.status(400).json({ message: "Item name is required" }); + return sendError(res, 400, "Item name is required"); } // Get the list item const item = await List.getItemByName(householdId, storeId, item_name); if (!item) { - return res.status(404).json({ message: "Item not found" }); + return sendError(res, 404, "Item not found"); } // Update item @@ -146,7 +147,7 @@ exports.updateItem = async (req, res) => { }); } catch (error) { console.error("Error updating item:", error); - res.status(500).json({ message: "Failed to update item" }); + sendError(res, 500, "Failed to update item"); } }; @@ -160,13 +161,13 @@ exports.deleteItem = async (req, res) => { const { item_name } = req.body; if (!item_name) { - return res.status(400).json({ message: "Item name is required" }); + return sendError(res, 400, "Item name is required"); } // Get the list item const item = await List.getItemByName(householdId, storeId, item_name); if (!item) { - return res.status(404).json({ message: "Item not found" }); + return sendError(res, 404, "Item not found"); } await List.deleteItem(item.id); @@ -174,7 +175,7 @@ exports.deleteItem = async (req, res) => { res.json({ message: "Item deleted" }); } catch (error) { console.error("Error deleting item:", error); - res.status(500).json({ message: "Failed to delete item" }); + sendError(res, 500, "Failed to delete item"); } }; @@ -191,7 +192,7 @@ exports.getSuggestions = async (req, res) => { res.json(suggestions); } catch (error) { console.error("Error getting suggestions:", error); - res.status(500).json({ message: "Failed to get suggestions" }); + sendError(res, 500, "Failed to get suggestions"); } }; @@ -206,7 +207,7 @@ exports.getRecentlyBought = async (req, res) => { res.json(items); } catch (error) { console.error("Error getting recent items:", error); - res.status(500).json({ message: "Failed to get recent items" }); + sendError(res, 500, "Failed to get recent items"); } }; @@ -220,7 +221,7 @@ exports.getClassification = async (req, res) => { const { item_name } = req.query; if (!item_name) { - return res.status(400).json({ message: "Item name is required" }); + return sendError(res, 400, "Item name is required"); } // Get item ID from name @@ -233,7 +234,7 @@ exports.getClassification = async (req, res) => { res.json({ classification }); } catch (error) { console.error("Error getting classification:", error); - res.status(500).json({ message: "Failed to get classification" }); + sendError(res, 500, "Failed to get classification"); } }; @@ -247,17 +248,17 @@ exports.setClassification = async (req, res) => { const { item_name, classification } = req.body; if (!item_name) { - return res.status(400).json({ message: "Item name is required" }); + return sendError(res, 400, "Item name is required"); } if (!classification) { - return res.status(400).json({ message: "Classification is required" }); + 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 res.status(400).json({ message: "Invalid classification value" }); + return sendError(res, 400, "Invalid classification value"); } // Get item - add to master items if not exists @@ -290,7 +291,7 @@ exports.setClassification = async (req, res) => { res.json({ message: "Classification set", classification }); } catch (error) { console.error("Error setting classification:", error); - res.status(500).json({ message: "Failed to set classification" }); + sendError(res, 500, "Failed to set classification"); } }; @@ -309,7 +310,7 @@ exports.updateItemImage = async (req, res) => { const mimeType = req.processedImage?.mimeType || null; if (!imageBuffer) { - return res.status(400).json({ message: "No image provided" }); + return sendError(res, 400, "No image provided"); } // Update the item with new image @@ -318,6 +319,6 @@ exports.updateItemImage = async (req, res) => { res.json({ message: "Image updated successfully" }); } catch (error) { console.error("Error updating image:", error); - res.status(500).json({ message: "Failed to update image" }); + sendError(res, 500, "Failed to update image"); } }; diff --git a/backend/controllers/stores.controller.js b/backend/controllers/stores.controller.js index 51a48f8..711f157 100644 --- a/backend/controllers/stores.controller.js +++ b/backend/controllers/stores.controller.js @@ -1,4 +1,5 @@ const storeModel = require("../models/store.model"); +const { sendError } = require("../utils/http"); // Get all available stores exports.getAllStores = async (req, res) => { @@ -7,7 +8,7 @@ exports.getAllStores = async (req, res) => { res.json(stores); } catch (error) { console.error("Get all stores error:", error); - res.status(500).json({ error: "Failed to fetch stores" }); + sendError(res, 500, "Failed to fetch stores"); } }; @@ -18,7 +19,7 @@ exports.getHouseholdStores = async (req, res) => { res.json(stores); } catch (error) { console.error("Get household stores error:", error); - res.status(500).json({ error: "Failed to fetch household stores" }); + sendError(res, 500, "Failed to fetch household stores"); } }; @@ -28,11 +29,11 @@ exports.addStoreToHousehold = async (req, res) => { const { storeId, isDefault } = req.body; // console.log("Adding store to household:", { householdId: req.params.householdId, storeId, isDefault }); if (!storeId) { - return res.status(400).json({ error: "Store ID is required" }); + return sendError(res, 400, "Store ID is required"); } const store = await storeModel.getStoreById(storeId); - if (!store) return res.status(404).json({ error: "Store not found" }); + if (!store) return sendError(res, 404, "Store not found"); const foundStores = await storeModel.getHouseholdStores(req.params.householdId); // if (foundStores.length == 0) isDefault = 'true'; @@ -48,7 +49,7 @@ exports.addStoreToHousehold = async (req, res) => { }); } catch (error) { console.error("Add store to household error:", error); - res.status(500).json({ error: "Failed to add store to household" }); + sendError(res, 500, "Failed to add store to household"); } }; @@ -63,7 +64,7 @@ exports.removeStoreFromHousehold = async (req, res) => { res.json({ message: "Store removed from household successfully" }); } catch (error) { console.error("Remove store from household error:", error); - res.status(500).json({ error: "Failed to remove store from household" }); + sendError(res, 500, "Failed to remove store from household"); } }; @@ -78,7 +79,7 @@ exports.setDefaultStore = async (req, res) => { res.json({ message: "Default store updated successfully" }); } catch (error) { console.error("Set default store error:", error); - res.status(500).json({ error: "Failed to set default store" }); + sendError(res, 500, "Failed to set default store"); } }; @@ -88,7 +89,7 @@ exports.createStore = async (req, res) => { const { name, default_zones } = req.body; if (!name || name.trim().length === 0) { - return res.status(400).json({ error: "Store name is required" }); + return sendError(res, 400, "Store name is required"); } const store = await storeModel.createStore(name.trim(), default_zones || null); @@ -100,9 +101,9 @@ exports.createStore = async (req, res) => { } catch (error) { console.error("Create store error:", error); if (error.code === '23505') { // Unique violation - return res.status(400).json({ error: "Store with this name already exists" }); + return sendError(res, 400, "Store with this name already exists"); } - res.status(500).json({ error: "Failed to create store" }); + sendError(res, 500, "Failed to create store"); } }; @@ -117,7 +118,7 @@ exports.updateStore = async (req, res) => { }); if (!store) { - return res.status(404).json({ error: "Store not found" }); + return sendError(res, 404, "Store not found"); } res.json({ @@ -126,7 +127,7 @@ exports.updateStore = async (req, res) => { }); } catch (error) { console.error("Update store error:", error); - res.status(500).json({ error: "Failed to update store" }); + sendError(res, 500, "Failed to update store"); } }; @@ -138,8 +139,8 @@ exports.deleteStore = async (req, res) => { } catch (error) { console.error("Delete store error:", error); if (error.message.includes('in use')) { - return res.status(400).json({ error: error.message }); + return sendError(res, 400, error.message); } - res.status(500).json({ error: "Failed to delete store" }); + sendError(res, 500, "Failed to delete store"); } }; diff --git a/backend/controllers/users.controller.js b/backend/controllers/users.controller.js index 2633bd0..1031583 100644 --- a/backend/controllers/users.controller.js +++ b/backend/controllers/users.controller.js @@ -1,5 +1,6 @@ -const User = require("../models/user.model"); -const bcrypt = require("bcryptjs"); +const User = require("../models/user.model"); +const bcrypt = require("bcryptjs"); +const { sendError } = require("../utils/http"); exports.test = async (req, res) => { console.log("User route is working"); @@ -16,34 +17,34 @@ exports.updateUserRole = async (req, res) => { try { const { id, role } = req.body; - console.log(`Updating user ${id} to role ${role}`); - if (!Object.values(User.ROLES).includes(role)) - return res.status(400).json({ error: "Invalid role" }); - - const updated = await User.updateUserRole(id, role); - if (!updated) - return res.status(404).json({ error: "User not found" }); + console.log(`Updating user ${id} to role ${role}`); + if (!Object.values(User.ROLES).includes(role)) + return sendError(res, 400, "Invalid role"); + + const updated = await User.updateUserRole(id, role); + if (!updated) + return sendError(res, 404, "User not found"); res.json({ message: "Role updated", id, role }); - } catch (err) { - res.status(500).json({ error: "Failed to update role" }); - } -}; + } catch (err) { + sendError(res, 500, "Failed to update role"); + } +}; exports.deleteUser = async (req, res) => { try { const { id } = req.params; - const deleted = await User.deleteUser(id); - if (!deleted) - return res.status(404).json({ error: "User not found" }); + const deleted = await User.deleteUser(id); + if (!deleted) + return sendError(res, 404, "User not found"); res.json({ message: "User deleted", id }); - } catch (err) { - res.status(500).json({ error: "Failed to delete user" }); - } -}; + } catch (err) { + sendError(res, 500, "Failed to delete user"); + } +}; exports.checkIfUserExists = async (req, res) => { const { username } = req.query; @@ -56,42 +57,42 @@ exports.getCurrentUser = async (req, res) => { const userId = req.user.id; const user = await User.getUserById(userId); - if (!user) { - return res.status(404).json({ error: "User not found" }); - } + if (!user) { + return sendError(res, 404, "User not found"); + } res.json(user); - } catch (err) { - console.error("Error getting current user:", err); - res.status(500).json({ error: "Failed to get user profile" }); - } -}; + } catch (err) { + console.error("Error getting current user:", err); + sendError(res, 500, "Failed to get user profile"); + } +}; exports.updateCurrentUser = async (req, res) => { try { const userId = req.user.id; const { display_name } = req.body; - if (!display_name || display_name.trim().length === 0) { - return res.status(400).json({ error: "Display name is required" }); - } - - if (display_name.length > 100) { - return res.status(400).json({ error: "Display name must be 100 characters or less" }); - } + if (!display_name || display_name.trim().length === 0) { + return sendError(res, 400, "Display name is required"); + } + + if (display_name.length > 100) { + return sendError(res, 400, "Display name must be 100 characters or less"); + } const updated = await User.updateUserProfile(userId, { display_name: display_name.trim() }); - if (!updated) { - return res.status(404).json({ error: "User not found" }); - } + if (!updated) { + return sendError(res, 404, "User not found"); + } res.json({ message: "Profile updated successfully", user: updated }); - } catch (err) { - console.error("Error updating user profile:", err); - res.status(500).json({ error: "Failed to update profile" }); - } -}; + } catch (err) { + console.error("Error updating user profile:", err); + sendError(res, 500, "Failed to update profile"); + } +}; exports.changePassword = async (req, res) => { try { @@ -99,27 +100,27 @@ exports.changePassword = async (req, res) => { const { current_password, new_password } = req.body; // Validation - if (!current_password || !new_password) { - return res.status(400).json({ error: "Current password and new password are required" }); - } - - if (new_password.length < 6) { - return res.status(400).json({ error: "New password must be at least 6 characters" }); - } + if (!current_password || !new_password) { + return sendError(res, 400, "Current password and new password are required"); + } + + if (new_password.length < 6) { + return sendError(res, 400, "New password must be at least 6 characters"); + } // Get current password hash const currentHash = await User.getUserPasswordHash(userId); - if (!currentHash) { - return res.status(404).json({ error: "User not found" }); - } + if (!currentHash) { + return sendError(res, 404, "User not found"); + } // Verify current password const isValidPassword = await bcrypt.compare(current_password, currentHash); - if (!isValidPassword) { - return res.status(401).json({ error: "Current password is incorrect" }); - } + if (!isValidPassword) { + return sendError(res, 401, "Current password is incorrect"); + } // Hash new password const salt = await bcrypt.genSalt(10); @@ -129,8 +130,8 @@ exports.changePassword = async (req, res) => { await User.updateUserPassword(userId, hashedPassword); res.json({ message: "Password changed successfully" }); - } catch (err) { - console.error("Error changing password:", err); - res.status(500).json({ error: "Failed to change password" }); - } -}; + } catch (err) { + console.error("Error changing password:", err); + sendError(res, 500, "Failed to change password"); + } +};