diff --git a/.env b/.env index def78a6..e5b250d 100644 --- a/.env +++ b/.env @@ -1,6 +1,7 @@ DB_URL="mongodb+srv://smellica:Anjefef23dnsfjne@cluster0.c5gfqzm.mongodb.net/" +# DB_URL="mongodb://localhost:27017/smellica" PORT = 5000 JWT_SECRET = jdvnvjwrniwj4562ddsjn6@1xsbfeh@wre4Njdf; @@ -13,9 +14,12 @@ WEBHOOK_SECRET_KEY="whsec_m9u7CFBCY1qWarhxq65CkII6egOBf20K" STRIPE_SECRET_KEY="sk_test_51OhPRdSG6gbAOwcEid1GavJ4FTD0ZuHVTferdvJwKal77RlMtFJGBzL5GjtL0ie8ZJztsGjUWi8DWrnw1pDdDRGS005Hk0ahql" STRIPE_WEBHOOK_SECRET="whsec_dc9b9084fc764c806c8c5c06dd91de1ee809e9c8deab6d56e8e3ef2fc9c30c67" +<<<<<<< HEAD FRONTEND_URL="http://127.0.0.1:5173" RAZERPAY_KEY_ID="rzp_test_smzQmWoS64S2W9" RAZERPAY_SECRET_KEY="cSn6MgA4xSEaZBpPp4zpDA3C" +FRONTEND_URL="https://smellika.com" + SEND_EMAIL_FROM="hello@smellika.com" #brevo send mail @@ -25,4 +29,6 @@ SMPT_MAIL="neonflake.enterprises@gmail.com" SMPT_PASSWORD="ND5sgVnWtrpUFfTb" PAYPAL_CLIENT_ID="AemCjVuWswklp1sWUo4peCFg9eS4bofMsMR0RCrVRB2DifYR1IUSrWqtHpVmQlrVMKTI2cWZXLJAdYwn" -PAYPAL_CLIENT_SECRET="EAo0Y9ff3jpHHg1QAbftdebfh7cb_-vnebhQrP9KALbCVer908yx2tO2eHO39r7EJSfqc4D69Qgx8R31" \ No newline at end of file +PAYPAL_CLIENT_SECRET="EAo0Y9ff3jpHHg1QAbftdebfh7cb_-vnebhQrP9KALbCVer908yx2tO2eHO39r7EJSfqc4D69Qgx8R31" + +STRIPE_SECRET="sk_test_51OhPRdSG6gbAOwcEid1GavJ4FTD0ZuHVTferdvJwKal77RlMtFJGBzL5GjtL0ie8ZJztsGjUWi8DWrnw1pDdDRGS005Hk0ahql" \ No newline at end of file diff --git a/app.js b/app.js index 8f66712..b93919e 100644 --- a/app.js +++ b/app.js @@ -155,10 +155,20 @@ import TaxRouter from "./resources/Tax/tax_routes.js"; //specialties import SpecialtiesRouter from "./resources/Specialties/SpecialtiesRoute.js"; import ShippingAddressRoute from "./resources/ShippingAddresses/ShippingAddressRoute.js"; +import stripeRoute from "./resources/StripePayment/stripeRoute.js"; +import SeoRoute from "./resources/SEO&Analytics/SEORouter.js"; + +//Affiliate Routes +import AffiliateRoute from "./resources/Affiliate&Coupon/Affiliate/AffiliateRoute.js"; +//Blog Routes +import BlogRoute from "./resources/Blog/BlogRoute.js"; +//Coupon Routes +import CouponRoute from "./resources/Affiliate&Coupon/Coupon/CouponRoute.js"; //short urls // import ShortUrlRouter from "./resources/Businesses/Short_Urls/ShortUrlRoute.js"; - +//support Ticket +import SupportRouter from "./resources/Supports/supportRoute.js"; app.use("/api/v1/", user); //Product @@ -200,6 +210,18 @@ app.use("/api/business", orderRoute); app.use("/api/tax", TaxRouter); //config app.use("/api/config", ConfigRouter); + +app.use("/api/stripe", stripeRoute); + +app.use("/api/seo", SeoRoute); + +//Affiliates +app.use("/api/v1/affiliate", AffiliateRoute); + +//Coupons +app.use("/api/v1/coupon", CouponRoute); +//Blog +app.use("/api/v1/blog", BlogRoute); //config specialty // app.use("/api/config/specialty", SpecialtiesRouter); //specialties @@ -208,5 +230,6 @@ app.use("/api/config", ConfigRouter); // app.use("/api/appointment", AppointmentRouter); //short urls // app.use("/api/shorturl", ShortUrlRouter); - +//Support +app.use("/api", SupportRouter); export default app; diff --git a/resources/Affiliate&Coupon/Affiliate/AffiliateController.js b/resources/Affiliate&Coupon/Affiliate/AffiliateController.js new file mode 100644 index 0000000..790f917 --- /dev/null +++ b/resources/Affiliate&Coupon/Affiliate/AffiliateController.js @@ -0,0 +1,291 @@ +import { AffiliateModel } from "./AffiliateModel.js"; + +// -----------------------------AFFILIATE & COUPONS ARE HARDLY BINDED DATA-------------------------------------------------------- +//Create Affiliate +export const createAffiliate = async (req, res) => { + try { + const result = req.body; + const affiliate = new AffiliateModel(result); + const savedData = await affiliate.save(); + if (savedData) { + return res + .status(201) + .json({ success: true, message: "Affiliate Added" }); + } + } catch (error) { + res.status(500).json({ + success: false, + message: error.message + .split(":") + .splice(1) + .join(":") + .trim() + .split(":") + .splice(1) + .join(":") + .trim(), + }); + } +}; + +//EDIT +export const editAffiliate = async (req, res) => { + const updateFields = {}; + + const { + name, + mobile, + email, + country, + state, + city, + address, + pincode, + nameAsBank, + accountNo, + ifsc, + bankName, + branchName, + } = req.body; + + // Add only the fields that are present in the request body to the updateFields object + if (name) updateFields.name = name; + if (mobile) updateFields.mobile = mobile; + if (email) updateFields.email = email; + if (country) updateFields.country = country; + if (state) { + updateFields.state = state; + } else { + updateFields.state = ""; + } + if (city) { + updateFields.city = city; + } else { + updateFields.city = ""; + } + if (address) updateFields.address = address; + if (pincode) updateFields.pincode = pincode; + if (nameAsBank) updateFields.nameAsBank = nameAsBank; + if (accountNo) updateFields.accountNo = accountNo; + if (ifsc) updateFields.ifsc = ifsc; + if (bankName) updateFields.bankName = bankName; + if (branchName) updateFields.branchName = branchName; + try { + const saveData = await AffiliateModel.findByIdAndUpdate( + { _id: req.params.id }, + { $set: updateFields }, + { new: true } + ); + res.json({ + success: true, + message: "Affiliate Updated Succesfully", + }); + } catch (error) { + res.status(400).json({ + success: false, + message: "Error in Updation", + }); + } +}; +//DELETE +export const deleteAffiliate = async (req, res) => {}; +//PAY AFFILIATE TODO +export const payAffiliate = async (req, res) => { + // console.log(req.body); + const { noOfCoupons, amountToPay, amount, transecId, date, time } = req.body; + if ( + !req.params.id || + !noOfCoupons || + !amountToPay || + !amount || + !transecId || + !date || + !time + ) { + return res.status(400).json({ + success: false, + message: "Error in Payment", + }); + } + try { + const affiliate = await AffiliateModel.findById(req.params.id); + //Checking if it's valid data from the client + if ( + amountToPay != affiliate.total_earning - affiliate.paid_amount || + noOfCoupons != affiliate.coupon_claimed - affiliate.no_of_paid_coupon + ) { + return res.status(400).json({ + success: false, + message: "Data invalid", + }); + } + + // Construct the update operation + const updateOperation = { + $push: { + affiliate_pay_history: { + amount: amountToPay, + transecId: transecId, + date: date, + time: time, + }, + }, + $inc: { + paid_amount: amountToPay, + no_of_paid_coupon: noOfCoupons, + }, + }; + + // Execute the update operation + const updatedAffiliate = await AffiliateModel.findByIdAndUpdate( + req.params.id, + updateOperation, + { new: true } + ); + + return res.json({ + success: true, + message: "Payment Done Successfully", + updatedAffiliate: { updatedAffiliate }, + }); + } catch (error) { + console.error(error); + return res.status(400).json({ + success: false, + message: "Error in Payment", + }); + } +}; + +//GET ONE AFFLILIATE +export const getOneAffiliate = async (req, res) => { + if (req.params?.id) { + try { + const saveData = await AffiliateModel.findById(req.params.id); + const resObj = { + name: saveData.name, + mobile: saveData.mobile, + email: saveData.email, + country: saveData.country, + state: saveData.state, + city: saveData.city, + address: saveData.address, + pincode: saveData.pincode, + nameAsBank: saveData.nameAsBank, + accountNo: saveData.accountNo, + ifsc: saveData.ifsc, + bankName: saveData.bankName, + branchName: saveData.branchName, + }; + res.status(200).json({ + success: true, + message: resObj, + }); + } catch (error) { + res.status(400).json({ + success: false, + message: "Error in getting Affiliates", + }); + } + } +}; +//LIST ALL AFFILIATE +export const listAllAffiliate = async (req, res) => { + try { + const affiliate = await AffiliateModel.find( + {}, + { + name: 1, + _id: 1, + coupon_claimed: 1, + coupon_code: 1, + total_earning: 1, + paid_amount: 1, + is_affiliate_active: 1, + } + ).sort({ createdAt: -1 }); + res.status(200).json({ + success: true, + message: affiliate, + }); + } catch (error) { + res.status(500).json({ + success: false, + messgae: error.message ? error.message : "Something went wrong!", + }); + } +}; + +//Activate & Deactivate Affiliates +export const suspendAffiliate = async (req, res) => { + const { id, is_affiliate_active } = req.body; + try { + const saveData = await AffiliateModel.findByIdAndUpdate(id, { + is_affiliate_active: is_affiliate_active, + }); + res.status(200).json({ + success: true, + message: "Success", + }); + } catch (error) { + res.status(400).json({ + success: false, + message: "Affiliate Doesn't Exists", + }); + } +}; + +//Get Affiliate data for payment +export const getOneAffiliateForPay = async (req, res) => { + if (req.params?.id) { + try { + const saveData = await AffiliateModel.findById(req.params.id); + const resObj = { + name: saveData.name, + coupon_claimed: saveData.coupon_claimed, + total_earning: saveData.total_earning, + paid_amount: saveData.paid_amount, + no_of_paid_coupon: saveData.no_of_paid_coupon, + affiliate_discount_amount: saveData.affiliate_discount_amount, + coupon_code: saveData.coupon_code, + nameAsBank: saveData.nameAsBank, + accountNo: saveData.accountNo, + ifsc: saveData.ifsc, + bankName: saveData.bankName, + branchName: saveData.branchName, + }; + res.status(200).json({ + success: true, + message: resObj, + }); + } catch (error) { + res.status(400).json({ + success: false, + message: "Error in getting Affiliates", + }); + } + } +}; +//Get Affiliate data for History +export const affiliatePayHistory = async (req, res) => { + if (req.params?.id) { + try { + const saveData = await AffiliateModel.findById(req.params.id).sort({ + updatedAt: -1, + }); + const resObj = { + affiliate_pay_history: saveData.affiliate_pay_history, + name: saveData.name, + }; + res.status(200).json({ + success: true, + message: resObj, + }); + } catch (error) { + res.status(400).json({ + success: false, + message: "Error in getting History", + }); + } + } +}; diff --git a/resources/Affiliate&Coupon/Affiliate/AffiliateModel.js b/resources/Affiliate&Coupon/Affiliate/AffiliateModel.js new file mode 100644 index 0000000..928243e --- /dev/null +++ b/resources/Affiliate&Coupon/Affiliate/AffiliateModel.js @@ -0,0 +1,122 @@ +import mongoose from "mongoose"; +const { Schema, model } = mongoose; + +const couponUsedSchema = new Schema({ + userId: { type: String, required: true }, + orderId: { type: String, required: true }, + couponCode: { type: String, required: true }, + date: { type: String, required: true }, +}); +const affilitePaySchema = new Schema({ + amount: { type: Number, required: true }, + transecId: { type: String, required: true }, + date: { type: String, required: true }, + time: { type: String, required: true }, +}); +const affiliateSchema = new Schema( + { + name: { + type: String, + maxLength: [25, "name cannot exceed 25 characters"], + required: [true, "Please Enter Name"], + }, + mobile: { + type: Number, + maxLength: [10, "Mobile cannot exceed 10 characters"], + minlength: [10, "Invalid Mobile Number"], + required: [true, "Please Enter Mobile Number"], + unique: true, + }, + email: { + type: String, + required: [true, "Please Enter Email"], + unique: true, + }, + country: { + type: String, + required: [true, "Please Enter Country"], + }, + state: { + type: String, + }, + city: { + type: String, + }, + address: { + type: String, + required: [true, "Please Enter Address"], + }, + pincode: { + type: Number, + required: [true, "Please Enter Pincode"], + }, + nameAsBank: { + type: String, + required: [true, "Please Enter Name as Bank"], + }, + accountNo: { + type: Number, + required: [true, "Please Enter Account Number"], + unique: true, + }, + ifsc: { + type: String, + required: [true, "Please Enter IFSC code"], + }, + bankName: { + type: String, + required: [true, "Please Enter Bank Name"], + }, + branchName: { + type: String, + required: [true, "Please Enter Branch Name"], + }, + coupon_code: { + type: String, + unique: [true, "Coupon Alerady Exists"], + sparse: true, + }, + discount_amount: { + type: Number, + }, + affiliate_discount_amount: { + type: Number, + }, + valid_till: { + type: String, + }, + createdAt: { + type: String, + }, + + coupon_claimed: { + type: Number, + default: 0, + }, + total_earning: { + type: Number, + default: 0, + }, + paid_amount: { + type: Number, + default: 0, + }, + no_of_paid_coupon: { + type: Number, + default: 0, + }, + is_affiliate_active: { + type: Boolean, + default: true, + }, + is_coupon_active: { + type: Boolean, + default: false, + }, + coupon_used_history: [couponUsedSchema], + affiliate_pay_history: [affilitePaySchema], + }, + { timestamps: true } +); + +export const AffiliateModel = model("Affiliate", affiliateSchema); diff --git a/resources/Affiliate&Coupon/Affiliate/AffiliateRoute.js b/resources/Affiliate&Coupon/Affiliate/AffiliateRoute.js new file mode 100644 index 0000000..ccf9b66 --- /dev/null +++ b/resources/Affiliate&Coupon/Affiliate/AffiliateRoute.js @@ -0,0 +1,69 @@ +import express from "express"; +import { + affiliatePayHistory, + createAffiliate, + editAffiliate, + getOneAffiliate, + getOneAffiliateForPay, + listAllAffiliate, + payAffiliate, + suspendAffiliate, +} from "./AffiliateController.js"; + +import { + isAuthenticatedUser, + authorizeRoles, +} from "../../../middlewares/auth.js"; + +const router = express.Router(); + +router.post( + "/create", + isAuthenticatedUser, + authorizeRoles("admin"), + createAffiliate +); +router.get( + "/getall", + isAuthenticatedUser, + authorizeRoles("admin"), + listAllAffiliate +); +router.get( + "/getone/:id", + isAuthenticatedUser, + authorizeRoles("admin"), + getOneAffiliate +); +router.patch( + "/edit/:id", + isAuthenticatedUser, + authorizeRoles("admin"), + editAffiliate +); +router.patch( + "/suspend", + isAuthenticatedUser, + authorizeRoles("admin"), + suspendAffiliate +); +router.post( + "/pay/:id", + isAuthenticatedUser, + authorizeRoles("admin"), + payAffiliate +); +router.get( + "/getpay/:id", + isAuthenticatedUser, + authorizeRoles("admin"), + getOneAffiliateForPay +); +router.get( + "/history/:id", + isAuthenticatedUser, + authorizeRoles("admin"), + affiliatePayHistory +); + +export default router; diff --git a/resources/Affiliate&Coupon/Coupon/CouponController.js b/resources/Affiliate&Coupon/Coupon/CouponController.js new file mode 100644 index 0000000..285d145 --- /dev/null +++ b/resources/Affiliate&Coupon/Coupon/CouponController.js @@ -0,0 +1,414 @@ +import { AffiliateModel } from "../Affiliate/AffiliateModel.js"; //Note AffiliteModel is binded with coupons + +//GET ALL Coupons +export const listAllCoupon = async (req, res) => { + try { + const coupon = await AffiliateModel.find( + {}, + { + name: 1, + _id: 1, + coupon_code: 1, + discount_amount: 1, + affiliate_discount_amount: 1, + is_coupon_active: 1, + } + ).sort({ createdAt: -1 }); + const filteredCoupons = coupon.filter( + (data) => !(data.coupon_code == null) + ); + // console.log(filteredCoupons); + // console.log(coupon); + + res.status(200).json({ + success: true, + message: filteredCoupons, + }); + } catch (error) { + res.status(500).json({ + success: false, + message: error.message ? error.message : "Something went wrong!", + }); + } +}; +//CREATE Coupon (AKA Need to update Affiliate ) +export const createCoupon = async (req, res) => { + //creation of date + const date = new Date(); + const daysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + const monthsOfYear = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ]; + const dayOfWeek = daysOfWeek[date.getUTCDay()]; + const dateOfMonth = date.getUTCDate(); + const month = monthsOfYear[date.getUTCMonth()]; + const year = date.getUTCFullYear(); + const formattedDate = `${dayOfWeek} ${dateOfMonth}-${month}-${year}`; + const { + coupon_code, + discount_amount, + valid_till, + is_coupon_active, + affiliate_discount_amount, + } = req.body; + try { + const { id } = req.body; + const update = { + coupon_code, + discount_amount, + valid_till, + affiliate_discount_amount, + createdAt: formattedDate, + is_coupon_active, + }; + const options = { new: true }; + const saveData = await AffiliateModel.findByIdAndUpdate( + id, + update, + options + ); + + if (saveData) { + res.json("done"); + } else { + res.status(404).json("Affiliate not found"); + } + } catch (error) { + res.status(400).json({ + success: true, + message: "Coupon Already Exists", + }); + console.log(error); + } +}; + +//GET AFFILIATE FOR COUPON LIST +export const listAffiliateCoupon = async (req, res) => { + try { + let resArr = []; + const coupon = await AffiliateModel.find( + {}, + { + name: 1, + _id: 1, + is_coupon_active: 1, + mobile: 1, + } + ); + for (let i = 0; i < coupon.length; i++) { + if (coupon[i].is_coupon_active == false) { + resArr.push(coupon[i]); + } + } + // console.log(resArr); + res.status(200).json({ + success: true, + message: resArr, + }); + } catch (error) { + res.status(500).json({ + success: false, + message: error.message ? error.message : "Something went wrong!", + }); + } +}; +//EDIT COUPON +export const editCoupon = async (req, res) => { + const { + coupon_code, + discount_amount, + valid_till, + affiliate_discount_amount, + } = req.body; + + const updateFields = {}; + + // Add only the fields that are present in the request body to the updateFields object + if (coupon_code) updateFields.coupon_code = coupon_code; + if (discount_amount) updateFields.discount_amount = discount_amount; + if (valid_till) updateFields.valid_till = valid_till; + if (affiliate_discount_amount) + updateFields.affiliate_discount_amount = affiliate_discount_amount; + + try { + const saveData = await AffiliateModel.findByIdAndUpdate( + { _id: req.params.id }, + { $set: updateFields }, + { new: true } + ); + res.json({ + success: true, + message: "Coupon Edited Succesfully", + }); + } catch (error) { + res.status(400).json({ + success: false, + message: error.message + ? error.message + .split(":") + .splice(1) + .join(":") + .trim() + .split(":") + .splice(1) + .join(":") + .trim() + : "Error in Editing Coupon", + }); + } +}; +//SUSPEND COUPON +export const suspendCoupon = async (req, res) => { + const { id, is_coupon_active } = req.body; + try { + const saveData = await AffiliateModel.findByIdAndUpdate(id, { + is_coupon_active: is_coupon_active, + }); + res.status(200).json({ + success: true, + message: "Success", + }); + } catch (error) { + res.status(400).json({ + success: false, + message: "Coupon Doesn't Exists", + }); + } +}; + +//GET ONE COUPON +export const getOneCoupon = async (req, res) => { + if (req.params?.id) { + try { + const saveData = await AffiliateModel.findById(req.params.id); + + const resObj = { + name: saveData.name, + mobile: saveData.mobile, + coupon_code: saveData.coupon_code, + discount_amount: saveData.discount_amount, + valid_till: saveData.valid_till, + affiliate_discount_amount: saveData.affiliate_discount_amount, + }; + res.status(200).json({ + success: true, + message: resObj, + }); + } catch (error) { + res.status(400).json({ + success: false, + message: "Error in getting Coupons", + }); + } + } +}; + +//Validate Coupon----------------------- +export const validateCoupon = async (req, res) => { + const { coupon } = req.params; + + if (!coupon) { + return res.status(400).json({ + success: false, + message: "Coupon code is required", + }); + } + + try { + // Find the coupon data in the database + const couponData = await AffiliateModel.findOne({ coupon_code: coupon }); + + if (!couponData) { + return res.status(404).json({ + success: false, + message: "Coupon not found", + }); + } + + const { valid_till, discount_amount, is_coupon_active } = couponData; + //Check whether Coupon is Active or not + if (!is_coupon_active) { + return res.status(404).json({ + success: false, + message: "Coupon Code Expired", + }); + } + // Check if the coupon is expired + const currentDate = new Date(); + const expirationDate = new Date(valid_till); + + if (currentDate > expirationDate) { + return res.status(400).json({ + success: false, + message: "Coupon has expired", + }); + } + + // If coupon is valid, return success response + res.status(200).json({ + success: true, + message: "Coupon is valid", + discount_amount: discount_amount, + }); + } catch (error) { + console.error(error); + res.status(500).json({ + success: false, + message: "Internal server error", + }); + } +}; + +//PAY & HISTORY--------------------- +export const usedCoupon = async (req, res) => { + // Retrieve orderId and coupon_code from request body or query parameters + const { orderId, coupon_code, userId } = req.body; + if (!orderId || !coupon_code || !userId) { + return res.status(400).json({ + success: false, + message: "Error in getting OrderId or Coupon", + }); + } + + // Validating Coupon + try { + const couponData = await AffiliateModel.findOne({ + coupon_code: coupon_code, + }); + //order exists or not + + if (!couponData) { + // Check if the coupon exists + return res.status(404).json({ + success: false, + message: "Coupon not found", + }); + } + // Check if orderId is unique + try { + const isOrderIdUnique = await AffiliateModel.find( + {}, + { + coupon_used_history: 1, + } + ); + + let orderIdFound = false; + + isOrderIdUnique.forEach((data) => { + data.coupon_used_history.forEach((subItem) => { + if (subItem.orderId == orderId) { + orderIdFound = true; + } + }); + }); + + if (orderIdFound) { + return res.status(400).json({ + success: false, + message: "Error: OrderId already used", + }); + } + } catch (error) { + console.error(error); + return res.status(500).json({ + success: false, + message: "Internal server error", + }); + } + //If not unique then create + const { + valid_till, + is_coupon_active, + is_affiliate_active, + affiliate_discount_amount, + _id, + } = couponData; + // console.log(couponData); + if (!is_coupon_active || !is_affiliate_active) { + return res.status(404).json({ + success: false, + message: "Coupon Code Expired", + }); + } + const currentDate = new Date(); + const expirationDate = new Date(valid_till); + + if (currentDate > expirationDate) { + return res.status(400).json({ + success: false, + message: "Coupon has expired", + }); + } + + AffiliateModel.findByIdAndUpdate( + _id, + { + $inc: { total_earning: affiliate_discount_amount, coupon_claimed: 1 }, + $push: { + coupon_used_history: { + orderId: orderId, + userId: userId, + date: currentDate, + couponCode: coupon_code, + }, + }, + }, + { new: true } + ) + .then(() => { + res.status(200).json({ + success: true, + message: "Coupon add success", + }); + }) + .catch((error) => { + console.error(error); + res.status(500).json({ + success: false, + message: "Internal server error", + }); + }); + } catch (error) { + console.error(error); + res.status(500).json({ + success: false, + message: "Internal server error", + }); + } +}; +//Get Coupon data for History +export const couponPayHistory = async (req, res) => { + if (req.params?.id) { + try { + const saveData = await AffiliateModel.findById(req.params.id).sort({ + updatedAt: -1, + }); + // console.log(saveData.coupon_used_history); + const resObj = { + coupon_used_history: saveData.coupon_used_history, + coupon_code: saveData.coupon_code, + }; + res.status(200).json({ + success: true, + message: resObj, + }); + } catch (error) { + res.status(400).json({ + success: false, + message: "Error in getting History", + }); + } + } +}; diff --git a/resources/Affiliate&Coupon/Coupon/CouponRoute.js b/resources/Affiliate&Coupon/Coupon/CouponRoute.js new file mode 100644 index 0000000..bead939 --- /dev/null +++ b/resources/Affiliate&Coupon/Coupon/CouponRoute.js @@ -0,0 +1,76 @@ +import express from "express"; +import { + couponPayHistory, + createCoupon, + editCoupon, + getOneCoupon, + listAffiliateCoupon, + listAllCoupon, + suspendCoupon, + usedCoupon, + validateCoupon, +} from "./CouponController.js"; + +import { + isAuthenticatedUser, + authorizeRoles, +} from "../../../middlewares/auth.js"; + +const router = express.Router(); +router.get( + "/getall", + isAuthenticatedUser, + authorizeRoles("admin"), + listAllCoupon +); +router.patch( + "/create", + isAuthenticatedUser, + authorizeRoles("admin"), + createCoupon +); +router.get( + "/getaffiliate", + isAuthenticatedUser, + authorizeRoles("admin"), + listAffiliateCoupon +); +router.patch( + "/edit/:id", + isAuthenticatedUser, + authorizeRoles("admin"), + editCoupon +); +router.get( + "/getone/:id", + isAuthenticatedUser, + authorizeRoles("admin"), + getOneCoupon +); +router.get("/validcoupon/:coupon", validateCoupon); +router.patch( + "/suspend", + isAuthenticatedUser, + authorizeRoles("admin"), + suspendCoupon +); +router.patch( + "/paycoupon", + // isAuthenticatedUser, + usedCoupon +); +/* url:http://localhost:5000/api/v1/coupon/paycoupon + json structure to paycoupon , Need Header to be auth +{ + "userId":"random1", + "orderId":"12s213", + "coupon_code":"3000MONY" +}*/ +router.get( + "/history/:id", + isAuthenticatedUser, + authorizeRoles("admin"), + couponPayHistory +); + +export default router; diff --git a/resources/Blog/BlogController.js b/resources/Blog/BlogController.js new file mode 100644 index 0000000..79abfd3 --- /dev/null +++ b/resources/Blog/BlogController.js @@ -0,0 +1,63 @@ +import Blog from "./BlogModel.js"; + +export const createBlog = async (req, res) => { + const { title, tags, image, blog_content } = req.body; + console.log(req.body); + + // Checking Fields + if (!title || !tags || !image || !blog_content) { + return res.status(400).json({ + success: false, + message: "All fields are mandatory", + }); + } + + try { + let images = []; + let Allfiles = req.files.image; + console.log(Allfiles); + // if (!Array.isArray(Allfiles)) { + // Allfiles = [Allfiles]; // Convert to array if it's not already + // } + + // Allfiles.forEach((file) => { + // if (typeof file.tempFilePath === "string") { + // let filepath = file.tempFilePath; + // images.push(filepath); + // } + // }); + + // const newBlog = await Blog.create({ + // title, + // tags, + // image: images, // Assign the array of image file paths + // blog_content, + // }); + + res.status(201).json({ + success: true, + message: "Blog created successfully", + data: images, + }); + } catch (error) { + console.error("Error creating blog:", error); + res.status(500).json({ + success: false, + message: error.message ? error.message : "Internal server error", + }); + } +}; +export const getAllBlog = async (req, res) => { + try { + const saveData = await Blog.find(); + res.status(200).json({ + success: true, + message: saveData, + }); + } catch { + res.status(500).json({ + success: false, + message: "Internal server error", + }); + } +}; diff --git a/resources/Blog/BlogModel.js b/resources/Blog/BlogModel.js new file mode 100644 index 0000000..1b3a65b --- /dev/null +++ b/resources/Blog/BlogModel.js @@ -0,0 +1,28 @@ +import mongoose from "mongoose"; + +const { Schema, model } = mongoose; + +const blogSchema = new Schema( + { + title: { + type: String, + required: [true, "Title is required"], + }, + tags: { + type: [String], + required: [true, "Tags are required"], + }, + image: { + type: String, + }, + blog_content: { + type: Object, + required: [true, "Content is required"], + }, + }, + { timestamps: true } +); + +const Blog = model("Blog", blogSchema); + +export default Blog; diff --git a/resources/Blog/BlogRoute.js b/resources/Blog/BlogRoute.js new file mode 100644 index 0000000..772b7b6 --- /dev/null +++ b/resources/Blog/BlogRoute.js @@ -0,0 +1,11 @@ +import express from "express"; + +import { createBlog, getAllBlog } from "./BlogController.js"; +import { isAuthenticatedUser, authorizeRoles } from "../../middlewares/auth.js"; + +const router = express.Router(); + +router.post("/create", createBlog); +router.get("/getallblog", getAllBlog); + +export default router; diff --git a/resources/Blog/dummy.json b/resources/Blog/dummy.json new file mode 100644 index 0000000..90dfec3 --- /dev/null +++ b/resources/Blog/dummy.json @@ -0,0 +1,5 @@ +[ + { + "title": "String0" + } +] diff --git a/resources/ContactRequests/ContactRequestsModel.js b/resources/ContactRequests/ContactRequestsModel.js index ccad01d..6dcca01 100644 --- a/resources/ContactRequests/ContactRequestsModel.js +++ b/resources/ContactRequests/ContactRequestsModel.js @@ -1,7 +1,4 @@ - - - import mongoose from "mongoose"; const { Schema, model } = mongoose; diff --git a/resources/Content/ContentController.js b/resources/Content/ContentController.js index f7500ff..b366441 100644 --- a/resources/Content/ContentController.js +++ b/resources/Content/ContentController.js @@ -51,16 +51,24 @@ export const getTermsAndCondition = async (req, res) => { export const updateTermsAndConditions = async (req, res) => { try { if (!req?.user) return res.status(400).json({ message: "please login !" }); - // console.log(req?.user) - const { content } = req.body; - const termsAndCondition = await TermsAndCondition.findOneAndUpdate( - { - addedBy: req.user._id, - }, - { - termsAndContionContent: content, - } - ); + // new content + const { content } = req.body; + + // id of the terms and conndition document + const id = req.query.id; + + // object for updated terms and conndition data + const updatedTermsData = { + termsAndContionContent: content, + addedBy: req.user._id + } + + // update the terms and conndition in database + const termsAndCondition = await TermsAndCondition.findByIdAndUpdate( + { _id: id }, + { $set: updatedTermsData }, + { new: true } + ); res.status(200).json({ success: true, @@ -80,23 +88,10 @@ export const RefundPolicy = async (req, res) => { if (!req?.user) return res.status(400).json({ message: "please login !" }); // console.log(req?.user) const { content } = req.body; - const findv = await Refundpolicy.findOne(); - let refundPolicy; - if (findv) { - refundPolicy = await Refundpolicy.findOneAndUpdate( - { - addedBy: req.user._id, - }, - { - Refundpolicy: content, - } - ); - } else { - refundPolicy = await Refundpolicy.create({ + const refundPolicy = await Refundpolicy.create({ addedBy: req.user._id, Refundpolicy: content, }); - } res.status(200).json({ success: true, @@ -130,6 +125,42 @@ export const getRefundPolicy = async (req, res) => { } }; +// update refund policy +export const updateRefundPolicy = async (req, res) => { + try { + if (!req?.user) return res.status(400).json({ message: "please login !" }); + + const {content} = req.body; + // id of the refund policy document + const id = req.query.id; + + // object for updated refund policy data + const updatedRefundPolicyData = { + Refundpolicy: content, + addedBy: req.user._id + } + + // update the refund policy in database + const refundPolicy = await Refundpolicy.findByIdAndUpdate( + { _id: id }, + { $set: updatedRefundPolicyData }, + { new: true } + ); + + res.status(200).json({ + success: true, + refundPolicy, + message: "updated successfully ", + }); + } catch (error) { + res.status(500).json({ + success: false, + message: error.message ? error.message : "Something went Wrong", + }); + } +}; + + // Privacy policy controller functions export const AddPrivacyAndPolicy = async (req, res) => { @@ -180,15 +211,24 @@ export const getPrivacyPolicy = async (req, res) => { export const updatePrivacyPolicy = async (req, res) => { try { if (!req?.user) return res.status(400).json({ message: "please login !" }); - // console.log(req?.user) + + // new content const { content } = req.body; - const privacyAndPolicy = await PrivacyAndPolicy.findOneAndUpdate( - { - addedBy: req.user._id, - }, - { - privacyAndPolicyContent: content, - } + + // id of the privacy policy document + const id = req.query.id; + + // object for updated privacy policy data + const updatedPrivacyPolicyData = { + privacyAndPolicyContent: content, + addedBy: req.user._id + } + + // update the privacy policy in database + const privacyAndPolicy = await PrivacyAndPolicy.findByIdAndUpdate( + { _id: id }, + { $set: updatedPrivacyPolicyData }, + { new: true } ); res.status(200).json({ @@ -254,15 +294,23 @@ export const getShipping = async (req, res) => { export const updateShipping = async (req, res) => { try { if (!req?.user) return res.status(400).json({ message: "please login !" }); - // console.log(req?.user) + // new content const { content } = req.body; - const shipping = await Shipping.findOneAndUpdate( - { - addedBy: req.user._id, - }, - { - shippingContent: content, - } + + // id of the shipping policy document + const id = req.query.id; + + // object for updated shipping policy data + const updatedShippingData = { + shippingContent: content, + addedBy: req.user._id + } + + // update the shipping policy in database + const shipping = await Shipping.findByIdAndUpdate( + { _id: id }, + { $set: updatedShippingData }, + { new: true } ); res.status(200).json({ diff --git a/resources/Content/ContentRoutes.js b/resources/Content/ContentRoutes.js index 907dafe..eddc29a 100644 --- a/resources/Content/ContentRoutes.js +++ b/resources/Content/ContentRoutes.js @@ -11,6 +11,7 @@ import { updatePrivacyPolicy, updateShipping, updateTermsAndConditions, + updateRefundPolicy } from "./ContentController.js"; import { isAuthenticatedUser, authorizeRoles } from "../../middlewares/auth.js"; @@ -46,7 +47,10 @@ router router.route("/refund-policy").get(getRefundPolicy); router .route("/refund-policy") - .patch(isAuthenticatedUser, authorizeRoles("admin"), RefundPolicy); + .post(isAuthenticatedUser, authorizeRoles("admin"), RefundPolicy); +router + .route("/refund-policy-update") + .patch(isAuthenticatedUser, authorizeRoles("admin"), updateRefundPolicy); // export default router; diff --git a/resources/Orders/StripeCheckOutController.js b/resources/Orders/StripeCheckOutController.js index b92eb18..f031fe1 100644 --- a/resources/Orders/StripeCheckOutController.js +++ b/resources/Orders/StripeCheckOutController.js @@ -11,7 +11,7 @@ import { shippingAddress } from "../ShippingAddresses/ShippingAddressModel.js"; import sendEmail from "../../Utils/sendEmail.js"; // const endpointSecret = STRIPE_SECRET_KEY; //generate unique order id -const generateUniqueOrderId = async () => { +export const generateUniqueOrderId = async () => { const currentYear = new Date().getFullYear(); // Find the latest order to get the last serial number const latestOrder = await Order.findOne({}, {}, { sort: { orderID: -1 } }); @@ -36,6 +36,7 @@ export const handlePayment = async (req, res) => { if (!email) return res.status(400).send({ message: "Please enter the email" }); const { address, cart, subtotal } = req.body; + if (cart.length < 1) return res.status(400).json({ message: "cart is empty!" }); switch (true) { @@ -48,7 +49,7 @@ export const handlePayment = async (req, res) => { } } let addss = await shippingAddress.findById(address); - console.log(addss?.postalCode); + let shipping = { first_Name: addss.first_Name, last_Name: addss.last_Name, @@ -78,7 +79,7 @@ export const handlePayment = async (req, res) => { shippingInfo: shipping, user: req.user._id, }); - console.log("fffffffff", order, "llllllllll"); + // console.log("fffffffff", order, "llllllllll"); const lineItems = await cart.map((item) => ({ price_data: { currency: "inr", @@ -102,12 +103,19 @@ export const handlePayment = async (req, res) => { // Add any other key-value pairs as needed }, - success_url: `${process.env.FRONTEND_URL}/cart`, - cancel_url: `${process.env.FRONTEND_URL}/error`, + shipping_address_collection: { + allowed_countries: ["IN"], + // Allow only India for INR transactions + }, + billing_address_collection: "required", + success_url: `${process.env.FRONTEND_URL}/order-complete`, // Provide your success URL here + cancel_url: `${process.env.FRONTEND_URL}/cart`, }); // res.json({ sessionId: session.id }); - res.status(200).send({ message: "order created", url: session.url }); + res + .status(200) + .send({ message: "order created", url: session.url, id: session.id }); } } catch (err) { console.log(err); @@ -117,6 +125,7 @@ export const handlePayment = async (req, res) => { export const webhook = async (req, res) => { const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET; + const signature = req.headers["stripe-signature"]; let event; if (webhookSecret) { @@ -134,7 +143,6 @@ export const webhook = async (req, res) => { } if (event.type === "checkout.session.completed") { - // console.log("dddddddddddd", event.data); const findOrder = await Order.findById(event.data.object.metadata?.orderId); findOrder.paypal_payer_id = event.data.object.id; findOrder.paidAt = new Date(event.data.object.created * 1000); @@ -146,20 +154,52 @@ export const webhook = async (req, res) => { } findOrder.orderStatus = "new"; await findOrder.save(); + + // Construct the HTML for the email + const itemRows = findOrder?.orderItems + .map( + (item) => + `${item?.name}${item?.quantity}₹${item?.price}` + ) + .join(""); + + const htmlContent = ` + + Hi ${findOrder?.shippingInfo?.first_Name}, + +

