Received product data: {
name: 'test',
description: 'test',
brand_id: 86,
has_color: true,
has_size: false,
product_type: 'color_size',
category_ids: [ 178 ],
colors: [
{
name: 'red',
images: [],
has_size: true,
sizes: [Array],
price: null,
qty: 0
}
],
sizes: [],
price: null,
qty: 0,
images: []
}
Inserting product with adjusted price: 0
Inserted product ID: 1213
Associating category ID 178 with product ID 1213
Inserted all category associations.
Inserted 0 product images.
Inserting color: red with has_size: true
Inserted color ID=50, has_size=true
Inserted 0 images for color ID=50
Inserting size: 20ml with price: 30000
Inserted size ID=16 for color ID=50
Inserted 0 images for size ID=16
New product to cache: {
id: 1213,
name: 'test',
description: 'test',
brand_id: 86,
has_color: true,
has_size: false,
product_type: 'color_size',
price: 0,
qty: 0,
sold: 0,
created_at: '2024-09-24T13:10:14.369Z',
updated_at: '2024-09-24T13:10:14.369Z',
categories: [
{ id: 178, name: '', main_category_id: 0, main_category_name: '' }
],
colors: [
{
id: 50,
name: 'red',
price: 0,
qty: 0,
sold: 0,
has_size: true,
sizes: [Array],
images: []
}
],
sizes: [],
images: []
}
Added new product to cache.

