Wolkendama-API / Controller / shopController.js
shopController.js
Raw
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();
});