Great news! Your order #${findOrder?.orderID} has been confirmed. Here are the details:

+
+ + + + + + + + + + + ${itemRows} + +
ItemQuantityPrice
+ +

Shipping Address: ${findOrder?.shippingInfo.first_Name} ${findOrder?.shippingInfo.last_Name},${findOrder?.shippingInfo.postalCode}, + ${findOrder?.shippingInfo.street}, + ${findOrder?.shippingInfo.city}, + ${findOrder?.shippingInfo.state}, + ${findOrder?.shippingInfo.country}
Phone number:${findOrder?.shippingInfo.phone_Number}

+

Total: ₹${findOrder.total_amount}

+ +
+ Best regards,
+ Team Smellika`; + + // Send the email await sendEmail({ to: `${event.data.object.customer_email}`, // Change to your recipient - from: `${process.env.SEND_EMAIL_FROM}`, // Change to your verified sender - subject: `Your Order #${findOrder?.orderID} Confirmation`, - html: `

Welcome to Smellika - Let the Shopping Begin!

- Hi ${findOrder?.shippingInfo?.first_Name}, - -

Great news! Your order #${findOrder?.orderID} has been confirmed. Here are the details

-
- Best regards,
- - Team Smellika`, + html: htmlContent, }); // Items: [List of Purchased Items] @@ -176,7 +216,7 @@ export const webhook = async (req, res) => { "---------------------" ); - console.log(`💰 Payment status: ${event.data.object?.payment_status}`); + // console.log(`💰 Payment status: ${event.data.object?.payment_status}`); // Saving the payment details in the database // const payment = await Payment.create({ diff --git a/resources/Orders/orderController.js b/resources/Orders/orderController.js index cf73972..77dd538 100644 --- a/resources/Orders/orderController.js +++ b/resources/Orders/orderController.js @@ -1,3 +1,4 @@ +import sendEmail from "../../Utils/sendEmail.js"; import { Order } from "./orderModel.js"; export const getAllOrder = async (req, res) => { @@ -44,7 +45,7 @@ export const getSingleOrder = async (req, res) => { const order = await Order.findById(req.params.id) .populate({ path: "user", - select: "name -_id", + select: "name email -_id", }) .populate({ path: "shippingInfo.addressId", @@ -67,13 +68,13 @@ export const getSingleOrder = async (req, res) => { //get self User Order export const getUserSelf = async (req, res) => { + if (!req?.user) return res.status(400).json({ message: "please login !" }); try { const order = await Order.find({ - user: req.user._id, + user: req.user?._id, payment_status: "success", - }) - .populate("shippingInfo.addressId") - .sort({ createdAt: -1 }); + }).sort({ createdAt: -1 }); + if (order) { return res.status(200).json({ success: true, @@ -117,6 +118,7 @@ export const deleteOneOrder = async (req, res) => { export const updateOrderStatusById = async (req, res) => { try { let body = { orderStatus: req.body.status }; + const currentDate = new Date(); body["status_timeline." + req.body.status] = currentDate; // if (req.body?.package_weight) body.package_weight = req.body.package_weight; @@ -151,6 +153,21 @@ export const updateOrderStatusById = async (req, res) => { body["courier_name"] = req.body.courierName; body["courier_tracking_id"] = req.body.TrackingID; await Order.findByIdAndUpdate(order._id, body); + await sendEmail({ + to: `${req.body.sendemail}`, // Change to your recipient + + from: `${process.env.SEND_EMAIL_FROM}`, // Change to your verified sender + + subject: `Your Order is On Its Way!`, + html: `