why has_size in colors became false in the cache???
{"id":1207,"name":"test","description":"test","brand_id":73,"has_color":true,"has_size":false,"product_type":"color_size","price":0,"qty":0,"sold":0,"created_at":"2024-09-24T11:58:58.539Z","updated_at":"2024-09-24T11:58:58.539Z","categories":[{"id":178,"name":"Body Cleansers","main_category_id":2,"main_category_name":"Body Care"}],"colors":[{"id":45,"name":"red","price":0,"qty":0,"sold":0,"has_size":false,"sizes":[],"images":[]}],"sizes":[{"id":10,"name":"20ml","price":20000,"qty":20,"sold":0,"color_id":45,"images":[]}],"images":[]}
backend:
export const createProduct = async (req: Request, res: Response) => {
try {
console.log("Received product data:", req.body); // Log incoming data
await query('BEGIN');
pgsql

    const productData: CreateProductInput = req.body;

    // Adjust price if necessary
    let adjustedPrice = productData.price;
    if (productData.price < 1000) {
        adjustedPrice = parseFloat((productData.price * 1000).toFixed(2));
    }

    console.log("Inserting product with adjusted price:", adjustedPrice);

    // Insert product
    const productResult = await query(
        `INSERT INTO products (name, description, brand_id, has_color, has_size, product_type, price, qty, sold, created_at, updated_at)
         VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
         RETURNING id, created_at, updated_at`,
        [
            productData.name,
            productData.description,
            productData.brand_id,
            Boolean(productData.has_color),
            Boolean(productData.has_size),
            productData.product_type,
            adjustedPrice,
            productData.qty
        ]
    );
    const productId = productResult.rows[0].id;
    console.log(`Inserted product ID: ${productId}`);

    // Insert category associations
    const categoryInsertPromises = productData.category_ids.map((categoryId: number) => {
        console.log(`Associating category ID ${categoryId} with product ID ${productId}`);
        return query(
            `INSERT INTO product_categories (product_id, category_id) 
             VALUES ($1, $2)`,
            [productId, categoryId]
        );
    });
    await Promise.all(categoryInsertPromises);
    console.log('Inserted all category associations.');

    // Insert images
    const imagesInsertPromises = productData.images.map((image: ImageInput) => {
        console.log(`Inserting product image: ${image.url}`);
        return query(
            `INSERT INTO images (product_id, url, color_id, size_id)
             VALUES ($1, $2, $3, $4)
             RETURNING id, color_id, size_id, url`,
            [productId, image.url, image.color_id || null, image.size_id || null]
        );
    });
    const insertedImagesResults = await Promise.all(imagesInsertPromises);
    const insertedImages = insertedImagesResults.map((imgResult: any) => ({
        id: imgResult.rows[0].id,
        url: imgResult.rows[0].url,
        color_id: imgResult.rows[0].color_id,
        size_id: imgResult.rows[0].size_id
    }));
    console.log(`Inserted ${insertedImages.length} product images.`);

    // Handle colors and sizes based on product type
    const insertedColors: number[] = [];
    const insertedSizes: number[] = [];

    if (productData.has_color && productData.colors) {
        for (const color of productData.colors) {
            // Adjust color price if necessary
            let adjustedColorPrice = color.price;
            if (color.price < 1000) {
                adjustedColorPrice = parseFloat((color.price * 1000).toFixed(2));
            }

            console.log(`Inserting color: ${color.name} with has_size: ${color.has_size}`);

            // Insert color with explicit boolean casting
            const colorResult = await query(
                `INSERT INTO colors (product_id, name, price, qty, sold, has_size)
                 VALUES ($1, $2, $3, $4, 0, $5)
                 RETURNING id, has_size`,
                [productId, color.name, adjustedColorPrice, color.qty, Boolean(color.has_size)]
            );
            const colorId = colorResult.rows[0].id;
            const colorHasSize = colorResult.rows[0].has_size;
            console.log(`Inserted color ID=${colorId}, has_size=${colorHasSize}`);

            insertedColors.push(colorId);

            // Insert color images
            const colorImagesInsertPromises = color.images.map((image: ImageInput) => {
                console.log(`Inserting color image: ${image.url}`);
                return query(
                    `INSERT INTO images (product_id, color_id, url, size_id)
                     VALUES ($1, $2, $3, $4)
                     RETURNING id, color_id, size_id, url`,
                    [productId, colorId, image.url, image.size_id || null]
                );
            });
            const insertedColorImagesResults = await Promise.all(colorImagesInsertPromises);
            console.log(`Inserted ${insertedColorImagesResults.length} images for color ID=${colorId}`);

            if (color.has_size && color.sizes) {
                for (const size of color.sizes) {
                    // Adjust size price if necessary
                    let adjustedSizePrice = size.price;
                    if (size.price < 1000) {
                        adjustedSizePrice = parseFloat((size.price * 1000).toFixed(2));
                    }

                    console.log(`Inserting size: ${size.name} with price: ${adjustedSizePrice}`);

                    // Insert size
                    const sizeResult = await query(
                        `INSERT INTO sizes (product_id, color_id, name, price, qty, sold)
                         VALUES ($1, $2, $3, $4, $5, 0)
                         RETURNING id`,
                        [productId, colorId, size.name, adjustedSizePrice, size.qty]
                    );
                    const sizeId = sizeResult.rows[0].id;
                    console.log(`Inserted size ID=${sizeId} for color ID=${colorId}`);
                    insertedSizes.push(sizeId);

                    // Insert size images
                    const sizeImagesInsertPromises = size.images.map((image: ImageInput) => {
                        console.log(`Inserting size image: ${image.url}`);
                        return query(
                            `INSERT INTO images (product_id, color_id, size_id, url)
                             VALUES ($1, $2, $3, $4)
                             RETURNING id, color_id, size_id, url`,
                            [productId, colorId, sizeId, image.url]
                        );
                    });
                    const insertedSizeImagesResults = await Promise.all(sizeImagesInsertPromises);
                    console.log(`Inserted ${insertedSizeImagesResults.length} images for size ID=${sizeId}`);
                }
            }
        }
    } else if (productData.has_size && productData.sizes) {
        for (const size of productData.sizes) {
            // Adjust size price if necessary
            let adjustedSizePrice = size.price;
            if (size.price < 1000) {
                adjustedSizePrice = parseFloat((size.price * 1000).toFixed(2));
            }

            console.log(`Inserting standalone size: ${size.name} with price: ${adjustedSizePrice}`);

            // Insert size
            const sizeResult = await query(
                `INSERT INTO sizes (product_id, name, price, qty, sold)
                 VALUES ($1, $2, $3, $4, 0)
                 RETURNING id`,
                [productId, size.name, adjustedSizePrice, size.qty]
            );
            const sizeId = sizeResult.rows[0].id;
            console.log(`Inserted standalone size ID=${sizeId}`);
            insertedSizes.push(sizeId);

            // Insert size images
            const sizeImagesInsertPromises = size.images.map((image: ImageInput) => {
                console.log(`Inserting standalone size image: ${image.url}`);
                return query(
                    `INSERT INTO images (product_id, size_id, url)
                     VALUES ($1, $2, $3)
                     RETURNING id, color_id, size_id, url`,
                    [productId, sizeId, image.url]
                );
            });
            const insertedSizeImagesResults = await Promise.all(sizeImagesInsertPromises);
            console.log(`Inserted ${insertedSizeImagesResults.length} images for standalone size ID=${sizeId}`);
        }
    }

    // Construct the new product object as it would appear in the cache
    const newProduct: Product = {
        id: productId,
        name: productData.name,
        description: productData.description,
        brand_id: productData.brand_id,
        has_color: productData.has_color,
        has_size: productData.has_size,
        product_type: productData.product_type,
        price: adjustedPrice,
        qty: productData.qty,
        sold: 0,
        created_at: productResult.rows[0].created_at.toISOString(),
        updated_at: productResult.rows[0].updated_at.toISOString(),
        categories: productData.category_ids.map((cid: number) => ({
            id: cid,
            name: "", // Optional: Fetch and populate category names
            main_category_id: 0, // Optional: Set appropriately
            main_category_name: "" // Optional: Set appropriately
        })),
        colors: productData.has_color && productData.colors ? productData.colors.map((color, index) => ({
            id: insertedColors[index],
            name: color.name,
            price: color.price || 0,
            qty: color.qty,
            sold: 0,
            has_size: Boolean(color.has_size),
            sizes: Boolean(color.has_size) && color.sizes ? color.sizes.map((size, sIndex) => ({
                id: insertedSizes[sIndex],
                name: size.name,
                price: size.price,
                qty: size.qty,
                sold: 0,
                color_id: null, // Optional: Set if needed
                images: size.images.map(img => img.url)
            })) : [],
            images: color.images.map(img => img.url)
        })) : [],
        sizes: productData.has_size && !productData.has_color && productData.sizes ? productData.sizes.map((size, index) => ({
            id: insertedSizes[index],
            name: size.name,
            price: size.price,
            qty: size.qty,
            sold: 0,
            color_id: null,
            images: size.images.map(img => img.url)
        })) : [],
        images: insertedImages
    };

    console.log("New product to cache:", newProduct); // Log new product

    // Update the cache
    addProductToCache(newProduct);

    await query('COMMIT');
    res.status(201).json({ message: 'Product added successfully!', productId });
} catch (error) {
    await query('ROLLBACK');
    console.error('Error creating product:', error);
    res.status(500).json({ error: 'Internal server error' });
}

};

