cart post items array are required error.
// src/controllers/cartController.ts

import { Request, Response, NextFunction } from 'express';
import * as cartModel from '../models/cartModel';
import { query } from "../config/database";
import { deleteCartById, getAllCartsWithItems, updateCartStatus } from "../models/cartModel";
import {
    decreaseProductQtyInCache,
    increaseColorSoldInCache,
    increaseProductSoldInCache,
    increaseSizeSoldInCache
} from "../cacheHelper";
import {cartCache, flushAllCarts, flushCartCache} from "../cache";

/**
 * Creates a new cart and updates product quantities accordingly.
 */
async function updateProductQuantity(productId: number, sizeId: number | null, colorId: number | null, quantity: number): Promise<void> {
    let updateQuery = '';
    const params: any[] = [quantity, productId, null]; // Initialize with null

    if (sizeId) {
        updateQuery = 'UPDATE sizes SET qty = qty - $1 WHERE id = $2 AND product_id = $3';
        params[2] = sizeId;
    } else if (colorId) {
        updateQuery = 'UPDATE colors SET qty = qty - $1 WHERE id = $2 AND product_id = $3';
        params[2] = colorId;
    } else {
        updateQuery = 'UPDATE products SET qty = qty - $1 WHERE id = $2';
        params.splice(2, 1); // Remove the third parameter as it's not needed
    }

    if (updateQuery) {
        await query(updateQuery, params);

        // Decrease the quantity in the cache
        decreaseProductQtyInCache(productId, sizeId, colorId, quantity);
    }
}

/**
 * Increases the 'sold' count based on product attributes.
 * @param productId - ID of the product
 * @param sizeId - ID of the size (optional)
 * @param colorId - ID of the color (optional)
 * @param quantity - Quantity to increase
 */
async function increaseSold(productId: number, sizeId: number | null, colorId: number | null, quantity: number): Promise<void> {
    let updateQuery = '';
    let params: any[] = [quantity, productId];

    if (sizeId) {
        updateQuery = 'UPDATE sizes SET sold = sold + $1 WHERE id = $2 AND product_id = $3';
        params.push(sizeId);
    } else if (colorId) {
        updateQuery = 'UPDATE colors SET sold = sold + $1 WHERE id = $2 AND product_id = $3';
        params.push(colorId);
    }
    let productQuery = 'UPDATE products SET sold = sold + $1 WHERE id = $2';
    let productParams = [quantity, productId];

    if (updateQuery) {
        await query(updateQuery, params);
        await query(productQuery, productParams);

        // Update the sold count in the cache
        if (sizeId) {
            increaseSizeSoldInCache(productId, sizeId, quantity);
        } else if (colorId) {
            increaseColorSoldInCache(productId, colorId, quantity);
        }
        increaseProductSoldInCache(productId, quantity);
    }
}
/**
 * Creates a new cart and updates product quantities and sold counts accordingly.
 */
export const createCart = async (req: Request, res: Response) => {
    try {
        const { coupon_id, ...cartData } = req.body;

        await query('BEGIN');

        const cartResult = await query(
            'INSERT INTO carts (user_id, status, notes, full_name, governorate, city, address, closest_point, phone_number, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *',
            [cartData.user_id, 'Pending', cartData.notes, cartData.full_name, cartData.governorate, cartData.city, cartData.address, cartData.closest_point, cartData.phone_number, cartData.type]
        );
        const cart = cartResult.rows[0];

        if (coupon_id) {
            const couponResult = await query('SELECT * FROM coupons WHERE id = $1', [coupon_id]);
            const coupon = couponResult.rows[0];

            if (coupon && coupon.is_active) {
                if (coupon.usage_limit !== 0) {
                    await query('UPDATE coupons SET usage_limit = usage_limit - 1 WHERE id = $1', [coupon_id]);
                    const updatedCouponResult = await query('SELECT usage_limit FROM coupons WHERE id = $1', [coupon_id]);
                    if (updatedCouponResult.rows[0].usage_limit === 0) {
                        await query('UPDATE coupons SET is_active = false WHERE id = $1', [coupon_id]);
                    }
                }
                await query('UPDATE carts SET coupon_id = $1 WHERE id = $2', [coupon_id, cart.id]);
            } else {
                await query('ROLLBACK');
                return res.status(400).json({ message: 'Invalid or inactive coupon' });
            }
        }

        for (const item of cartData.items) {
            await query(
                'INSERT INTO cart_items (cart_id, product_id, size_id, color_id, quantity) VALUES ($1, $2, $3, $4, $5)',
                [cart.id, item.product_id, item.size_id, item.color_id, item.quantity]
            );

            // Decrease the product quantity
            await updateProductQuantity(item.product_id, item.size_id, item.color_id, item.quantity);

            // Increase the sold count based on product attributes
            await increaseSold(item.product_id, item.size_id, item.color_id, item.quantity);
        }

        await query('COMMIT');

        // Invalidate cart caches as data has changed
        flushAllCarts();

        res.status(201).json({ cart });
    } catch (error) {
        await query('ROLLBACK');
        console.error('Error creating cart:', error);
        res.status(500).json({ message: 'Error creating cart' });
    }
};

