Create a postgresql should_update_product table that have just one row value either true or false... Give me the query...
I have two differents backend, backend for admin and backend for customers they are in separated projects.
the backend for admin has update, create and delete:
export const createProduct = async (req: Request, res: Response) => {
    try {
        await query('BEGIN');

        const productData = req.body;

        if (productData.price < 1000) {
            productData.price *= 1000;
        }

        const productResult = await query(
            'INSERT INTO products (name, description, brand_id, has_color, has_size, product_type, price, qty, sold) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id',
            [productData.name, productData.description, productData.brand_id, productData.hasColor, productData.hasSize, productData.product_type, productData.price, productData.qty, 0]
        );
        const productId = productResult.rows[0].id;

        // Insert category associations
        for (const categoryId of productData.category_ids) {
            await query(
                'INSERT INTO product_categories (product_id, category_id) VALUES ($1, $2)',
                [productId, categoryId]
            );
        }

        // Insert images
        for (const imageUrl of productData.images) {
            await query(
                'INSERT INTO images (product_id, url) VALUES ($1, $2)',
                [productId, imageUrl]
            );
        }

        // Handle colors and sizes based on product type
        if (productData.hasColor) {
            for (const color of productData.colors) {
                // Adjust color price if less than 1000
                if (color.price < 1000) {
                    color.price *= 1000;
                }

                const colorResult = await query(
                    'INSERT INTO colors (product_id, name, has_size, price, qty, sold) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id',
                    [productId, color.name, color.hasSize, color.price, color.qty, 0]
                );
                const colorId = colorResult.rows[0].id;

                // Insert color images
                for (const imageUrl of color.images) {
                    await query(
                        'INSERT INTO images (product_id, color_id, url) VALUES ($1, $2, $3)',
                        [productId, colorId, imageUrl]
                    );
                }

                if (color.hasSize) {
                    for (const size of color.sizes) {
                        // Adjust size price if less than 1000
                        if (size.price < 1000) {
                            size.price *= 1000;
                        }

                        const sizeResult = await query(
                            'INSERT INTO sizes (product_id, color_id, name, price, qty, sold) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id',
                            [productId, colorId, size.name, size.price, size.qty, 0]
                        );
                        const sizeId = sizeResult.rows[0].id;

                        // Insert size images
                        for (const imageUrl of size.images) {
                            await query(
                                'INSERT INTO images (product_id, color_id, size_id, url) VALUES ($1, $2, $3, $4)',
                                [productId, colorId, sizeId, imageUrl]
                            );
                        }
                    }
                }
            }
        } else if (productData.hasSize) {
            for (const size of productData.sizes) {
                // Adjust size price if less than 1000
                if (size.price < 1000) {
                    size.price *= 1000;
                }

                const sizeResult = await query(
                    'INSERT INTO sizes (product_id, name, price, qty, sold) VALUES ($1, $2, $3, $4, $5) RETURNING id',
                    [productId, size.name, size.price, size.qty, 0]
                );
                const sizeId = sizeResult.rows[0].id;

                // Insert size images
                for (const imageUrl of size.images) {
                    await query(
                        'INSERT INTO images (product_id, size_id, url) VALUES ($1, $2, $3)',
                        [productId, sizeId, imageUrl]
                    );
                }
            }
        }
        await query('COMMIT');
        res.status(201).json({ message: 'Product created successfully', productId });
    } catch (error) {
        await query('ROLLBACK');
        console.error('Error creating product:', error);
        res.status(500).json({ error: 'Internal server error' });
    } finally {
    }
};