cache:
// src/cacheHelpers.ts

import { productCache } from "./cache";
import {Product, CacheData, Color, Size} from "product";

/**

    Adds a new product to the cache.
    */
    export const addProductToCache = (newProduct: Product) => {
    try {
    const cacheData: CacheData | undefined = productCache.get('allProducts');
    if (cacheData) {
    cacheData.allProducts.push(newProduct);
    stylus

     // Extract all prices
     const productPrices: number[] = [];
     productPrices.push(newProduct.price);

     newProduct.colors.forEach((color: Color) => {
         productPrices.push(color.price);
         color.sizes.forEach((size: Size) => productPrices.push(size.price));
     });

     newProduct.sizes.forEach((size: Size) => productPrices.push(size.price));

     // Update minPrice and maxPrice
     if (productPrices.length > 0) {
         const productMinPrice = Math.min(...productPrices, cacheData.minPrice);
         const productMaxPrice = Math.max(...productPrices, cacheData.maxPrice);
         cacheData.minPrice = Math.min(cacheData.minPrice, productMinPrice);
         cacheData.maxPrice = Math.max(cacheData.maxPrice, productMaxPrice);
     }

     productCache.set('allProducts', cacheData);
     console.log('Added new product to cache.');
 } else {
     // Flush cache to force reload on next access
     productCache.del('allProducts');
     console.log('Cache unavailable. Flushed product cache.');
 }

} catch (error) {
console.error('Error adding product to cache:', error);
productCache.del('allProducts');
}
};
/**

Updates an existing product in the cache.
*/
export const updateProductInCache = (updatedProduct: Product) => {
try {
const cacheData: CacheData | undefined = productCache.get('allProducts');
if (cacheData) {
const index = cacheData.allProducts.findIndex(p => p.id === updatedProduct.id);
if (index !== -1) {
cacheData.allProducts[index] = updatedProduct;
ini

             // Recalculate minPrice and maxPrice
             const allPrices: number[] = [];
             cacheData.allProducts.forEach(product => {
                 allPrices.push(product.price);
                 product.colors.forEach(color => {
                     allPrices.push(color.price);
                     color.sizes.forEach(size => allPrices.push(size.price));
                 });
                 product.sizes.forEach(size => allPrices.push(size.price));
             });

             if (allPrices.length > 0) {
                 cacheData.minPrice = Math.min(...allPrices);
                 cacheData.maxPrice = Math.max(...allPrices);
             } else {
                 cacheData.minPrice = 0;
                 cacheData.maxPrice = 0;
             }

             productCache.set('allProducts', cacheData);
             console.log('Updated product in cache.');
         } else {
             // Product not found; flush cache
             productCache.del('allProducts');
             console.log('Product not found in cache. Flushed product cache.');
         }
     } else {
         // Flush cache to force reload
         productCache.del('allProducts');
         console.log('Cache unavailable. Flushed product cache.');
     }

    } catch (error) {
    console.error('Error updating product in cache:', error);
    productCache.del('allProducts');
    }
    };

