diff --git a/backend/src/routes/products.js b/backend/src/routes/products.js index 03c7cd8..645c8fc 100644 --- a/backend/src/routes/products.js +++ b/backend/src/routes/products.js @@ -82,10 +82,12 @@ router.get('/', async (req, res, next) => { // Get user's custom products const userProductsQuery = ` - SELECT up.*, p.name as base_product_name, p.brand, p.product_type, pc.name as category_name + SELECT up.*, p.name as base_product_name, p.brand as base_brand, p.product_type as base_product_type, + pc.name as category_name, upc.name as custom_category_name FROM user_products up LEFT JOIN products p ON up.product_id = p.id LEFT JOIN product_categories pc ON p.category_id = pc.id + LEFT JOIN product_categories upc ON up.category_id = upc.id WHERE up.user_id = $1 ORDER BY COALESCE(up.custom_name, p.name) `; @@ -112,14 +114,23 @@ router.get('/', async (req, res, next) => { baseProductId: product.product_id, baseProductName: product.base_product_name, customName: product.custom_name, - brand: product.brand, - categoryName: product.category_name, - productType: product.product_type, + // Use custom fields if available, otherwise fall back to base product + brand: product.custom_brand || product.base_brand, + customBrand: product.custom_brand, + categoryId: product.category_id, + categoryName: product.custom_category_name || product.category_name, + productType: product.custom_product_type || product.base_product_type, + customProductType: product.custom_product_type, + activeIngredients: product.custom_active_ingredients, + customActiveIngredients: product.custom_active_ingredients, + description: product.custom_description, + customDescription: product.custom_description, customRateAmount: parseFloat(product.custom_rate_amount), customRateUnit: product.custom_rate_unit, notes: product.notes, isShared: false, - createdAt: product.created_at + createdAt: product.created_at, + updatedAt: product.updated_at })) } }); @@ -292,7 +303,19 @@ router.post('/:id/rates', validateParams(idParamSchema), validateRequest(product // @access Private router.post('/user', validateRequest(userProductSchema), async (req, res, next) => { try { - const { productId, customName, customRateAmount, customRateUnit, notes } = req.body; + const { + productId, + customName, + customRateAmount, + customRateUnit, + notes, + // Advanced fields for creating custom products + brand, + categoryId, + productType, + activeIngredients, + description + } = req.body; // If based on existing product, verify it exists if (productId) { @@ -312,10 +335,13 @@ router.post('/user', validateRequest(userProductSchema), async (req, res, next) } const result = await pool.query( - `INSERT INTO user_products (user_id, product_id, custom_name, custom_rate_amount, custom_rate_unit, notes) - VALUES ($1, $2, $3, $4, $5, $6) + `INSERT INTO user_products (user_id, product_id, custom_name, custom_brand, category_id, + custom_product_type, custom_active_ingredients, custom_description, + custom_rate_amount, custom_rate_unit, notes) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING *`, - [req.user.id, productId, customName, customRateAmount, customRateUnit, notes] + [req.user.id, productId, customName, brand, categoryId, productType, activeIngredients, + description, customRateAmount, customRateUnit, notes] ); const userProduct = result.rows[0]; @@ -328,10 +354,16 @@ router.post('/user', validateRequest(userProductSchema), async (req, res, next) id: userProduct.id, baseProductId: userProduct.product_id, customName: userProduct.custom_name, + customBrand: userProduct.custom_brand, + categoryId: userProduct.category_id, + customProductType: userProduct.custom_product_type, + customActiveIngredients: userProduct.custom_active_ingredients, + customDescription: userProduct.custom_description, customRateAmount: parseFloat(userProduct.custom_rate_amount), customRateUnit: userProduct.custom_rate_unit, notes: userProduct.notes, - createdAt: userProduct.created_at + createdAt: userProduct.created_at, + updatedAt: userProduct.updated_at } } }); @@ -393,7 +425,25 @@ router.get('/user/:id', validateParams(idParamSchema), async (req, res, next) => router.put('/user/:id', validateParams(idParamSchema), validateRequest(userProductSchema), async (req, res, next) => { try { const userProductId = req.params.id; - const { productId, customName, customRateAmount, customRateUnit, notes } = req.body; + const { + productId, + customName, + customRateAmount, + customRateUnit, + notes, + isAdvancedEdit, + // Advanced edit fields (will be ignored for now as schema doesn't support them) + brand, + categoryId, + productType, + activeIngredients, + description + } = req.body; + + // Log warning if advanced edit is attempted (not fully supported yet) + if (isAdvancedEdit) { + console.warn(`Advanced edit attempted for user product ${userProductId}. Only basic fields will be updated.`); + } // Check if user product exists and belongs to user const checkResult = await pool.query( @@ -419,11 +469,24 @@ router.put('/user/:id', validateParams(idParamSchema), validateRequest(userProdu const result = await pool.query( `UPDATE user_products - SET product_id = $1, custom_name = $2, custom_rate_amount = $3, - custom_rate_unit = $4, notes = $5, updated_at = CURRENT_TIMESTAMP - WHERE id = $6 + SET product_id = $1, custom_name = $2, custom_brand = $3, category_id = $4, + custom_product_type = $5, custom_active_ingredients = $6, custom_description = $7, + custom_rate_amount = $8, custom_rate_unit = $9, notes = $10, updated_at = CURRENT_TIMESTAMP + WHERE id = $11 RETURNING *`, - [productId, customName, customRateAmount, customRateUnit, notes, userProductId] + [ + productId, + customName, + brand, + categoryId, + productType, + activeIngredients, + description, + customRateAmount, + customRateUnit, + notes, + userProductId + ] ); const userProduct = result.rows[0]; @@ -436,6 +499,11 @@ router.put('/user/:id', validateParams(idParamSchema), validateRequest(userProdu id: userProduct.id, baseProductId: userProduct.product_id, customName: userProduct.custom_name, + customBrand: userProduct.custom_brand, + categoryId: userProduct.category_id, + customProductType: userProduct.custom_product_type, + customActiveIngredients: userProduct.custom_active_ingredients, + customDescription: userProduct.custom_description, customRateAmount: parseFloat(userProduct.custom_rate_amount), customRateUnit: userProduct.custom_rate_unit, notes: userProduct.notes, diff --git a/database/init.sql b/database/init.sql index 513fd67..2872857 100644 --- a/database/init.sql +++ b/database/init.sql @@ -100,6 +100,11 @@ CREATE TABLE user_products ( user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, product_id INTEGER REFERENCES products(id), custom_name VARCHAR(255), + custom_brand VARCHAR(100), + category_id INTEGER REFERENCES product_categories(id), + custom_product_type VARCHAR(50) CHECK (custom_product_type IN ('granular', 'liquid')), + custom_active_ingredients TEXT, + custom_description TEXT, custom_rate_amount DECIMAL(8, 4), custom_rate_unit VARCHAR(50), notes TEXT, diff --git a/database/migrations/add_advanced_fields_to_user_products.sql b/database/migrations/add_advanced_fields_to_user_products.sql new file mode 100644 index 0000000..20bebb2 --- /dev/null +++ b/database/migrations/add_advanced_fields_to_user_products.sql @@ -0,0 +1,10 @@ +-- Migration: Add advanced fields to user_products table +-- This allows users to have fully customizable products with their own brand, category, etc. + +-- Add the advanced fields +ALTER TABLE user_products +ADD COLUMN custom_brand VARCHAR(100), +ADD COLUMN category_id INTEGER REFERENCES product_categories(id), +ADD COLUMN custom_product_type VARCHAR(50) CHECK (custom_product_type IN ('granular', 'liquid')), +ADD COLUMN custom_active_ingredients TEXT, +ADD COLUMN custom_description TEXT; \ No newline at end of file