/**
 * Updates cart status.
 */
export async function updateCart(req: Request, res: Response, next: NextFunction) {
    try {
        const cartId = parseInt(req.params.id, 10);
        const { status } = req.body;

        const updatedCart = await updateCartStatus(cartId, status);

        if (!updatedCart) {
            return res.status(404).json({ message: 'Cart not found' });
        }

        // Invalidate specific cart cache if necessary
        if (updatedCart.user_id) {
            flushCartCache(updatedCart.user_id);
        }

        res.json({ message: 'Cart status updated successfully', cart: updatedCart });
    } catch (error) {
        next(error);
    }
}

/**
 * Deletes a cart by ID.
 */
export async function deleteCart(req: Request, res: Response, next: NextFunction) {
    try {
        const cartId = parseInt(req.params.id, 10);

        const deleted = await deleteCartById(cartId);

        if (!deleted) {
            return res.status(404).json({ message: 'Cart not found' });
        }

        // Invalidate all carts cache as data has changed
        flushAllCarts();

        res.json({ message: 'Cart deleted successfully' });
    } catch (error) {
        next(error);
    }
}

export async function getAllCarts(req: Request, res: Response, next: NextFunction) {
    try {
        const { page = 1, limit = 10 } = req.query;

        const pageNumber = parseInt(page as string, 10);
        const limitNumber = parseInt(limit as string, 10);

        const cacheKey = `allCarts_page_${pageNumber}_limit_${limitNumber}`;

        // Check if data is in cache
        const cachedCarts = cartCache.get(cacheKey);
        if (cachedCarts) {
            return res.json(cachedCarts);
        }

        const { carts, total } = await getAllCartsWithItemsPaginated(pageNumber, limitNumber);

        const response = { carts, total, page: pageNumber, limit: limitNumber };

        // Store in cache
        cartCache.set(cacheKey, response);

        res.json(response);
    } catch (error) {
        next(error);
    }
}
export async function getAllCartsWithItemsPaginated(page: number, limit: number) {
    const offset = (page - 1) * limit;

    const cartsResult = await query(
        `SELECT * FROM carts ORDER BY created_at DESC LIMIT $1 OFFSET $2`,
        [limit, offset]
    );
    const carts = cartsResult.rows;

    // Fetch total number of carts for pagination
    const totalResult = await query(`SELECT COUNT(*) FROM carts`);
    const total = parseInt(totalResult.rows[0].count, 10);

    // Fetch items for each cart
    for (const cart of carts) {
        const itemsResult = await query(
            `SELECT * FROM cart_items WHERE cart_id = $1`,
            [cart.id]
        );
        cart.items = itemsResult.rows;
    }

    return { carts, total };
}
cartRoutes.ts:
import express from 'express';
import { body } from 'express-validator';
import { validateRequest } from '../middleware/validateRequest';
import { isAuthenticated } from '../middleware/isAuthenticated';
import * as cartController from '../controllers/cartController';
import {isAdmin} from "../middleware/authMiddleware";
import {deleteCart, getAllCarts, updateCart} from "../controllers/cartController";

const router = express.Router();

// Create a new cart with items
router.post(
    '/',
    [
            body('notes').optional().isString().withMessage('Notes is not valid'),
            body('full_name').optional().isString().withMessage('Full name must be provided'),
            body('governorate').isString().withMessage('Governorate must be provided'),
            body('city').optional().isString().withMessage('City is not valid'),
            body('address').isString().withMessage('Address must be provided'),
            body('closest_point').optional().isString().withMessage('Closest point is not valid'),
            body('phone_number').isString().withMessage('Phone number must be provided'),
            body('type')
                .optional({values: "falsy"})
                .isString()
                .withMessage('Type must be home, work, or other'),
            body('coupon_id')
                .optional({ nullable: true })
                .custom((value) => {
                        if (value === null) return true;
                        return Number.isInteger(Number(value));
                })
                .withMessage('Coupon is not valid'),

            body('items').isArray({ min: 1 }).withMessage('Items array is required'),
            body('items.*.product_id').isInt().withMessage('Product ID must be an integer'),
            body('items.*.size_id').optional({values: "falsy"}).isInt().withMessage('Size ID must be an integer'),
            body('items.*.color_id').optional({values: "falsy"}).isInt().withMessage('Color ID must be an integer'),
            body('items.*.quantity').isInt({ min: 1 }).withMessage('Quantity must be greater than zero'),
    ],
    validateRequest,
    cartController.createCart
);

router.get('/', isAuthenticated, cartController.getAllCarts);
router.get('/all', isAdmin, getAllCarts);

router.put(
    '/:id',
    [
        body('status').isString().withMessage('Status is required'),
    ],
    validateRequest,
    updateCart
);

router.delete('/:id', isAdmin, deleteCart);

export default router;