/**

    Deletes a product from the cache.
    */
    export const deleteProductFromCache = (productId: number) => {
    try {
    const cacheData: CacheData | undefined = productCache.get('allProducts');
    if (cacheData) {
    const initialLength = cacheData.allProducts.length;
    cacheData.allProducts = cacheData.allProducts.filter(p => p.id !== productId);
    if (cacheData.allProducts.length < initialLength) {
    // Recalculate minPrice and maxPrice
    const allPrices: number[] = [];
    cacheData.allProducts.forEach(product => {
    allPrices.push(product.price);
    product.colors.forEach(color => {
    allPrices.push(color.price);
    color.sizes.forEach(size => allPrices.push(size.price));
    });
    product.sizes.forEach(size => allPrices.push(size.price));
    });
    stylus

             if (allPrices.length > 0) {
                 cacheData.minPrice = Math.min(...allPrices);
                 cacheData.maxPrice = Math.max(...allPrices);
             } else {
                 cacheData.minPrice = 0;
                 cacheData.maxPrice = 0;
             }

             productCache.set('allProducts', cacheData);
             console.log('Deleted product from cache.');
         } else {
             // Product not found; flush cache
             productCache.del('allProducts');
             console.log('Product not found in cache. Flushed product cache.');
         }
     } else {
         // Flush cache to force reload
         productCache.del('allProducts');
         console.log('Cache unavailable. Flushed product cache.');
     }

    } catch (error) {
    console.error('Error deleting product from cache:', error);
    productCache.del('allProducts');
    }
    };

