const slugify = require("slugify"); const Shop = require("../Model/shopModel"); const AppError = require("../utils/appError"); const catchAsync = require("../utils/catchAsync"); const multer = require("../utils/multer"); const cloudinary = require("../utils/cloudinary"); const { Mongoose, default: mongoose } = require("mongoose"); /////////////////////////////////////////////////////////////// // Configure cloudinary and multer exports.uploadShopImgArr = multer.multerUploadImage.fields([ { name: "imgs" }, { name: "imgCover", maxCount: 1 }, ]); exports.uploadImg = catchAsync(async (req, res, next) => { if (!req.files || (!req.files.imgCover && !req.files.imgs)) { return next(new AppError("No images found", 401)); } let reqPre = {}; // 1) Upload img cover if (req.files.imgCover) { if (req.data.imgCover) { return next( new AppError( "Please delete previous image cover before uploading a new picture", 401 ) ); } const { url, public_id } = await cloudinary.uploader( req.files.imgCover[0].path, `${req.data.slug}-imgCover` ); reqPre.imgCover = url; reqPre.imgCoverPublicId = public_id; } // 2) Upload imgs array if (req.files.imgs.length > 0) { const imgsResult = await Promise.all( req.files.imgs.map((el) => cloudinary.uploader( el.path, `${req.data.slug}-${Date.now().toString()}` ) ) ); const updatedImg = imgsResult.map((el) => el.url); const updatedImgPublicId = imgsResult.map((el) => el.public_id); reqPre.img = [...req.data.img, ...updatedImg]; reqPre.imgPublicId = [...req.data.imgPublicId, ...updatedImgPublicId]; } // 3) Change database item const data = await Shop.findByIdAndUpdate(req.params.id, reqPre, { new: true, runValidators: true, }); res.status(201).json({ status: "success", data, }); }); /////////////////////////////////////////////////////////////// // Save img into local storage // const storage = multer.memoryStorage(); /* const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, "public/images"); }, filename: function (req, file, cb) { const randomname = Date.now(); cb(null, `${file.fieldname}-${randomname}.jpg`); }, }); const fileFilter = (req, file, cb) => { if (file.mimetype.startsWith("image")) { cb(null, true); } else { cb(new AppError("Not an image! Please upload only images", 400), false); } }; const upload = multer({ storage, fileFilter }); exports.uploadImage = upload.fields([{ name: "images", maxCount: 3 }]); */ /////////////////////////////////////////////////////////////// exports.createShopItem = catchAsync(async (req, res, next) => { const { title, itemFilter, specification, variant, descriptions, rank } = req.body; const variantKeys = Object.keys(variant); variantKeys.forEach((el) => { if ( (!variant[el].remainingQuantity && variant[el].remainingQuantity !== 0) || !variant[el].price ) { return next( new AppError( "Please include remaining quantity and price into variant", 401 ) ); } }); const shopItem = await Shop.create({ title, itemFilter, specification, variant, descriptions, rank, }); res.status(201).json({ status: "success", data: shopItem }); }); exports.getAllShopItem = catchAsync(async (req, res, next) => { let data = await Shop.find(req.query).sort("rank"); res.status(201).json({ status: "success", data, }); }); exports.getOneShopItem = catchAsync(async (req, res, next) => { res.status(201).json({ status: "success", data: req.data, }); }); exports.getTopThree = catchAsync(async (req, res, next) => { let data = await Shop.find({ productionReady: true }) .where("rank") .lte(3) .sort("rank"); res.status(201).json({ status: "success", data, }); }); exports.deletePhoto = catchAsync(async (req, res, next) => { const { img, imgCover } = req.body; // Guard Clause if (!req.body || (!img && !imgCover)) { return next(new AppError("No images found", 401)); } let reqPre = {}; // 1) Delete imgCover if (imgCover) { if (!req.data.imgCover) { return next(new AppError("There is no image cover in the database", 401)); } reqPre.imgCover = null; reqPre.imgCoverPublicId = null; await cloudinary.destroyer(req.data.imgCoverPublicId); } // 2) Delete imgs if (img && img.length > 0) { if (req.data.img.length === 0 || img.length > req.data.img.length) { return next(new AppError("There is no images in the database", 401)); } const deleteImgPublicArr = img.map((el) => el.replace(".jpg", "").split("/").slice(-2).join("/") ); // Delete cloudinary req.data.imgPublicId.forEach(async (el) => { if (deleteImgPublicArr.includes(el)) await cloudinary.destroyer(el); }); // Update reqPre reqPre.img = req.data.img.filter((el) => !img.includes(el)); reqPre.imgPublicId = req.data.imgPublicId.filter( (el) => !deleteImgPublicArr.includes(el) ); } // 3) Update Database const data = await Shop.findByIdAndUpdate(req.params.id, reqPre, { new: true, runValidators: true, }); res.status(201).json({ status: "success", data, }); }); exports.changeOneInventory = catchAsync(async (req, res, next) => { const { variant } = req.body; let variantError = false; // Guard Clause if ((!variant || Object.keys(variant).length === 0) && !reserveItem) { return next( new AppError( "Please provide variants or reserve items for the products.", 401 ) ); } let keys = Object.keys(variant); for (let i = 0; i < keys.length; i++) { if ( !( variant[keys[i]].remainingQuantity >= 0 && typeof variant[keys[i]].remainingQuantity === "number" ) || !( variant[keys[i]].price >= 0 && typeof variant[keys[i]].price === "number" ) ) { variantError = true; break; } } if (variantError) { return next( new AppError( `Please include remaining stock quantity or price for variant.`, 401 ) ); } let reqPre = {}; reqPre.variant = variant; const data = await Shop.findByIdAndUpdate(req.params.id, reqPre, { returnDocument: "after", runValidators: true, }); res.status(201).json({ status: "success", data, }); }); exports.changeOneShopItem = catchAsync(async (req, res, next) => { const { img, imgPublicId, imgCover, imgCoverPublicId, title, variant, rank, descriptions, specification, itemFilter, productionReady, } = req.body; // return error if using this API for inventory management if (variant) { return next( new AppError("Please use other APIs for inventory managements", 401) ); } // return error if using this API for photo management if (img || imgPublicId || imgCover || imgCoverPublicId) { return next( new AppError("Please use other APIs for image managements", 401) ); } const slug = title ? slugify(title, { lower: true }) : req.data.slug; let reqPre = { descriptions, specification, itemFilter, rank, slug, productionReady, }; if (req.data.title !== title) { reqPre.title = title; } // Change cloudinary img public Id and database public id if (req.data.img.length > 0 && reqPre.title) { reqPre.img = req.data.img.map((el) => el.replace(req.data.slug, slug)); reqPre.imgPublicId = await Promise.all( req.data.imgPublicId.map((el) => { const newPublicId = el.replace(req.data.slug, slug); cloudinary.editPublicId(el, newPublicId); return newPublicId; }) ); } // Change cloudinary imgCover public Id and database public id if (req.data.imgCover && reqPre.title) { reqPre.imgCover = req.data.imgCover.replace(req.data.slug, slug); reqPre.imgCoverPublicId = req.data.imgCoverPublicId.replace( req.data.slug, slug ); cloudinary.editPublicId(req.data.imgCoverPublicId, reqPre.imgCoverPublicId); } const data = await Shop.findByIdAndUpdate(req.params.id, reqPre, { new: true, runValidators: true, }); res.status(201).json({ status: "success", data, }); }); exports.deleteOneShopItem = catchAsync(async (req, res, next) => { if (req.data.imgPublicId && req.data.imgCoverPublicId) { req.data.imgPublicId.forEach(async (el) => await cloudinary.destroyer(el)); await cloudinary.destroyer(req.data.imgCoverPublicId); } await Shop.findByIdAndDelete(req.params.id); res.status(200).json({ status: "success", data: null, }); }); exports.checkShopId = catchAsync(async (req, res, next) => { const param = req.params.id; if (param.length < 24) { return next(new AppError("Please provide valid id", 400)); } let data; if (req.query) { data = await Shop.findOne({ _id: mongoose.Types.ObjectId(param), ...req.query, }); } else { data = await Shop.findById(param); } if (!data) { return next(new AppError("No shop item found!", 404)); } req.data = data; next(); });