Welcome to Smellika - Let the Shopping Begin!

+ Hi ${req.body?.customerName}, + +

Great news! Your order has been confirmed. Here are the details

+
+ Best regards,
+ + Team Smellika`, + }); return res .status(200) .json({ status: "ok", message: "Order status updated successfully!" }); diff --git a/resources/Orders/orderModel.js b/resources/Orders/orderModel.js index dad4f86..3268b67 100644 --- a/resources/Orders/orderModel.js +++ b/resources/Orders/orderModel.js @@ -117,7 +117,7 @@ const orderSchema = new mongoose.Schema( "cancelled", "returned", ], - // default: "new", + default: "new", }, // paypal_payer_id: { type: String }, diff --git a/resources/Orders/orderRoute.js b/resources/Orders/orderRoute.js index 2d97e50..32cd99a 100644 --- a/resources/Orders/orderRoute.js +++ b/resources/Orders/orderRoute.js @@ -33,9 +33,7 @@ router.route("/:orderID/capture/payment").post(captureOrderPayment); // ----------------------stripe checkOut-----------------// // app.post("/webhook", express.raw({ type: "application/json" }), webhook); -router - .route("/stripe-checkout-session") - .post(isAuthenticatedUser, handlePayment); +router.route("/stripe-checkout").post(isAuthenticatedUser, handlePayment); router .route("/webhook") .post(express.raw({ type: "application/json" }), webhook); diff --git a/resources/Products/ProductController.js b/resources/Products/ProductController.js index 887ed0c..9fb4748 100644 --- a/resources/Products/ProductController.js +++ b/resources/Products/ProductController.js @@ -1,7 +1,7 @@ import { Product } from "./ProductModel.js"; import cloudinary from "../../Utils/cloudinary.js"; import { v4 as uuidv4 } from "uuid"; - +import { CategoryModel } from "../Category/CategoryModel.js"; export const createProduct = async (req, res) => { try { if (!req.files) { @@ -75,6 +75,48 @@ export const getAllProduct = async (req, res) => { }); } }; +// get all product with device products first +export const getAllProductsDevicesFirst = async (req, res) => { + try { + // we want products with category name Device to be displayed first, so i have first found the products with category name Devices then made another request to find all products and filtered products with category devices , then merged both arrays so we get devices first then all other categories + + const categoryName = 'Devices'; + // Find the category object by name first + const category = await CategoryModel.findOne({ categoryName }); + + if (!category) { + throw new Error("Category not found"); + } + // products with device category + const deviceProducts = await Product.find({ category: category._id }).populate('category'); + + // all products + const allProducts = await Product.find() + .populate({ + path: "category gst addedBy", + select: "name categoryName tax", + }) + .sort({ + createdAt: -1, + }); + // filtering out products with device category + const filteredProducts = allProducts.filter((ele) => { return ele.category?.categoryName !== categoryName }) + + // merging both deviceProcuts and filtered products + const product = deviceProducts.concat(filteredProducts) + if (product) { + return res.status(200).json({ + success: true, + product, + }); + } + } catch (error) { + res.status(500).json({ + success: false, + msg: error.message ? error.message : "Something went wrong!", + }); + } +}; //get One Product export const getOneProduct = async (req, res) => { try { @@ -98,8 +140,109 @@ export const getOneProduct = async (req, res) => { }; // 3.update Product +// export const updateProduct = async (req, res) => { +// const { +// name, +// description, +// price, +// category, +// image, +// gst_amount, +// gst, +// total_amount, +// } = req.body; +// console.log(gst_amount, gst, total_amount); + +// try { +// // Prepare an array for the images +// const jsonArray = JSON.parse(image); +// const AllImages = jsonArray.map(({ public_id, url }) => ({ +// public_id, +// url, +// })); + +// if (req.files && req.files.newImages) { +// const newuploadImages = Array.isArray(req.files.newImages) +// ? req.files.newImages +// : [req.files.newImages]; + +// const imagesLinks = []; + +// for (let i = 0; i < newuploadImages.length; i++) { +// const result = await cloudinary.v2.uploader.upload( +// newuploadImages[i].tempFilePath, +// { +// folder: "smellica/product", +// } +// ); + +// imagesLinks.push({ +// public_id: result.public_id, +// url: result.secure_url, +// }); +// } + +// // Combine the existing images and the newly uploaded images +// const updatedImages = [...AllImages, ...imagesLinks]; + +// // Perform the product update +// const ModifyProduct = await Product.findOneAndUpdate( +// { _id: req.params.id }, +// { +// $set: { +// name, +// description, +// price, +// category, +// image: updatedImages, +// gst, +// gst_amount, +// total_amount, +// }, +// }, +// { new: true } +// ); +// return res.status(200).json({ +// success: true, +// ModifyProduct, +// }); +// } else { +// const ModifyProduct = await Product.findOneAndUpdate( +// { _id: req.params.id }, +// { +// $set: { +// name, +// description, +// price, +// category, +// image: AllImages, +// }, +// }, +// { new: true } +// ); +// return res.status(200).json({ +// success: true, +// ModifyProduct, +// }); +// } +// } catch (error) { +// res.status(500).json({ +// success: false, +// msg: error.message ? error.message : "Something went wrong!", +// }); +// } +// }; export const updateProduct = async (req, res) => { - const { name, description, price, category, image } = req.body; + const { + name, + description, + price, + category, + image, + gst_amount, + gst, + total_amount, + } = req.body; try { // Prepare an array for the images @@ -109,16 +252,18 @@ export const updateProduct = async (req, res) => { url, })); + let updatedImages = AllImages; + if (req.files && req.files.newImages) { - const newuploadImages = Array.isArray(req.files.newImages) + const newUploadImages = Array.isArray(req.files.newImages) ? req.files.newImages : [req.files.newImages]; const imagesLinks = []; - for (let i = 0; i < newuploadImages.length; i++) { + for (let i = 0; i < newUploadImages.length; i++) { const result = await cloudinary.v2.uploader.upload( - newuploadImages[i].tempFilePath, + newUploadImages[i].tempFilePath, { folder: "smellica/product", } @@ -131,46 +276,37 @@ export const updateProduct = async (req, res) => { } // Combine the existing images and the newly uploaded images - const updatedImages = [...AllImages, ...imagesLinks]; - - // Perform the product update - const ModifyProduct = await Product.findOneAndUpdate( - { _id: req.params.id }, - { - $set: { - name, - description, - price, - category, - image: updatedImages, - }, - }, - { new: true } - ); - return res.status(200).json({ - success: true, - ModifyProduct, - }); - } else { - const ModifyProduct = await Product.findOneAndUpdate( - { _id: req.params.id }, - { - $set: { - name, - description, - price, - category, - image: AllImages, - }, - }, - { new: true } - ); - return res.status(200).json({ - success: true, - ModifyProduct, - }); + updatedImages = [...AllImages, ...imagesLinks]; } + + // Perform the product update + const updatedProduct = await Product.findOneAndUpdate( + { _id: req.params.id }, + { + $set: { + name, + description, + price, + category, + image: updatedImages, + gst, + gst_amount, + total_amount, + }, + }, + { new: true } + ); + + if (!updatedProduct) { + return res.status(404).json({ success: false, msg: "Product not found" }); + } + + return res.status(200).json({ + success: true, + updatedProduct, + }); } catch (error) { + console.error("Error updating product:", error); res.status(500).json({ success: false, msg: error.message ? error.message : "Something went wrong!", @@ -240,13 +376,20 @@ export const deleteProduct = async (req, res) => { }); } }; + export const getProductsByCategory = async (req, res) => { const { categoryName } = req.params; // Assuming category name is in the route + // console.log(categoryName); try { - const products = await Product.find({ - category: categoryName, - }).sort({ createdAt: -1 }); + // Find the category object by name first + const category = await CategoryModel.findOne({ categoryName }); + + if (!category) { + throw new Error("Category not found"); + } + const products = await Product.find({ category: category._id }).populate('category'); + // console.log(products); if (products && products.length > 0) { return res.status(200).json({ diff --git a/resources/Products/ProductModel.js b/resources/Products/ProductModel.js index 24469a4..4780c94 100644 --- a/resources/Products/ProductModel.js +++ b/resources/Products/ProductModel.js @@ -5,7 +5,7 @@ const productSchema = new Schema( { name: { type: String, - maxLength: [25, "name cannot exceed 25 characters"], + maxLength: [35, "name cannot exceed 25 characters"], required: [true, "Please Enter product Name"], trim: true, }, @@ -15,7 +15,7 @@ const productSchema = new Schema( }, description: { type: String, - maxLength: [100, "description cannot exceed 100 characters"], + maxLength: [400, "description cannot exceed 100 characters"], required: [true, "Please Enter product Description"], }, price: { diff --git a/resources/Products/ProductRoute.js b/resources/Products/ProductRoute.js index 220f4e3..2159d1f 100644 --- a/resources/Products/ProductRoute.js +++ b/resources/Products/ProductRoute.js @@ -7,6 +7,7 @@ import { getOneProduct, deleteImageFromCloudinary, getProductsByCategory, + getAllProductsDevicesFirst, } from "./ProductController.js"; const router = express.Router(); import { isAuthenticatedUser, authorizeRoles } from "../../middlewares/auth.js"; @@ -14,6 +15,7 @@ router .route("/product/create/") .post(isAuthenticatedUser, authorizeRoles("admin"), createProduct); router.route("/product/getAll/").get(getAllProduct); +router.route("/product/getAllProductsDevicesFrist/").get(getAllProductsDevicesFirst); router.route("/product/getOne/:id").get(getOneProduct); router .route("/product/update/:id") diff --git a/resources/SEO&Analytics/SEOController.js b/resources/SEO&Analytics/SEOController.js new file mode 100644 index 0000000..8a25cfe --- /dev/null +++ b/resources/SEO&Analytics/SEOController.js @@ -0,0 +1,39 @@ +import {SeoRequest} from "./SEOModel.js"; + +export const AddNewSeoRequest = async (req, res) => { + try { + + let existingSeoRequest = await SeoRequest.findOne(); + + if (existingSeoRequest) { + + existingSeoRequest.GoogleTag = req.body.GoogleTag; + existingSeoRequest.FacebookPixel = req.body.FacebookPixel; + existingSeoRequest.GoogleAnalytics = req.body.GoogleAnalytics; + existingSeoRequest.MicrosoftClarity=req.body.MicrosoftClarity; + + + existingSeoRequest = await existingSeoRequest.save(); + + res.status(200).json({ + success: true, + seorequest: existingSeoRequest, + message: "Seo Request Updated", + }); + } else { + + const newSeoRequest = await SeoRequest.create(req.body); + + res.status(201).json({ + success: true, + seorequest: newSeoRequest, + message: "Seo Request Added", + }); + } + } catch (error) { + res.status(500).json({ + success: false, + message: error.message ? error.message : "Something went Wrong", + }); + } +}; diff --git a/resources/SEO&Analytics/SEOModel.js b/resources/SEO&Analytics/SEOModel.js new file mode 100644 index 0000000..625cb84 --- /dev/null +++ b/resources/SEO&Analytics/SEOModel.js @@ -0,0 +1,33 @@ +import mongoose from "mongoose"; + +const { Schema, model } = mongoose; + +const SeoRequestSchema = new mongoose.Schema( + { + + GoogleTag: { + type: String, + maxLength: [25, "tag cannot exceed 25 characters"], + required: [true, "Please Enter google tag "], + }, + FacebookPixel: { + type: String, + maxLength: [25, "tag cannot exceed 25 characters"], + required: [true, "Please Enter Facebook Pixel "], + }, + GoogleAnalytics: { + type: String, + maxLength: [500, "google analytics cannot exceed 500 characters"], + required: [true, "Please Enter google analytics"], + }, + MicrosoftClarity: { + type: String, + maxLength: [500, "Microsoft clarity cannot exceed 500 characters"], + required: [true, "Please Enter microsoft clarity"], + }, + + }, + { timestamps: true, versionKey: false } +); + +export const SeoRequest = mongoose.model("SeoRequest", SeoRequestSchema); diff --git a/resources/SEO&Analytics/SEORouter.js b/resources/SEO&Analytics/SEORouter.js new file mode 100644 index 0000000..33bf606 --- /dev/null +++ b/resources/SEO&Analytics/SEORouter.js @@ -0,0 +1,12 @@ +import express from "express"; +import { isAuthenticatedUser, authorizeRoles } from "../../middlewares/auth.js"; +import { AddNewSeoRequest } from "./SEOController.js"; + +const router = express.Router(); + +router + .route("/new") + .post(isAuthenticatedUser, authorizeRoles("admin"), AddNewSeoRequest); + + +export default router; diff --git a/resources/StripePayment/stripeController.js b/resources/StripePayment/stripeController.js new file mode 100644 index 0000000..eba43e0 --- /dev/null +++ b/resources/StripePayment/stripeController.js @@ -0,0 +1,11 @@ +import { createCheckoutSession } from "./stripeModel.js"; + +export async function createCheckoutSessionController(req, res) { + try { + const sessionId = await createCheckoutSession(req.body); + res.json({ id: sessionId }); + } catch (error) { + console.error("Error creating checkout session:", error); + res.status(500).json({ error: "Failed to create checkout session" }); + } +} diff --git a/resources/StripePayment/stripeModel.js b/resources/StripePayment/stripeModel.js new file mode 100644 index 0000000..47bfc59 --- /dev/null +++ b/resources/StripePayment/stripeModel.js @@ -0,0 +1,27 @@ +import Stripe from "stripe"; + +const stripe = new Stripe(process.env.STRIPE_SECRET); + +export async function createCheckoutSession(body) { + const lineItems = body.products.map(({ product, quantity }) => ({ + price_data: { + currency: "usd", + product_data: { + name: product.name, + images: [product.image[0].url], // assuming you want to use the first image URL + }, + unit_amount: Math.round(product.price * 100), // Ensure proper conversion to cents + }, + quantity: quantity, + })); + + const session = await stripe.checkout.sessions.create({ + payment_method_types: ["card"], + line_items: lineItems, + mode: "payment", + success_url: "http://localhost:5173/order-complete", // Provide your success URL here + cancel_url: "http://localhost:5173/cart", // Provide your cancel URL here + }); + + return session.id; +} diff --git a/resources/StripePayment/stripeRoute.js b/resources/StripePayment/stripeRoute.js new file mode 100644 index 0000000..c22bae6 --- /dev/null +++ b/resources/StripePayment/stripeRoute.js @@ -0,0 +1,8 @@ +import express from "express"; +import { createCheckoutSessionController } from "./stripeController.js"; + +const router = express.Router(); + +router.post("/create-checkout-session", createCheckoutSessionController); + +export default router; diff --git a/resources/Supports/supportController.js b/resources/Supports/supportController.js new file mode 100644 index 0000000..d25f406 --- /dev/null +++ b/resources/Supports/supportController.js @@ -0,0 +1,292 @@ +import { Support } from "./supportModel.js"; +import cloudinary from "../../Utils/cloudinary.js"; + +export const createSupport = async (req, res) => { + // console.log(req.body); + // console.log(req.files.image); + try { + // const { ticketId, createdOn, subject, description } = req.body; + if(req.files && req.files.image){ + let images = []; + let Allfiles = req.files.image; + if (typeof Allfiles.tempFilePath === "string") { + let filepath = Allfiles.tempFilePath; + + images.push(filepath); + } else { + Allfiles.map((item) => { + images.push(item.tempFilePath); + }); + } + + const imagesLinks = []; + for (let i = 0; i < images.length; i++) { + const result = await cloudinary.v2.uploader.upload(images[i], { + folder: "smellica/CustomerSupport", + }); + + imagesLinks.push({ + public_id: result.public_id, + url: result.secure_url, + }); + } + req.body.image = imagesLinks; + } + req.body.addedBy = req.user._id; + // Check if any required field is missing + // if (!ticketId || !createdOn || !subject || !description) { + // return res.status(400).json({ + // success: false, + // msg: "All fields are required.", + // }); + // } + + // Create the support ticket + // const support = await Support.create({ + // ticketId, + // createdOn, + // subject, + // description, + // addedBy: req.user._id, + // image: imagesLinks, + // }); + const support = await Support.create({ ...req.body }); + // Return the created support ticket + res.status(201).json({ + success: true, + data: support, + msg: "Support ticket created successfully.", + }); + } catch (error) { + console.error(error); + // Handle errors + res.status(500).json({ + success: false, + msg: error.message, + }); + } +}; +// **************************** + +export const getAllSupportTicket = async (req, res) => { + try { + // Use the find method to retrieve all support tickets + const support = await Support.find().sort({ createdAt: -1 }); + + // Check if support tickets were found + if (support) { + return res.status(200).json({ + success: true, + support, + }); + } + } catch (error) { + // Handle errors + // console.error(error); + res.status(500).json({ + success: false, + msg: error.message ? error.message : "Something went wrong!", + }); + } +}; +export const getOneSupportTicket = async (req, res) => { + try { + // console.log(req.params.id); + const support = await Support.findOne({ ticketId: req.params.id }); + if (support) { + return res.status(200).json({ + success: true, + support, + }); + } else { + return res.status(404).json({ + success: false, + msg: "Support ticket not found", + }); + } + } catch (error) { + res.status(500).json({ + success: false, + msg: error.message ? error.message : "Something went wrong!", + }); + } +}; + +// ************************8 + +export const getAllSupportTicketofuser = async (req, res) => { + try { + // Retrieve the user ID from the request + const userId = req.user._id; + + // Use the find method to retrieve all support tickets created by the user + const support = await Support.find({ addedBy: userId }).sort({ + createdAt: -1, + }); + + // Check if support tickets were found + if (support) { + return res.status(200).json({ + success: true, + support, + }); + } else { + return res.status(404).json({ + success: false, + msg: "No support tickets found for the user.", + }); + } + } catch (error) { + // Handle errors + // console.error(error); + res.status(500).json({ + success: false, + msg: error.message ? error.message : "Something went wrong!", + }); + } +}; + +// ************************8 + +export const deleteSupport = async (req, res) => { + try { + if (!req.params.id) { + return res.status(400).json({ + success: false, + msg: "Please Provide Support ID!", + }); + } + // console.log(req.params.id); + const getSupport = await Support.findById(req.params.id); + if (!getSupport) { + return res.status(404).json({ + success: false, + msg: "Support not Found!", + }); + } + // Deleting Images From Cloudinary + for (let i = 0; i < getSupport.image.length; i++) { + await cloudinary.v2.uploader.destroy(getSupport.image[i].public_id); + } + + //-------------------------// + const supportticket = await Support.findByIdAndDelete(req.params.id); + if (!supportticket) { + return res.status(404).json({ message: "Support Not Found" }); + } + await supportticket.remove(); + res.status(200).json({ + success: true, + msg: "Support Deleted Successfully!!", + }); + } catch (error) { + res.status(500).json({ + success: false, + msg: error.message ? error.message : "Something went wrong!", + }); + } +}; + +export const updateSupport = async (req, res) => { + try { + const { status, message } = req.body; + // console.log(req.params.id); + // Prepare an array for the images + // const jsonArray = JSON.parse(image); + // const AllImages = jsonArray.map(({ public_id, url }) => ({ + // public_id, + // url, + // })); + + // if (req.files && req.files.newImages) { + // const newuploadImages = Array.isArray(req.files.newImages) + // ? req.files.newImages + // : [req.files.newImages]; + + // const imagesLinks = []; + + // for (let i = 0; i < newuploadImages.length; i++) { + // const result = await cloudinary.v2.uploader.upload( + // newuploadImages[i].tempFilePath, + // { + // folder: "smellica/product", + // } + // ); + + // imagesLinks.push({ + // public_id: result.public_id, + // url: result.secure_url, + // }); + // } + + // Combine the existing images and the newly uploaded images + // const updatedImages = [...AllImages, ...imagesLinks]; + + // Perform the product update + // Find the support ticket by ID + const supportTicket = await Support.findOne({ ticketId: req.params.id }); + // Check if the support ticket exists + if (!supportTicket) { + return res.status(404).json({ + success: false, + msg: "Support ticket not found", + }); + } + + // Update the support ticket fields + if (status) { + supportTicket.status = status; + } + if (message) { + const newMessage = { + message: message.message, + user: message.user, + replyDate: message.replyDate, // Add a timestamp to the message object + }; + supportTicket.message.push(newMessage); + // Update the last reply to the timestamp of the new message if the user is admin + if (message.user === "admin") { + supportTicket.lastreply = newMessage.replyDate; + } + } + + // Save the updated support ticket + const updatedSupportTicket = await supportTicket.save(); + + return res.status(200).json({ + success: true, + updatedSupportTicket, + }); + } catch (error) { + // Handle errors + // console.error(error); + res.status(500).json({ + success: false, + msg: error.message ? error.message : "Something went wrong!", + }); + } +}; +export const deleteImageFromCloudinary = async (req, res) => { + const { public_id } = req.params; + + try { + if (!public_id) { + return res.status(400).json({ + success: false, + msg: "Please Provide Product ID!", + }); + } + const response = await cloudinary.v2.uploader.destroy(public_id); + if (response) { + res.status(200).json({ + success: true, + msg: "CustomerSupport Deleted Successfully!!", + }); + } + } catch (error) { + res.status(500).json({ + success: false, + msg: error.message ? error.message : "Something went wrong!", + }); + } +}; \ No newline at end of file diff --git a/resources/Supports/supportModel.js b/resources/Supports/supportModel.js new file mode 100644 index 0000000..25892b0 --- /dev/null +++ b/resources/Supports/supportModel.js @@ -0,0 +1,67 @@ +import mongoose from "mongoose"; +const { Schema, model } = mongoose; +const supportSchema = new Schema( + { + ticketId: { + type: String, + required: true, + unique: true, + }, + addedBy: { + type: Schema.Types.ObjectId, + ref: "User", + required: true, + }, + subject: { + type: String, + required: true, + }, + description: { + type: String, + maxLength: [100, "description cannot exceed 100 characters"], + required: [true, "Please Enter product Description"], + }, + createdOn: { + type: String, + }, + lastreply: { + type: String, + }, + status: { + type: String, + enum: ["Open", "Close"], + default: "Open", + }, + image: [ + { + public_id: { + type: String, + // required: true, + }, + url: { + type: String, + // required: true, + }, + }, + ], + message: [ + { + message: { + type: String, + default: "", + }, + user: { + type: String, + enum: ["admin", "user"], + default: "user", + }, + replyDate: { + type: String, + }, + }, + ], + }, + { timestamps: true, versionKey: false } +); + +export const Support = model("Support", supportSchema); diff --git a/resources/Supports/supportRoute.js b/resources/Supports/supportRoute.js new file mode 100644 index 0000000..3cacdb0 --- /dev/null +++ b/resources/Supports/supportRoute.js @@ -0,0 +1,32 @@ +import bodyParser from "body-parser"; +import { createSupport, deleteImageFromCloudinary, deleteSupport, getAllSupportTicket, getAllSupportTicketofuser, getOneSupportTicket, updateSupport } from "./supportController.js"; +import { isAuthenticatedUser, authorizeRoles } from "../../middlewares/auth.js"; +import express from "express"; + +const app = express(); + +// Configure bodyParser to parse the raw request body as a buffer +app.use(bodyParser.raw({ type: "application/json" })); + +const router = express.Router(); +//checkout Routes-------------------------// +router.route("/support/create/").post(isAuthenticatedUser,createSupport); +router.route("/support/getAll/").get(isAuthenticatedUser, authorizeRoles("admin"),getAllSupportTicket); +router.route("/support/userticket/").get(isAuthenticatedUser,getAllSupportTicketofuser); +router + .route("/support/delete/:id") + .delete( deleteSupport); + router.route("/support/getOne/:id").get(isAuthenticatedUser, getOneSupportTicket); + router + .route("/support/update/:id") + .patch(isAuthenticatedUser, updateSupport); + router + .route("/support/deleteImage/jatinMor/CustomerSupport/:public_id") + .delete( + isAuthenticatedUser, + authorizeRoles("admin"), + deleteImageFromCloudinary + ); +// --------------------------------------------------------- + +export default router; diff --git a/resources/Testimonials/TestimonialController.js b/resources/Testimonials/TestimonialController.js index 98798d8..2268ecf 100644 --- a/resources/Testimonials/TestimonialController.js +++ b/resources/Testimonials/TestimonialController.js @@ -3,7 +3,7 @@ import { Testimonial } from "./TestimonialModel.js"; export const AddNewTestimonial = async (req, res) => { try { if (!req?.user) return res.status(400).json({ message: "please login !" }); - // console.log(req?.user) + // console.log(req?.user); if (req.files) { let getImg = req.files.image; @@ -36,8 +36,6 @@ export const AddNewTestimonial = async (req, res) => { export const FindAllTestimonial = async (req, res) => { try { - if (!req?.user) return res.status(400).json({ message: "please login !" }); - // console.log(req?.user) const testimonial = await Testimonial.find().sort({ createdAt: -1 }); if (testimonial) { @@ -68,6 +66,7 @@ export const FindOneTestimonial = async (req, res) => { return res.status(400).json({ message: "please give ID !" }); const testimonial = await Testimonial.findById(req.params.id); + // console.log(testimonial); if (testimonial) { return res.status(200).json({ success: true, @@ -82,3 +81,125 @@ export const FindOneTestimonial = async (req, res) => { }); } }; + +// 3.update testimonials +export const updatetesTimonial = async (req, res) => { + try { + // Check if the user is authenticated + if (!req.user) { + return res.status(400).json({ message: "Please login!" }); + } + + // Destructure request body + const { name, company, testimonial } = req.body; + + // Get the authenticated user's ID + const userId = req.user._id; + + // Prepare an object for the updated testimonial data + const updatedTestimonialData = { + name, + company, + testimonial, + user: userId, // Assign the authenticated user's ID to the testimonial's user field + }; + + // Check if files are uploaded + if (req.files && req.files.image) { + // If image file is uploaded, upload it to cloudinary + const uploadedImage = req.files.image; + const result = await cloudinary.v2.uploader.upload( + uploadedImage.tempFilePath, + { + folder: "GetSygnal/Testimonial", + } + ); + + // Prepare the image object with public_id and url + const image = { + public_id: result.public_id, + url: result.secure_url, + }; + + // Assign the uploaded image to the testimonial's image field + updatedTestimonialData.image = image; + } + // console.log(updatedTestimonialData); + // Update the testimonial in the database + const modifiedTestimonial = await Testimonial.findOneAndUpdate( + { _id: req.params.id }, + { $set: updatedTestimonialData }, + { new: true } + ); + + return res.status(200).json({ + success: true, + ModifyTestimonial: modifiedTestimonial, + }); + } catch (error) { + res.status(500).json({ + success: false, + msg: error.message ? error.message : "Something went wrong!", + }); + } +}; + +export const deleteImageFromCloudinary = async (req, res) => { + const { public_id } = req.params; + + try { + if (!public_id) { + return res.status(400).json({ + success: false, + msg: "Please Provide Product ID!", + }); + } + const response = await cloudinary.v2.uploader.destroy(public_id); + if (response) { + res.status(200).json({ + success: true, + msg: "Product Deleted Successfully!!", + }); + } + } catch (error) { + res.status(500).json({ + success: false, + msg: error.message ? error.message : "Something went wrong!", + }); + } +}; + +//delete one Product +export const deleteTestimonial = async (req, res) => { + try { + if (!req?.user) return res.status(400).json({ message: "please login !" }); + // console.log(req?.user) + if (!req.params.id) + return res.status(400).json({ message: "please give ID !" }); + // console.log(req.params.id) + const gettestimonial = await Testimonial.findById(req.params.id); + // console.log(gettestimonial) + if (!gettestimonial) { + return res + .status(404) + .json({ success: false, msg: "Testimonial not Found!" }); + } + // Deleting Images From Cloudinary + await cloudinary.v2.uploader.destroy(gettestimonial.image.public_id); + + //-------------------------// + const testimonial = await Testimonial.findByIdAndDelete(req.params.id); + if (!testimonial) { + return res.status(404).json({ message: "Testimonial Not Found" }); + } + await testimonial.remove(); + res + .status(200) + .json({ success: true, msg: "Testimonial Deleted Successfully!!" }); + } catch (error) { + res.status(500).json({ + success: false, + msg: error.message ? error.message : "Something went wrong!", + }); + } +}; diff --git a/resources/Testimonials/TestimonialRoute.js b/resources/Testimonials/TestimonialRoute.js index 90fec8c..9dda39f 100644 --- a/resources/Testimonials/TestimonialRoute.js +++ b/resources/Testimonials/TestimonialRoute.js @@ -4,6 +4,9 @@ import { AddNewTestimonial, FindAllTestimonial, FindOneTestimonial, + deleteImageFromCloudinary, + deleteTestimonial, + updatetesTimonial, } from "./TestimonialController.js"; const router = express.Router(); @@ -11,9 +14,19 @@ const router = express.Router(); router.route("/new").post(isAuthenticatedUser, AddNewTestimonial); router .route("/getAll") - .get(isAuthenticatedUser, authorizeRoles("admin"), FindAllTestimonial); + .get(FindAllTestimonial); router.route("/getOne/:id").get(isAuthenticatedUser, FindOneTestimonial); - -// router.route("/product/getAll/").get(getAllProduct) - +router + .route("/delete/:id") + .delete(isAuthenticatedUser, authorizeRoles("admin"), deleteTestimonial); + router + .route("/update/:id") + .patch(isAuthenticatedUser, authorizeRoles("admin"), updatetesTimonial); + router + .route("/deleteImage/GetSygnal/Testimonial/:public_id") + .delete( + isAuthenticatedUser, + authorizeRoles("admin"), + deleteImageFromCloudinary + ); export default router;