/**

    Decreases product, size, or color quantity in the cache based on identifiers.

    @param productId - ID of the product

    @param sizeId - ID of the size (optional)

    @param colorId - ID of the color (optional)

    @param quantity - Quantity to decrease
    */
    export const decreaseProductQtyInCache = (productId: number, sizeId: number | null, colorId: number | null, quantity: number) => {
    try {
    const cacheData: CacheData | undefined = productCache.get('allProducts');
    if (!cacheData) {
    // Cache is unavailable; nothing to update
    return;
    }
    ebnf

 const product = cacheData.allProducts.find(p => p.id === productId);
 if (!product) {
     // Product not found in cache; nothing to update
     return;
 }

 if (sizeId) {
     // Decrease quantity in the specific size
     const size = product.sizes.find(s => s.id === sizeId);
     if (size) {
         size.qty = size.qty - quantity;
         if (size.qty < 0) size.qty = 0; // Prevent negative stock
     }

     // Also, if color_id is present, update the associated color's size
     if (colorId) {
         const color = product.colors.find(c => c.id === colorId);
         if (color) {
             const colorSize = color.sizes.find(s => s.id === sizeId);
             if (colorSize) {
                 colorSize.qty = colorSize.qty - quantity;
                 if (colorSize.qty < 0) colorSize.qty = 0;
             }
         }
     }
 } else if (colorId) {
     // Decrease quantity in the specific color
     const color = product.colors.find(c => c.id === colorId);
     if (color) {
         color.qty = color.qty - quantity;
         if (color.qty < 0) color.qty = 0; // Prevent negative stock
     }
 } else {
     // Decrease quantity in the product itself
     product.qty = product.qty - quantity;
     if (product.qty < 0) product.qty = 0; // Prevent negative stock
 }

 // Update the cache
 productCache.set('allProducts', cacheData);
 console.log('Decreased product quantity in cache.');

} catch (error) {
console.error('Error decreasing product quantity in cache:', error);
// Decide whether to flush the cache or handle the error differently
productCache.del('allProducts');
}
};

The issue is in the cache...
types.ts:
// src/types/productInput.ts

export interface ImageInput {
    url: string;
    color_id?: number | null;
    size_id?: number | null;
}

export interface SizeInput {
    id?: number; // Optional for creation
    name: string;
    price: number; // Changed from string to number
    qty: number;
    sold?: number;
    color_id?: number | null;
    images: ImageInput[];
}

export interface ColorInput {
    id?: number; // Optional for creation
    name: string;
    price: number; // Changed from string to number
    qty: number;
    sold?: number;
    has_size: boolean;
    sizes: SizeInput[];
    images: ImageInput[];
}

export interface CreateProductInput {
    name: string;
    description: string;
    brand_id: number;
    has_color: boolean;
    has_size: boolean;
    product_type: string;
    price: number; // Changed from string to number
    qty: number;
    category_ids: number[];
    images: ImageInput[];
    colors?: ColorInput[]; // Optional based on has_color
    sizes?: SizeInput[]; // Optional based on has_size and has_color
}

export interface UpdateProductInput extends CreateProductInput {}

export interface Category {
    id: number;
    name: string;
    main_category_id: number;
    main_category_name: string;
}

export interface Image {
    id: number;
    url: string;
    color_id: number | null;
    size_id: number | null;
}

export interface Size {
    id: number;
    name: string;
    price: number; // Changed from string to number for arithmetic operations
    qty: number;
    sold: number;
    color_id: number | null;
    images: string[];
}

export interface Color {
    id: number;
    name: string;
    price: number; // Changed from string to number
    qty: number;
    sold: number;
    has_size: boolean; // Mandatory property
    sizes: Size[];
    images: string[];
}

export interface Product {
    id: number;
    name: string;
    description: string;
    brand_id: number; // Also a subcategory_id for brands
    has_color: boolean;
    has_size: boolean;
    product_type: string;
    price: number; // Changed from string to number
    qty: number;
    sold: number;
    created_at: string;
    updated_at: string;
    categories: Category[];
    colors: Color[];
    sizes: Size[];
    images: Image[];
}

export interface Brand {
    id: number;
    name: string;
    category_id: number;
    category_name: string;
}

export interface CacheData {
    allProducts: Product[];
    minPrice: number;
    maxPrice: number;
}

export interface BrandsCacheData {
    allBrands: Brand[];
}