// Update a product
export const updateProduct = async (req: Request, res: Response) => {
    const { id } = req.params;
    const productData = req.body;

    try {
        await query('BEGIN');

        // Adjust main product price if less than 1000
        if (productData.price < 1000) {
            productData.price *= 1000;
        }

        // Update main product details
        const result = await query(
            'UPDATE products SET name = $1, description = $2, brand_id = $3, has_color = $4, has_size = $5, product_type = $6, price = $7, qty = $8, updated_at = CURRENT_TIMESTAMP WHERE id = $9 RETURNING *',
            [productData.name, productData.description, productData.brand_id, productData.hasColor, productData.hasSize, productData.product_type, productData.price, productData.qty, id]
        );

        if (result.rows.length === 0) {
            await query('ROLLBACK');
            return res.status(404).json({ error: 'Product not found' });
        }

        // Delete existing associations and data
        await query('DELETE FROM product_categories WHERE product_id = $1', [id]);
        await query('DELETE FROM images WHERE product_id = $1', [id]);

        // Get existing colors and sizes
        const existingColors = await query('SELECT id FROM colors WHERE product_id = $1', [id]);
        const existingColorIds = new Set(existingColors.rows.map(row => row.id));

        const existingSizes = await query('SELECT id FROM sizes WHERE product_id = $1', [id]);
        const existingSizeIds = new Set(existingSizes.rows.map(row => row.id));

        // Determine colors and sizes to delete
        const updatedColorIds = new Set(productData.colors.map((color: { id: any; }) => color.id).filter((id: any) => id !== undefined));
        const colorsToDelete = [...existingColorIds].filter(id => !updatedColorIds.has(id));

        const updatedSizeIds = new Set([
            ...productData.sizes.map((size: { id: any; }) => size.id).filter((id: any) => id !== undefined),
            ...productData.colors.flatMap((color: { sizes: any[]; }) => color.sizes.map(size => size.id).filter(id => id !== undefined))
        ]);
        const sizesToDelete = [...existingSizeIds].filter(id => !updatedSizeIds.has(id));

        // Handle cart conflicts by deleting cart items associated with sizes and colors to be deleted


        // Delete colors and associated sizes
        for (const colorId of colorsToDelete) {
            await query('DELETE FROM sizes WHERE color_id = $1', [colorId]);
            await query('DELETE FROM images WHERE color_id = $1', [colorId]);
            await query('DELETE FROM colors WHERE id = $1', [colorId]);
        }

        // Delete sizes
        for (const sizeId of sizesToDelete) {
            await query('DELETE FROM images WHERE size_id = $1', [sizeId]);
            await query('DELETE FROM sizes WHERE id = $1', [sizeId]);
        }

        // Insert category associations
        for (const categoryId of productData.category_ids) {
            await query(
                'INSERT INTO product_categories (product_id, category_id) VALUES ($1, $2)',
                [id, categoryId]
            );
        }

        // Insert images
        for (const imageUrl of productData.images) {
            await query(
                'INSERT INTO images (product_id, url) VALUES ($1, $2)',
                [id, imageUrl]
            );
        }

        // Handle colors and sizes based on product type
        if (productData.hasColor) {
            for (const color of productData.colors) {
                // Adjust color price if less than 1000
                if (color.price < 1000) {
                    color.price *= 1000;
                }

                let colorId;
                if (color.id) {
                    // Update existing color
                    await query(
                        'UPDATE colors SET name = $1, has_size = $2, price = $3, qty = $4, sold = $5 WHERE id = $6',
                        [color.name, color.hasSize, color.price, color.qty, color.sold || 0, color.id]
                    );
                    colorId = color.id;
                } else {
                    // Insert new color
                    const colorResult = await query(
                        'INSERT INTO colors (product_id, name, has_size, price, qty, sold) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id',
                        [id, color.name, color.hasSize, color.price, color.qty, 0]
                    );
                    colorId = colorResult.rows[0].id;
                }

                if (color.images && color.images.length > 0) {
                    for (const imageUrl of color.images) {
                        await query(
                            'INSERT INTO images (product_id, color_id, url) VALUES ($1, $2, $3)',
                            [id, colorId, imageUrl]
                        );
                    }
                }

                if (color.hasSize) {
                    for (const size of color.sizes) {
                        // Adjust size price if less than 1000
                        if (size.price < 1000) {
                            size.price *= 1000;
                        }

                        let sizeId;
                        if (size.id) {
                            // Update existing size
                            await query(
                                'UPDATE sizes SET name = $1, price = $2, qty = $3, sold = $4 WHERE id = $5',
                                [size.name, size.price, size.qty, size.sold || 0, size.id]
                            );
                            sizeId = size.id;
                        } else {
                            // Insert new size
                            const sizeResult = await query(
                                'INSERT INTO sizes (product_id, color_id, name, price, qty, sold) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id',
                                [id, colorId, size.name, size.price, size.qty, 0]
                            );
                            sizeId = sizeResult.rows[0].id;
                        }

                        // Insert size images
                        if (size.images && size.images.length > 0) {
                            for (const imageUrl of size.images) {
                                await query(
                                    'INSERT INTO images (product_id, color_id, size_id, url) VALUES ($1, $2, $3, $4)',
                                    [id, colorId, sizeId, imageUrl]
                                );
                            }
                        }
                    }
                }
            }
        } else if (productData.hasSize) {
            for (const size of productData.sizes) {
                // Adjust size price if less than 1000
                if (size.price < 1000) {
                    size.price *= 1000;
                }

                let sizeId;
                if (size.id) {
                    // Update existing size
                    await query(
                        'UPDATE sizes SET name = $1, price = $2, qty = $3, sold = $4 WHERE id = $5',
                        [size.name, size.price, size.qty, size.sold || 0, size.id]
                    );
                    sizeId = size.id;
                } else {
                    // Insert new size
                    const sizeResult = await query(
                        'INSERT INTO sizes (product_id, name, price, qty, sold) VALUES ($1, $2, $3, $4, $5) RETURNING id',
                        [id, size.name, size.price, size.qty, 0]
                    );
                    sizeId = sizeResult.rows[0].id;
                }

                // Insert size images
                if (size.images && size.images.length > 0) {
                    for (const imageUrl of size.images) {
                        await query(
                            'INSERT INTO images (product_id, size_id, url) VALUES ($1, $2, $3)',
                            [id, sizeId, imageUrl]
                        );
                    }
                }
            }
        }
        await query('COMMIT');
        res.json({ message: 'Product updated successfully', product: result.rows[0] });
    } catch (error) {
        await query('ROLLBACK');
        console.error('Error updating product:', error);
        res.status(500).json({ error: 'Internal server error' });
    }
};
export const deleteProduct = async (req: Request, res: Response) => {
    const { id } = req.params;
    try {
        await query('BEGIN');

        // Delete cart items associated with this product
        // Delete related data first
        await query('DELETE FROM product_categories WHERE product_id = $1', [id]);
        await query('DELETE FROM images WHERE product_id = $1', [id]);
        await query('DELETE FROM sizes WHERE product_id = $1', [id]);
        await query('DELETE FROM colors WHERE product_id = $1', [id]);

        // Delete the product
        const result = await query('DELETE FROM products WHERE id = $1 RETURNING *', [id]);

        if (result.rows.length === 0) {
            await query('ROLLBACK');
            return res.status(404).json({ error: 'Product not found' });
        }

        await query('COMMIT');
        res.json({ message: 'Product and related data deleted successfully' });
    } catch (error) {
        await query('ROLLBACK');
        console.error('Error deleting product:', error);
        res.status(500).json({ error: 'Internal server error' });
    }
};
The backend for customers has getAllProducts and getProductById:
export const getProductById = async (req: Request, res: Response) => {
    const { id } = req.params;
    try {
        const productQuery = `
            SELECT 
                p.id AS product_id,
                p.name AS product_name,
                p.description,
                p.brand_id,
                p.has_color,
                p.has_size,
                p.product_type,
                p.price AS product_price,
                p.qty AS product_qty,
                p.sold AS product_sold,
                p.created_at,
                p.updated_at,
                pc.category_id,
                sc.name AS category_name,
                cl.id AS color_id,
                cl.name AS color_name,
                cl.price AS color_price,
                cl.qty AS color_qty,
                cl.sold AS color_sold,
                s.id AS size_id,
                s.name AS size_name,
                s.price AS size_price,
                s.qty AS size_qty,
                s.sold AS size_sold,
                i.id AS image_id,
                i.url AS image_url
            FROM products p
            LEFT JOIN product_categories pc ON p.id = pc.product_id
            LEFT JOIN subcategories sc ON pc.category_id = sc.id
            LEFT JOIN colors cl ON p.id = cl.product_id
            LEFT JOIN sizes s ON (p.id = s.product_id AND (cl.id = s.color_id OR s.color_id IS NULL))
            LEFT JOIN images i ON (p.id = i.product_id AND (cl.id = i.color_id OR i.color_id IS NULL) AND (s.id = i.size_id OR i.size_id IS NULL))
            WHERE p.id = $1
            ORDER BY pc.category_id, cl.id, s.id, i.id
        `;

        const result = await query(productQuery, [id]);

        if (result.rows.length === 0) {
            return res.status(404).json({ error: 'Product not found' });
        }

        // Process the result to group related data
        let product: {
            sold: any;
            has_size: any;
            images: any[];
            description: any;
            created_at: any;
            colors: any[];
            brand_id: any;
            has_color: any;
            product_type: any;
            updated_at: any;
            sizes: any[];
            price: any;
            qty: any;
            name: any;
            id: number;
            categories: any[]
        };
        product = {
            id: result.rows[0].product_id,
            name: result.rows[0].product_name,
            description: result.rows[0].description,
            brand_id: result.rows[0].brand_id,
            has_color: result.rows[0].has_color,
            has_size: result.rows[0].has_size,
            product_type: result.rows[0].product_type,
            price: result.rows[0].product_price,
            qty: result.rows[0].product_qty,
            sold: result.rows[0].product_sold,
            created_at: result.rows[0].created_at,
            updated_at: result.rows[0].updated_at,
            categories: [],
            colors: [],
            sizes: [],
            images: []
        };

        for (const row of result.rows) {
            if (row.category_id && !product.categories.some(c => c.id === row.category_id)) {
                product.categories.push({
                    id: row.category_id,
                    name: row.category_name
                });
            }

            // Add color if not already added
            if (row.color_id && !product.colors.some(c => c.id === row.color_id)) {
                product.colors.push({
                    id: row.color_id,
                    name: row.color_name,
                    price: row.color_price,
                    qty: row.color_qty,
                    sold: row.color_sold,
                    images: [],
                    sizes: []
                });
            }

            // Add size if not already added
            if (row.size_id && !product.sizes.some(s => s.id === row.size_id)) {
                const newSize = {
                    id: row.size_id,
                    name: row.size_name,
                    price: row.size_price,
                    qty: row.size_qty,
                    sold: row.size_sold,
                    color_id: row.color_id,
                    // @ts-ignore
                    images: []
                };
                product.sizes.push(newSize);

                // Add size to the corresponding color
                if (row.color_id) {
                    const color = product.colors.find(c => c.id === row.color_id);
                    if (color && !color.sizes.some((s: { id: any; }) => s.id === row.size_id)) {
                        color.sizes.push(newSize);
                    }
                }
            }

            if (row.image_id && !product.images.some(i => i.id === row.image_id)) {
                const newImage = {
                    id: row.image_id,
                    url: row.image_url,
                    color_id: row.color_id,
                    size_id: row.size_id
                };
                product.images.push(newImage);

                if (row.color_id) {
                    const color = product.colors.find(c => c.id === row.color_id);
                    if (color && !color.images.includes(row.image_url)) {
                        color.images.push(row.image_url);
                    }
                }
                if (row.size_id) {
                    const size = product.sizes.find(s => s.id === row.size_id);
                    if (size && !size.images.includes(row.image_url)) {
                        size.images.push(row.image_url);
                    }
                }
            }
        }

        res.json({ product });
    } catch (error) {
        console.error('Error fetching product by ID:', error);
        res.status(500).json({ error: 'Internal server error' });
    }
};

I think you should create more that a query... I want whenever a product is created or removed it removes the getAllProducts cache and then save the new result in a new cache...
and whenever a product is updated or removed it removes the cache of getProductById and creates a new one... idk do the best one for realtime data and high performance.
I'm using nodejs express as backend and postgresql as database and nextjs app router as frontend.