diff --git a/src/databases/seeders/20240604133044-orders.ts b/src/databases/seeders/20240604133044-orders.ts index b4f41fe7..f47efd5c 100644 --- a/src/databases/seeders/20240604133044-orders.ts +++ b/src/databases/seeders/20240604133044-orders.ts @@ -1,6 +1,6 @@ /* eslint-disable comma-dangle */ import { QueryInterface } from "sequelize"; -import { cartOneId, orderOneId, orderTwoId, shopOneId,productTwoId,productOneId } from "../../types/uuid"; +import { cartOneId, orderOneId, orderTwoId, shopOneId,productTwoId,productOneId, orderThreeId, orderFourId, orderFiveId, orderSixId, orderSevenId, orderEightId, orderNineId, shopTwoId } from "../../types/uuid"; module.exports = { async up(queryInterface: QueryInterface) { @@ -48,7 +48,161 @@ module.exports = { expectedDeliveryDate: new Date("2024-08-05"), createdAt: new Date(), updatedAt: new Date() - } + }, + { + id: orderThreeId, + products:JSON.stringify( [ + { + productId:productOneId, + status:"pending" + }, + { + productId:productTwoId, + status:"pending" + } + ]), + shopId: shopOneId, + cartId: cartOneId, + paymentMethodId: 2, + orderDate: new Date("2024-01-15"), + status: "canceled", + shippingProcess : "The Order is canceled", + expectedDeliveryDate: new Date("2024-08-05"), + createdAt: new Date(), + updatedAt: new Date() + }, + { + id: orderFourId, + products:JSON.stringify( [ + { + productId:productOneId, + status:"pending" + }, + { + productId:productTwoId, + status:"pending" + } + ]), + shopId: shopTwoId, + cartId: cartOneId, + paymentMethodId: 2, + orderDate: new Date("2024-05-15"), + status: "canceled", + shippingProcess : "The Order is canceled", + expectedDeliveryDate: new Date("2024-08-05"), + createdAt: new Date(), + updatedAt: new Date() + }, + { + id: orderFiveId, + products:JSON.stringify( [ + { + productId:productOneId, + status:"pending" + }, + { + productId:productTwoId, + status:"pending" + } + ]), + shopId: shopTwoId, + cartId: cartOneId, + paymentMethodId: 2, + orderDate: new Date("2024-02-15"), + status: "completed", + shippingProcess : "your order have reached Kigali in 30 minutes it will be reached to you", + expectedDeliveryDate: new Date("2024-08-05"), + createdAt: new Date(), + updatedAt: new Date() + }, + { + id: orderSixId, + products:JSON.stringify( [ + { + productId:productOneId, + status:"pending" + }, + { + productId:productTwoId, + status:"pending" + } + ]), + shopId: shopTwoId, + cartId: cartOneId, + paymentMethodId: 2, + orderDate: new Date("2024-07-15"), + status: "completed", + shippingProcess : "your order have reached Kigali in 30 minutes it will be reached to you", + expectedDeliveryDate: new Date("2024-08-05"), + createdAt: new Date(), + updatedAt: new Date() + }, + { + id: orderSevenId, + products:JSON.stringify( [ + { + productId:productOneId, + status:"pending" + }, + { + productId:productTwoId, + status:"pending" + } + ]), + shopId: shopTwoId, + cartId: cartOneId, + paymentMethodId: 2, + orderDate: new Date("2024-07-15"), + status: "canceled", + shippingProcess : "The Order is canceled", + expectedDeliveryDate: new Date("2024-08-05"), + createdAt: new Date(), + updatedAt: new Date() + }, + { + id: orderEightId, + products:JSON.stringify( [ + { + productId:productOneId, + status:"pending" + }, + { + productId:productTwoId, + status:"pending" + } + ]), + shopId: shopTwoId, + cartId: cartOneId, + paymentMethodId: 2, + orderDate: new Date("2024-07-15"), + status: "canceled", + shippingProcess : "The Order is canceled", + expectedDeliveryDate: new Date("2024-08-05"), + createdAt: new Date(), + updatedAt: new Date() + }, + { + id: orderNineId, + products:JSON.stringify( [ + { + productId:productOneId, + status:"pending" + }, + { + productId:productTwoId, + status:"pending" + } + ]), + shopId: shopTwoId, + cartId: cartOneId, + paymentMethodId: 2, + orderDate: new Date("2024-08-02"), + status: "canceled", + shippingProcess : "The Order is canceled", + expectedDeliveryDate: new Date("2024-08-05"), + createdAt: new Date(), + updatedAt: new Date() + }, ], {}); }, diff --git a/src/index.spec.ts b/src/index.spec.ts index 48f6abbb..ed6ef37d 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -356,7 +356,7 @@ describe("checkPasswordExpiration middleware", () => { expect(sendEmailStub).to.have.been.calledOnceWith( "user@example.com", "Password Expired - Reset Required", - `Your password has expired. Please reset your password using the following link: ${process.env.SERVER_URL_PRO}/api/auth/reset-password` + `Your password has expired. Please reset your password using the following link: ${process.env.SERVER_URL_PRO}/reset-password` ); expect(res.status).to.have.been.calledWith(httpStatus.FORBIDDEN); expect(res.json).to.have.been.calledWith({ @@ -394,3 +394,144 @@ describe("checkPasswordExpiration middleware", () => { expect(next).to.not.have.been.called; }); }); + + + +import { Request, Response } from 'express'; + + + +const paymentSuccess = (req: Request, res: Response) => { + try { + res.status(httpStatus.OK).json({ status: httpStatus.OK, message: "Payment successful!" }); + } catch (error) { + res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, message: error.message }); + } +}; + +const paymentCanceled = (req: Request, res: Response) => { + try { + res.status(httpStatus.OK).json({ status: httpStatus.OK, message: "Payment canceled" }); + } catch (error) { + res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, message: error.message }); + } +}; + +describe('Payment Controllers', () => { + let req: Partial; + let res: Partial; + let statusStub: sinon.SinonStub; + let jsonStub: sinon.SinonStub; + + beforeEach(() => { + req = {}; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + statusStub = res.status as sinon.SinonStub; + jsonStub = res.json as sinon.SinonStub; + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('paymentSuccess', () => { + it('should return 200 with success message', () => { + paymentSuccess(req as Request, res as Response); + + expect(statusStub.calledOnceWith(httpStatus.OK)).to.be.true; + expect(jsonStub.calledOnceWith({ status: httpStatus.OK, message: "Payment successful!" })).to.be.true; + }); + }); + + describe('paymentCanceled', () => { + it('should return 200 with canceled message', () => { + paymentCanceled(req as Request, res as Response); + + expect(statusStub.calledOnceWith(httpStatus.OK)).to.be.true; + expect(jsonStub.calledOnceWith({ status: httpStatus.OK, message: "Payment canceled" })).to.be.true; + }); + }); +}); + + + + + + + + + + +const userRepositories = { + findAddressByUserId: sinon.stub(), + addUserAddress: sinon.stub(), + updateUserAddress: sinon.stub(), +}; + +const changeUserAddress = async (req: any, res: Response) => { + try { + const isAddressFound = await userRepositories.findAddressByUserId(req.user.id); + let createdAddress; + if (!isAddressFound) { + createdAddress = await userRepositories.addUserAddress({ ...req.body, userId: req.user.id }); + } else { + createdAddress = await userRepositories.updateUserAddress(req.body, req.user.id); + } + return res + .status(httpStatus.OK) + .json({ + status: httpStatus.OK, + message: `${isAddressFound ? "Address updated successfully" : "Address added successfully"}`, + data: { address: createdAddress }, + }); + } catch (error) { + return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ + status: httpStatus.INTERNAL_SERVER_ERROR, + message: error.message, + }); + } +}; + +describe('changeUserAddress', () => { + let req: Partial; + let res: Partial; + let statusStub: sinon.SinonStub; + let jsonStub: sinon.SinonStub; + + beforeEach(() => { + req = { + user: { id: 'user123' }, + body: { street: '123 Main St', city: 'Sample City' }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + statusStub = res.status as sinon.SinonStub; + jsonStub = res.json as sinon.SinonStub; + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should add a new address if not found', async () => { + userRepositories.findAddressByUserId.resolves(null); + userRepositories.addUserAddress.resolves({ id: 'address123', ...req.body }); + + await changeUserAddress(req as Request, res as Response); + + expect(userRepositories.findAddressByUserId.calledOnceWith(req.user.id)).to.be.true; + expect(userRepositories.addUserAddress.calledOnceWith({ ...req.body, userId: req.user.id })).to.be.true; + expect(userRepositories.updateUserAddress.notCalled).to.be.true; + expect(statusStub.calledOnceWith(httpStatus.OK)).to.be.true; + expect(jsonStub.calledOnceWith({ + status: httpStatus.OK, + message: 'Address added successfully', + data: { address: { id: 'address123', ...req.body } }, + })).to.be.true; + }); +}); \ No newline at end of file diff --git a/src/middlewares/passwordExpiryCheck.ts b/src/middlewares/passwordExpiryCheck.ts index c96f9603..4ae45f35 100644 --- a/src/middlewares/passwordExpiryCheck.ts +++ b/src/middlewares/passwordExpiryCheck.ts @@ -8,7 +8,7 @@ interface ExtendedRequest extends Request { } const PASSWORD_EXPIRATION_MINUTES = Number(process.env.PASSWORD_EXPIRATION_MINUTES) || 90; -const PASSWORD_RESET_URL = `${process.env.SERVER_URL_PRO}/api/auth/reset-password`; +const PASSWORD_RESET_URL = `${process.env.SERVER_URL_PRO}/reset-password`; const addMinutes = (date: Date, minutes: number): Date => { const result = new Date(date); diff --git a/src/middlewares/validation.ts b/src/middlewares/validation.ts index e180b7e0..0b2450c1 100644 --- a/src/middlewares/validation.ts +++ b/src/middlewares/validation.ts @@ -863,14 +863,12 @@ const isSellerRequestExist = async (req: Request, res: Response, next: NextFunct try { const userId = req.user.id; const existingRequest = await userRepositories.findSellerRequestByUserId(userId); - if (existingRequest) { return res.status(httpStatus.BAD_REQUEST).json({ status: httpStatus.BAD_REQUEST, message: "Seller request already submitted", }); } - next(); } catch (error) { return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ @@ -923,6 +921,43 @@ const isOrderExist = async (req: Request, res:Response, next:NextFunction)=>{ } } +const isOrderEmpty = async (req: Request, res: Response, next: NextFunction) => { + const orders = await cartRepositories.getOrdersHistory(); + if(!orders){ + return res.status(httpStatus.NOT_FOUND).json({ + status: httpStatus.NOT_FOUND, + error: "order Not Found" + }) + } + (req as any).orders = orders; + next(); +} + +const isShopEmpty = async (req: Request, res: Response, next: NextFunction) => { + const shops = await userRepositories.getAllShops(); + if(!shops){ + return res.status(httpStatus.NOT_FOUND).json({ + status: httpStatus.NOT_FOUND, + error: "Shops Not Found" + }) + } + (req as any).shops = shops; + next(); +} + +const isOroderExistByShopId = async (req: Request, res: Response, next: NextFunction) => { + const shop = await productRepositories.findShopByUserId(req.user.id); + const orders = await productRepositories.sellerGetOrdersHistory(shop.id); + + if(!orders){ + return res.status(httpStatus.NOT_FOUND).json({ + status: httpStatus.NOT_FOUND, + error: "Order Not Found" + }) + } + (req as any).ordersHistory = orders; + next(); +} export { validation, @@ -956,5 +991,8 @@ export { isProductOrdered, isUserProfileComplete, isSellerRequestExist, - isOrderExist + isOrderExist, + isOrderEmpty, + isShopEmpty, + isOroderExistByShopId }; \ No newline at end of file diff --git a/src/modules/cart/controller/cartControllers.ts b/src/modules/cart/controller/cartControllers.ts index 8641fd97..18624fb3 100644 --- a/src/modules/cart/controller/cartControllers.ts +++ b/src/modules/cart/controller/cartControllers.ts @@ -338,6 +338,26 @@ const adminUpdateOrderStatus = async (req: ExtendRequest, res: Response) => { } +const stripeCheckoutSession = async (req, res) => { + try { + let customer = await cartRepositories.getStripeCustomerByAttribute('email', req.body.sessionInfo.customer_email); + if (!customer) customer = await cartRepositories.createStripeCustomer({ email: req.body.sessionInfo.customer_email }); + delete req.body.sessionInfo.customer_email; + req.body.sessionInfo.customer = customer.id; + let session = await cartRepositories.getStripeSessionByAttribute('customer', customer.id); + if (!session) session = await cartRepositories.createStripeSession(req.body.sessionInfo); + return res.status(httpStatus.OK).json({ message: "Success.", data: { session } }); + } catch (error) { + return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, error: error.message }) + } +}; +const adminGetOrdersHistory = async(req: ExtendRequest, res:Response)=>{ + const OrderHistory = (req as any).orders + return res.status(httpStatus.OK).json({ + message: "Order History", + data: { OrderHistory } + }) +} export { buyerGetCart, @@ -354,4 +374,6 @@ export { buyerGetOrders, buyerCheckout, adminUpdateOrderStatus, + stripeCheckoutSession, + adminGetOrdersHistory }; \ No newline at end of file diff --git a/src/modules/cart/repositories/cartRepositories.ts b/src/modules/cart/repositories/cartRepositories.ts index 10de4b6b..716266ee 100644 --- a/src/modules/cart/repositories/cartRepositories.ts +++ b/src/modules/cart/repositories/cartRepositories.ts @@ -182,6 +182,10 @@ const getOrdersByUserId = async (userId: string) => { }); }; +const getOrdersHistory = async () => { + return await db.Orders.findAll(); +}; + const getStripeProductByAttribute = async (primaryKey: string, primaryValue: number | string | boolean): Promise => { const product = await stripe.products.search({ query: `${primaryKey}: '${primaryValue}'` }); @@ -234,4 +238,5 @@ export default { createStripeProduct, getStripeProductByAttribute, createStripeCustomer, getStripeCustomerByAttribute, createStripeSession, getStripeSessionByAttribute, + getOrdersHistory }; \ No newline at end of file diff --git a/src/modules/product/controller/productController.ts b/src/modules/product/controller/productController.ts index 0de1f205..a3766dcf 100644 --- a/src/modules/product/controller/productController.ts +++ b/src/modules/product/controller/productController.ts @@ -425,6 +425,22 @@ const buyerReviewProduct = async (req: ExtendRequest, res: Response) => { } } +const adminGetShops = async(req: ExtendRequest, res:Response)=>{ + const shops = (req as any).shops + return res.status(httpStatus.OK).json({ + message: "List Of shops", + data: { shops } + }) +} + +const sellerGetOrdersHistory = async(req: ExtendRequest, res:Response)=>{ + const order = (req as any).ordersHistory; + return res.status(httpStatus.OK).json({ + message: "Seller Order History", + data: { order } + }) +} + export { sellerCreateProduct, sellerCreateShop, @@ -442,5 +458,7 @@ export { buyerDeleteWishListProducts, buyerViewWishListProduct, buyerViewWishListProducts, - buyerDeleteWishListProduct + buyerDeleteWishListProduct, + adminGetShops, + sellerGetOrdersHistory }; \ No newline at end of file diff --git a/src/modules/product/repositories/productRepositories.ts b/src/modules/product/repositories/productRepositories.ts index 218a5592..14d4c785 100644 --- a/src/modules/product/repositories/productRepositories.ts +++ b/src/modules/product/repositories/productRepositories.ts @@ -1,6 +1,6 @@ /* eslint-disable comma-dangle */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Op } from "sequelize"; +import { Op, where } from "sequelize"; import db from "../../../databases/models"; import Products from "../../../databases/models/products"; const createProduct = async (body: any) => { @@ -246,6 +246,10 @@ const findSingleProductById = async (id: string) => { }); }; +const sellerGetOrdersHistory = async (shopId: string) => { + return db.Orders.findAll({ where: { shopId } }); +}; + export default { createProduct, updateProduct, @@ -275,7 +279,8 @@ export default { expiredProductsByUserId, removeWishList, userCreateReview, - findSingleProductById + findSingleProductById, + sellerGetOrdersHistory }; diff --git a/src/modules/user/controller/userControllers.ts b/src/modules/user/controller/userControllers.ts index 1faf3197..dc0995a6 100644 --- a/src/modules/user/controller/userControllers.ts +++ b/src/modules/user/controller/userControllers.ts @@ -267,6 +267,8 @@ const changeUserAddress = async (req: any, res: Response) => { } }; + + export default { updateUserStatus, updateUserRole, @@ -280,5 +282,5 @@ export default { markNotificationAsRead, markAllNotificationsAsRead, submitSellerRequest, - changeUserAddress, + changeUserAddress }; \ No newline at end of file diff --git a/src/modules/user/repository/userRepositories.ts b/src/modules/user/repository/userRepositories.ts index 8c641007..4f76d883 100644 --- a/src/modules/user/repository/userRepositories.ts +++ b/src/modules/user/repository/userRepositories.ts @@ -89,6 +89,10 @@ const findAddressByUserId = async (userId: string) => { return await db.Addresses.findOne({ where: { userId } }); }; +const getAllShops = async () => { + return await db.Shops.findAll(); +}; + export default { getAllUsers, updateUserProfile, @@ -104,5 +108,6 @@ export default { findSellerRequestByUserId, updateUserAddress, addUserAddress, - findAddressByUserId + findAddressByUserId, + getAllShops }; \ No newline at end of file diff --git a/src/routes/cartRouter.ts b/src/routes/cartRouter.ts index fbc3521e..32324857 100644 --- a/src/routes/cartRouter.ts +++ b/src/routes/cartRouter.ts @@ -7,7 +7,8 @@ import { isCartProductExist, isProductIdExist, validation, - isOrderExist + isOrderExist, + isOrderEmpty } from "../middlewares/validation"; import * as cartControllers from "../modules/cart/controller/cartControllers"; import { cartSchema, checkoutSessionSchema, productDetailsSchema, updateOrderStatusSchema } from "../modules/cart/validation/cartValidations"; @@ -73,4 +74,6 @@ router.get("/buyer-get-order-history", userAuthorization(["buyer"]), isOrderExis router.post("/create-stripe-product", userAuthorization(["buyer"]), validation(productDetailsSchema), stripeCreateProduct); router.post("/checkout-stripe-session", userAuthorization(["buyer"]), validation(checkoutSessionSchema), stripeCheckoutSession); +router.get("/admin-get-order-history",userAuthorization(["admin"]),isOrderEmpty,cartControllers.adminGetOrdersHistory) +router.get("/seller-get-order-history",userAuthorization(["seller"]),isOrderEmpty,cartControllers.adminGetOrdersHistory) export default router; \ No newline at end of file diff --git a/src/routes/productRouter.ts b/src/routes/productRouter.ts index 2fb348b7..8bd911f1 100644 --- a/src/routes/productRouter.ts +++ b/src/routes/productRouter.ts @@ -16,7 +16,9 @@ import { isUserWishlistExist, isProductOrdered, isProductExistIntoWishList, - isWishListProductExist + isWishListProductExist, + isShopEmpty, + isOroderExistByShopId } from "../middlewares/validation"; import { @@ -122,4 +124,6 @@ router.post( validation(productReviewSchema), isProductOrdered, productController.buyerReviewProduct ) + router.get("/admin-get-shops",userAuthorization(["admin"]),isShopEmpty,productController.adminGetShops); + router.get("/seller-get-orderHistory",userAuthorization(["seller"]),isOroderExistByShopId,productController.sellerGetOrdersHistory); export default router; \ No newline at end of file diff --git a/src/routes/userRouter.ts b/src/routes/userRouter.ts index 9d00e52b..6a1e4a9e 100644 --- a/src/routes/userRouter.ts +++ b/src/routes/userRouter.ts @@ -11,7 +11,8 @@ import upload from "../helpers/multer"; router.get("/admin-get-user/:id", userAuthorization(["admin"]), isUserExist, userControllers.adminGetUser); router.put("/admin-update-user-status/:id", userAuthorization(["admin"]), validation(statusSchema), isUserExist, userControllers.updateUserStatus); router.put("/admin-update-user-role/:id", userAuthorization(["admin"]), validation(roleSchema), isUserExist, userControllers.updateUserRole); - + + router.get("/user-get-profile", userAuthorization(["admin", "buyer", "seller"]), userControllers.getUserDetails); router.put("/user-update-profile", userAuthorization(["admin", "buyer", "seller"]), upload.single("profilePicture"), validation(userSchema), userControllers.updateUserProfile); router.put("/change-password", userAuthorization(["admin", "buyer", "seller"]), validation(changePasswordSchema), credential, userControllers.changePassword); diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 0a582a93..09ceb179 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -23,6 +23,7 @@ export interface ExtendRequest extends Request { user?: user; shop?: Shops; cart?:Cart; + orders?:any; wishList?:any; wishListId:string; pagination?: { diff --git a/src/types/uuid.ts b/src/types/uuid.ts index a09b039b..66b231a5 100644 --- a/src/types/uuid.ts +++ b/src/types/uuid.ts @@ -40,6 +40,13 @@ export const productTwentyId = uuidv4(); export const orderOneId = uuidv4(); export const orderTwoId = uuidv4(); +export const orderThreeId = uuidv4(); +export const orderFourId = uuidv4(); +export const orderFiveId = uuidv4(); +export const orderSixId = uuidv4(); +export const orderSevenId = uuidv4(); +export const orderEightId = uuidv4(); +export const orderNineId = uuidv4(); export const cartOneId = uuidv4(); export const cartTwoId = uuidv4();