From 2a4ac43eeefaf21ff42f20fad59d1425d8c5a25b Mon Sep 17 00:00:00 2001 From: EddyShimwa Date: Wed, 17 Jul 2024 16:19:41 +0200 Subject: [PATCH] include vendors names and product category in the return response --- README.md | 4 +- src/Notification.vendor/EmailSendor.ts | 12 +- src/Notification.vendor/event.services.ts | 504 +++++++------- src/Notification.vendor/message.Templete.ts | 213 +++--- src/Notification.vendor/node.cron.services.ts | 136 ++-- src/__test__/chatbotService.test.ts | 2 - src/__test__/coupon.test.ts | 659 +++++++++--------- src/__test__/handlePayment.test.ts | 88 +++ src/__test__/imageUpload.test.ts | 26 +- src/__test__/notification.vendor.test.ts | 23 +- src/__test__/review.test.ts | 155 ++-- src/__test__/searchProduct.test.ts | 5 +- src/__test__/userController.test.ts | 165 ++--- src/controller/couponController.ts | 287 ++++---- src/controller/notificationController.ts | 102 ++- src/controller/orderController.ts | 6 +- src/controller/productController.ts | 21 +- src/controller/reviewController.ts | 156 +++-- src/controller/searchProducts.ts | 53 +- src/controller/userController.ts | 63 +- src/database/models/buyerWishList.ts | 37 +- src/database/models/cartEntity.ts | 2 +- src/database/models/chatbotModel.ts | 32 +- src/database/models/couponEntity.ts | 82 +-- src/database/models/inbox_notification.ts | 23 +- src/database/models/index.ts | 2 +- src/database/models/reviewEntity.ts | 5 +- src/database/models/serviceEntity.ts | 55 +- src/database/models/userModel.ts | 4 +- src/docs/couponDocs.ts | 4 +- src/docs/couponDoct.ts | 4 +- src/docs/notification.vendor.Docs.ts | 6 +- src/docs/reviewDocs.ts | 2 +- src/docs/searchProduct.ts | 2 +- src/docs/userRegisterDocs.ts | 4 +- src/emails/index.ts | 2 +- src/index.ts | 5 +- src/middlewares/availabilityValidator.ts | 20 +- src/middlewares/cloudinary.ts | 13 +- src/middlewares/createReviewSchema.ts | 9 +- src/middlewares/isLoggedIn.ts | 2 +- src/middlewares/multer.ts | 4 +- src/middlewares/productAvailabilityWatch.ts | 19 +- src/middlewares/validateSearchParams.ts | 34 +- src/routes/couponRoute.ts | 22 +- src/routes/index.ts | 4 +- src/routes/notificationRoutes.ts | 25 +- src/routes/reviewRoutes.ts | 7 +- src/routes/searchRoutes.ts | 4 +- src/routes/serviceRoutes.ts | 8 +- src/routes/userRoutes.ts | 16 +- src/utilis/couponCalculator.ts | 33 +- src/utilis/nlp.ts | 637 +++++++++++------ src/utilis/sendEmail.ts | 6 +- 54 files changed, 2100 insertions(+), 1714 deletions(-) create mode 100644 src/__test__/handlePayment.test.ts diff --git a/README.md b/README.md index 28312855..302291b3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ [![CI for ecomm-project for Dynamite](https://github.com/atlp-rwanda/dynamites-ecomm-be/actions/workflows/workflow_for_ecomm.yml/badge.svg)](https://github.com/atlp-rwanda/dynamites-ecomm-be/actions/workflows/workflow_for_ecomm.yml) [![codecov](https://codecov.io/gh/atlp-rwanda/dynamites-ecomm-be/graph/badge.svg?token=I1G8CMQKBH)](https://codecov.io/gh/atlp-rwanda/dynamites-ecomm-be) -# Dynamites API + +# Dynamites API ## Overview @@ -63,6 +64,7 @@ Before you run that commands you must have docker installed in your PC ```sh docker-compose down ``` + ## Usage Once the development server is running, you can interact with the API using HTTP requests. diff --git a/src/Notification.vendor/EmailSendor.ts b/src/Notification.vendor/EmailSendor.ts index 2e39674b..c217ec4b 100644 --- a/src/Notification.vendor/EmailSendor.ts +++ b/src/Notification.vendor/EmailSendor.ts @@ -3,13 +3,17 @@ import dotenv from 'dotenv'; dotenv.config(); -async function sendEmail(vendorEmail: string, message_title: string, messageContent: string) { +async function sendEmail( + vendorEmail: string, + message_title: string, + messageContent: string +) { try { const transporter = nodemailer.createTransport({ service: 'gmail', host: 'smtp.gmail.com', port: 587, - secure: false, + secure: false, auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASS, @@ -25,11 +29,9 @@ async function sendEmail(vendorEmail: string, message_title: string, messageCont }; await transporter.sendMail(mailOptions); - } catch (error) { - throw error + throw error; } } export default sendEmail; - diff --git a/src/Notification.vendor/event.services.ts b/src/Notification.vendor/event.services.ts index 12b46b83..8701853c 100644 --- a/src/Notification.vendor/event.services.ts +++ b/src/Notification.vendor/event.services.ts @@ -5,19 +5,19 @@ import Product from '../database/models/productEntity'; import { Order } from '../database/models/orderEntity'; import dbConnection from '../database'; import sendEmailfunc from './EmailSendor'; -import { - added_to_cart_message, - removed_to_cart_message, - pressorder_message, - order_status_changed, - new_product_created, - updated_Product, - product_deleted, - order_canceled} from './message.Templete'; +import { + added_to_cart_message, + removed_to_cart_message, + pressorder_message, + order_status_changed, + new_product_created, + updated_Product, + product_deleted, + order_canceled, +} from './message.Templete'; export const eventEmitter = new EventEmitter(); - interface product { id: number; name: string; @@ -65,26 +65,31 @@ const orderRepository = dbConnection.getRepository(Order); eventEmitter.on('addToCart', async (product_id, userId) => { try { - if (process.env.NODE_ENV == 'test'){ - return + if (process.env.NODE_ENV == 'test') { + return; } const product = await productRepository.findOne({ where: { id: product_id }, - select: { vendor: { firstName: true, lastName: true, picture: true, id: true, email: true } }, + select: { + vendor: { + firstName: true, + lastName: true, + picture: true, + id: true, + email: true, + }, + }, relations: ['vendor'], }); if (!product) { return; + } else if (!product.vendor || !product.vendor.email) { + return; } - - else if (!product.vendor || !product.vendor.email) - { - return - } const User = await userRepository.findOne({ - where: { id: userId } + where: { id: userId }, }); if (!User) { @@ -98,36 +103,42 @@ eventEmitter.on('addToCart', async (product_id, userId) => { new_notification.message_title = 'your product is add to buyer cart'; new_notification.message_content = added_to_cart_message(product, User); await NotificationRepository.save(new_notification); - await sendEmailfunc(product.vendor.email, new_notification.message_title, new_notification.message_content) - - } - catch (error) { - throw error - + await sendEmailfunc( + product.vendor.email, + new_notification.message_title, + new_notification.message_content + ); + } catch (error) { + throw error; } }); eventEmitter.on('removeItem', async (removeItem) => { try { - if (process.env.NODE_ENV == 'test'){ - return + if (process.env.NODE_ENV == 'test') { + return; } const product = await productRepository.findOne({ where: { id: removeItem.product.id }, - select: { vendor: { firstName: true, lastName: true, picture: true, id: true, email: true } }, + select: { + vendor: { + firstName: true, + lastName: true, + picture: true, + id: true, + email: true, + }, + }, relations: ['vendor'], }); if (!product) { return; + } else if (!product.vendor || !product.vendor.email) { + return; } - - else if (!product.vendor || !product.vendor.email) - { - return - } const user = await userRepository.findOne({ - where: { id: removeItem.user.id } + where: { id: removeItem.user.id }, }); if (!user) { @@ -142,247 +153,262 @@ eventEmitter.on('removeItem', async (removeItem) => { new_notification.message_content = removed_to_cart_message(product, user); await NotificationRepository.save(new_notification); - await sendEmailfunc(product.vendor.email, new_notification.message_title, new_notification.message_content) - + await sendEmailfunc( + product.vendor.email, + new_notification.message_title, + new_notification.message_content + ); } catch (error) { - throw error + throw error; } }); -eventEmitter.on('pressorder', async (order:order) => { +eventEmitter.on('pressorder', async (order: order) => { try { - if (process.env.NODE_ENV == 'test'){ - return - } - const orderDetail = order.orderDetails - for(let i=0; i { +eventEmitter.on('order_status_change', async (orderId: number) => { try { - if (process.env.NODE_ENV == 'test'){ - return + if (process.env.NODE_ENV == 'test') { + return; } const order = await orderRepository.findOne({ where: { id: orderId, }, - relations:['orderDetails','orderDetails.product'] + relations: ['orderDetails', 'orderDetails.product'], }); - if(order == null) - { - return - } - const orderDetail = order.orderDetails - - for(let i=0; i { + try { + if (process.env.NODE_ENV == 'test') { + return; + } + if (!product) { + return; + } else if (!product.vendor || !product.vendor.email) { + return; + } -eventEmitter.on('productCreated', async(product:product)=>{ - try{ - if (process.env.NODE_ENV == 'test'){ - return - } - if(!product) - { - return - } - - else if (!product.vendor || !product.vendor.email) { - return - } - - const new_notification = new Notification_box(); - new_notification.product_id =product.id ; - new_notification.vendor_id = product.vendor.id - new_notification.vendor_email = product.vendor.email; - new_notification.message_title = 'Your Product was created sucessfull'; - new_notification.message_content = new_product_created(product); + const new_notification = new Notification_box(); + new_notification.product_id = product.id; + new_notification.vendor_id = product.vendor.id; + new_notification.vendor_email = product.vendor.email; + new_notification.message_title = 'Your Product was created sucessfull'; + new_notification.message_content = new_product_created(product); - await NotificationRepository.save(new_notification); - await sendEmailfunc(product.vendor.email, new_notification.message_title, new_notification.message_content) - } - catch(error) - { - throw error + await NotificationRepository.save(new_notification); + await sendEmailfunc( + product.vendor.email, + new_notification.message_title, + new_notification.message_content + ); + } catch (error) { + throw error; } +}); -}) - -eventEmitter.on('product_updated', async(product:product)=>{ - - try{ - if (process.env.NODE_ENV == 'test'){ - return - } - if(!product) - { - return - } - else if (!product.vendor || !product.vendor.email) - { - return - } - const new_notification = new Notification_box(); - new_notification.product_id =product.id ; - new_notification.vendor_id = product.vendor.id - new_notification.vendor_email = product.vendor.email; - new_notification.message_title = 'Your product was Updated succesfull'; - new_notification.message_content = updated_Product(product); - - await NotificationRepository.save(new_notification); - await sendEmailfunc(product.vendor.email, new_notification.message_title, new_notification.message_content) - } - catch(error) - { - throw error - } -}) - - -eventEmitter.on('product_deleted', async(product_id:number)=>{ - try{ - if (process.env.NODE_ENV == 'test'){ - return - } - const product= await productRepository.findOne({ - where:{id: product_id}, - relations:['vendor'] - }) - if(!product || !product.vendor || !product.vendor.email) - { - return - } - - else if (!product.vendor || !product.vendor.email) - { - return - } - const new_notification = new Notification_box(); - new_notification.product_id =product.id ; - new_notification.vendor_id = product.vendor.id - new_notification.vendor_email = product.vendor.email; - new_notification.message_title = 'Your product was deleted succesfull'; - new_notification.message_content = product_deleted(product); - - await NotificationRepository.save(new_notification); - await sendEmailfunc(product.vendor.email, new_notification.message_title, new_notification.message_content) - } - catch(error) - { - throw error +eventEmitter.on('product_updated', async (product: product) => { + try { + if (process.env.NODE_ENV == 'test') { + return; + } + if (!product) { + return; + } else if (!product.vendor || !product.vendor.email) { + return; + } + const new_notification = new Notification_box(); + new_notification.product_id = product.id; + new_notification.vendor_id = product.vendor.id; + new_notification.vendor_email = product.vendor.email; + new_notification.message_title = 'Your product was Updated succesfull'; + new_notification.message_content = updated_Product(product); + + await NotificationRepository.save(new_notification); + await sendEmailfunc( + product.vendor.email, + new_notification.message_title, + new_notification.message_content + ); + } catch (error) { + throw error; } -}) +}); + +eventEmitter.on('product_deleted', async (product_id: number) => { + try { + if (process.env.NODE_ENV == 'test') { + return; + } + const product = await productRepository.findOne({ + where: { id: product_id }, + relations: ['vendor'], + }); + if (!product || !product.vendor || !product.vendor.email) { + return; + } else if (!product.vendor || !product.vendor.email) { + return; + } + const new_notification = new Notification_box(); + new_notification.product_id = product.id; + new_notification.vendor_id = product.vendor.id; + new_notification.vendor_email = product.vendor.email; + new_notification.message_title = 'Your product was deleted succesfull'; + new_notification.message_content = product_deleted(product); + await NotificationRepository.save(new_notification); + await sendEmailfunc( + product.vendor.email, + new_notification.message_title, + new_notification.message_content + ); + } catch (error) { + throw error; + } +}); eventEmitter.on('order_canceled', async (orderId) => { try { - if (process.env.NODE_ENV == 'test'){ - return + if (process.env.NODE_ENV == 'test') { + return; } const order = await orderRepository.findOne({ where: { id: orderId }, - relations: ['orderDetails','orderDetails.product', 'orderDetails.product.vendor'], + relations: [ + 'orderDetails', + 'orderDetails.product', + 'orderDetails.product.vendor', + ], }); - if(!order) - { - return - } - const orderDetail = order.orderDetails - - for(let i=0; i{ - - return ` + id: number; + firstName: string; + lastName: string; + email: string; + picture: string | null; +} + +interface Product { + id: number; + name: string; + image: string; + gallery: string[]; + shortDesc: string; + longDesc: string; + quantity: number; + regularPrice: number; + salesPrice: number; + tags: string[]; + type: string; + isAvailable: boolean; + averageRating: number; + createdAt: Date; + updatedAt: Date; + vendor: UserModel; +} + +interface order { + id: number; + user: UserModel | null; + totalAmount: number; + status: string; + deliveryInfo: string | null; + trackingNumber: string; + createdAt: Date; + updatedAt: Date; + orderDetails: OrderDetails[]; + paid: boolean | null; +} +interface OrderDetails { + id: number; + order: order; + product: Product; + quantity: number; + price: number; +} + +export const added_to_cart_message = ( + product: Product, + user: UserModel +): string => { + return ` Dear ${product.vendor.firstName} ${product.vendor.lastName}, We are pleased to inform you that a new product has been added to a cart. @@ -66,17 +65,14 @@ export const added_to_cart_message=( Best regards, The E-commerce Team - ` -} - - - -export const removed_to_cart_message=( - product:Product, - user:UserModel -):string=>{ + `; +}; - return ` +export const removed_to_cart_message = ( + product: Product, + user: UserModel +): string => { + return ` Dear ${product.vendor.firstName} ${product.vendor.lastName} We would like to inform you that a product has been removed from a cart. @@ -92,17 +88,11 @@ export const removed_to_cart_message=( Best regards, The E-commerce Team - ` -} - - -export const pressorder_message = ( - product:Product, - order:order -): string =>{ + `; +}; - - return ` +export const pressorder_message = (product: Product, order: order): string => { + return ` Dear ${product.vendor.firstName} ${product.vendor.lastName}, We are pleased to inform you that a new order has been placed. @@ -121,15 +111,11 @@ export const pressorder_message = ( Best regards, The E-commerce Team - ` -} - -export const order_status_changed = ( - product:Product, - order:order -)=>{ + `; +}; - return ` +export const order_status_changed = (product: Product, order: order) => { + return ` Dear ${product.vendor.firstName} ${product.vendor.lastName}, We hope this message finds you well. @@ -156,13 +142,10 @@ export const order_status_changed = ( **Note:** This is an automated message. Please do not reply directly to this email. - ` -} + `; +}; -export const order_canceled = ( - product:Product, - order:order -)=>{ +export const order_canceled = (product: Product, order: order) => { return ` Dear ${product.vendor.firstName} ${product.vendor.lastName}, @@ -182,15 +165,10 @@ export const order_canceled = ( Best regards, The E-commerce Team - ` -} - + `; +}; - - -export const new_product_created = ( -product:Product -)=>{ +export const new_product_created = (product: Product) => { return ` Hello ${product.vendor.firstName} ${product.vendor.lastName}, @@ -207,12 +185,10 @@ product:Product Best regards, The E-commerce Team - ` -} + `; +}; -export const updated_Product = ( - product:Product -)=>{ +export const updated_Product = (product: Product) => { return ` Hello ${product.vendor.firstName} ${product.vendor.lastName}, @@ -229,12 +205,10 @@ export const updated_Product = ( Best regards, The E-commerce Team - ` -} + `; +}; -export const product_deleted=( - product:Product -)=>{ +export const product_deleted = (product: Product) => { return ` Hello ${product.vendor.firstName} ${product.vendor.id}, @@ -250,13 +224,11 @@ export const product_deleted=( Best regards, The E-commerce Team - ` -} + `; +}; -export const product_not_availble = ( - product:Product -)=>{ - return ` +export const product_not_availble = (product: Product) => { + return ` Dear ${product.vendor.firstName}, We regret to inform you that your product "${product.name}" is currently unavailable on our platform. @@ -270,13 +242,10 @@ export const product_not_availble = ( Best regards, The E-commerce Team -` -} +`; +}; -export const expiried_order = ( - product:Product, - order:order -)=>{ +export const expiried_order = (product: Product, order: order) => { return ` Hello ${product.vendor.firstName} ${product.vendor.lastName}, @@ -289,15 +258,19 @@ export const expiried_order = ( - Ordered At: ${order.createdAt.toLocaleString()} Product Details: - ${order.orderDetails.map(detail => ` + ${order.orderDetails + .map( + (detail) => ` - Product Name: ${detail.product.name} - Product ID: ${detail.product.id} - Quantity: ${detail.quantity} - - Price: $${detail.price}`).join('\n')} + - Price: $${detail.price}` + ) + .join('\n')} If you have any questions or concerns, please contact our support team. Best regards, The E-commerce Team - ` -} \ No newline at end of file + `; +}; diff --git a/src/Notification.vendor/node.cron.services.ts b/src/Notification.vendor/node.cron.services.ts index 3cf22d98..e6d9878d 100644 --- a/src/Notification.vendor/node.cron.services.ts +++ b/src/Notification.vendor/node.cron.services.ts @@ -5,9 +5,7 @@ import { Order } from '../database/models/orderEntity'; import dbConnection from '../database'; import sendEmailfunc from './EmailSendor'; import cron from 'node-cron'; -import { - product_not_availble, - expiried_order} from './message.Templete'; +import { product_not_availble, expiried_order } from './message.Templete'; export const eventEmitter = new EventEmitter(); @@ -15,79 +13,85 @@ const productRepository = dbConnection.getRepository(Product); const NotificationRepository = dbConnection.getRepository(Notification_box); const orderRepository = dbConnection.getRepository(Order); - const dailyTasks = cron.schedule('0 0 * * *', async () => { - - const orders = await orderRepository.find(); - for (const order of orders) { - const createdAt = new Date(order.createdAt); - const now = new Date(); - const timeDiff = now.getTime() - createdAt.getTime(); - const diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24)); - - if (diffDays >= 0.1 && order.status !== 'Canceled') { - order.status = 'Canceled'; - await orderRepository.save(order); - - // ************************************************************************* + const orders = await orderRepository.find(); + for (const order of orders) { + const createdAt = new Date(order.createdAt); + const now = new Date(); + const timeDiff = now.getTime() - createdAt.getTime(); + const diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24)); - const orderDetail = order.orderDetails - - for(let i=0; i= 0.1 && order.status !== 'Canceled') { + order.status = 'Canceled'; + await orderRepository.save(order); - if(!product || !product.vendor || !product.vendor.email) - { - return - } + // ************************************************************************* - else if (!product.vendor || !product.vendor.email) - { - return - } - const new_notification = new Notification_box(); - new_notification.product_id = orderDetail[i].product.id; - new_notification.vendor_id = product.vendor.id - new_notification.vendor_email = product.vendor.email; - new_notification.message_title = 'order with you product was placed'; - new_notification.message_content = expiried_order(product, order); + const orderDetail = order.orderDetails; - await NotificationRepository.save(new_notification); - - sendEmailfunc(product.vendor.email, new_notification.message_title, new_notification.message_content) - } + for (let i = 0; i < orderDetail.length; i++) { + const product = await productRepository.findOne({ + where: { id: orderDetail[i].product.id }, + select: { + vendor: { + firstName: true, + lastName: true, + picture: true, + id: true, + email: true, + }, + }, + relations: ['vendor'], + }); + if (!product || !product.vendor || !product.vendor.email) { + return; + } else if (!product.vendor || !product.vendor.email) { + return; } - } - - const unavailableProducts = await productRepository.find({ where: { isAvailable: false } }); - for (const product of unavailableProducts) { - if(!product || !product.vendor) - { - continue - } - - else if (!product.vendor || !product.vendor.email) - { - continue - } - const new_notification = new Notification_box(); - new_notification.product_id = product.id - new_notification.vendor_id = product.vendor.id + new_notification.product_id = orderDetail[i].product.id; + new_notification.vendor_id = product.vendor.id; new_notification.vendor_email = product.vendor.email; - new_notification.message_title = 'Your Product is UnAvailable'; - new_notification.message_content = product_not_availble(product); - + new_notification.message_title = 'order with you product was placed'; + new_notification.message_content = expiried_order(product, order); + await NotificationRepository.save(new_notification); - - sendEmailfunc(product.vendor.email, new_notification.message_title, new_notification.message_content) + + sendEmailfunc( + product.vendor.email, + new_notification.message_title, + new_notification.message_content + ); + } } + } + + const unavailableProducts = await productRepository.find({ + where: { isAvailable: false }, + }); + for (const product of unavailableProducts) { + if (!product || !product.vendor) { + continue; + } else if (!product.vendor || !product.vendor.email) { + continue; + } + + const new_notification = new Notification_box(); + new_notification.product_id = product.id; + new_notification.vendor_id = product.vendor.id; + new_notification.vendor_email = product.vendor.email; + new_notification.message_title = 'Your Product is UnAvailable'; + new_notification.message_content = product_not_availble(product); + + await NotificationRepository.save(new_notification); + + sendEmailfunc( + product.vendor.email, + new_notification.message_title, + new_notification.message_content + ); + } }); -export default dailyTasks \ No newline at end of file +export default dailyTasks; diff --git a/src/__test__/chatbotService.test.ts b/src/__test__/chatbotService.test.ts index 7f884d06..e6a2b624 100644 --- a/src/__test__/chatbotService.test.ts +++ b/src/__test__/chatbotService.test.ts @@ -220,5 +220,3 @@ describe('Chatbot Service', () => { }); }); }); - - diff --git a/src/__test__/coupon.test.ts b/src/__test__/coupon.test.ts index 50514977..10b1394f 100644 --- a/src/__test__/coupon.test.ts +++ b/src/__test__/coupon.test.ts @@ -2,332 +2,341 @@ import request from 'supertest'; import app from '../app'; import { getVendorToken, afterAllHook, beforeAllHook } from './testSetup'; - beforeAll(beforeAllHook); afterAll(afterAllHook); - describe('Coupon Controller Tests', () => { - let token: string; - let couponId: number; - let productId: number; - - beforeAll(async () => { - token = await getVendorToken(); - }); - - it('should create a new coupon with valid data', async () => { - const categoryData = { - name: 'Category', - description: 'category description', - icon: 'category icon' - }; - - const categoryResponse = await request(app) - .post('/api/v1/category') - .set('Authorization', `Bearer ${token}`) - .send(categoryData); - - const categoryId = categoryResponse.body.data.id; - const productData = { - name: 'New Product', - image: 'new_product.jpg', - gallery: [], - shortDesc: 'This is a new product', - longDesc: 'Detailed description of the new product', - categoryId: categoryId, - quantity: 10, - regularPrice: 5, - salesPrice: 4, - tags: ['tag1', 'tag2'], - type: 'Simple', - isAvailable: true, - }; - - const productResponse = await request(app) - .post('/api/v1/product') - .set('Authorization', `Bearer ${token}`) - .send(productData); - - expect(productResponse.statusCode).toEqual(201); - expect(productResponse.body.message).toEqual('Product successfully created'); - expect(productResponse.body.data).toBeDefined(); - productId = productResponse.body.data.id; - - const couponData = { - description: 'New Coupon', - percentage: 30, - expirationDate: '2024-12-31', - applicableProducts: [productId], - }; - - const response = await request(app) - .post('/api/v1/coupons') - .set('Authorization', `Bearer ${token}`) - .send(couponData); - - expect(response.statusCode).toEqual(201); - expect(response.body.message).toEqual('Coupon created successfully'); - expect(response.body.data).toBeDefined(); - couponId = response.body.data.id; - }); - - it('should return validation errors for invalid coupon data', async () => { - const invalidCouponData = { - description: '', - percentage: 120, - expirationDate: '2022-12-31', - applicableProducts: [], - }; - - const response = await request(app) - .post('/api/v1/coupons') - .set('Authorization', `Bearer ${token}`) - .send(invalidCouponData); - - expect(response.statusCode).toEqual(400); - expect(response.body.errors).toBeDefined(); - }); - - it ('should return a 404 for a non-existent product', async () => { - const nonExistentProductId = 999; - const couponData = { - description: 'New Coupon', - percentage: 30, - expirationDate: '2024-12-31', - applicableProducts: [nonExistentProductId], - }; - - const response = await request(app) - .post('/api/v1/coupons') - .set('Authorization', `Bearer ${token}`) - .send(couponData); - - expect(response.statusCode).toEqual(400); - expect(response.body.error).toEqual(`Product with id ${nonExistentProductId} not found`); - }) - - it('should return a 404 for a non-existent coupon', async () => { - const nonExistentCouponId = 999; - const response = await request(app).get(`/api/v1/coupons/${nonExistentCouponId}`); - - expect(response.statusCode).toEqual(404); - expect(response.body.error).toEqual('Coupon not found'); - }); - - it('should return a 401 for an unauthenticated user', async () => { - const couponData = { - description: 'New Coupon', - percentage: 30, - expirationDate: '2024-12-31', - applicableProducts: [productId], - }; - - const response = await request(app) - .post('/api/v1/coupons') - .set('Authorization', 'Invalid Token') - .send(couponData); - expect(response.statusCode).toEqual(401); - }) - - it('should return a 403 for a user trying to create a coupon for another user\'s product', async () => { - const otherVendorToken = await getVendorToken( - 'email@example.com', - 'Password123', - 'OtherVendor', - 'OtherName' - ) - const couponData = { - description: 'New Coupon', - percentage: 30, - expirationDate: '2024-12-31', - applicableProducts: [productId], - }; - - const response = await request(app) - .post('/api/v1/coupons') - .set('Authorization', `Bearer ${otherVendorToken}`) - .send(couponData); - expect(response.statusCode).toEqual(403); - expect(response.body.error).toEqual('You can only create coupons for your own products'); - }) - - it('should retrieve all coupons', async () => { - const response = await request(app).get('/api/v1/coupons'); - - expect(response.statusCode).toEqual(200); - expect(Array.isArray(response.body)).toBeTruthy(); - }); - - it('should retrieve all coupons by vendor', async () => { - const response = await request(app) - .get('/api/v1/coupons/mine') - .set('Authorization', `Bearer ${token}`); - - expect(response.statusCode).toEqual(200); - expect(Array.isArray(response.body)).toBeTruthy(); - }) - - it('should retrieve a single coupon by ID', async () => { - const response = await request(app).get(`/api/v1/coupons/${couponId}`); - - expect(response.statusCode).toEqual(200); - expect(response.body).toBeDefined(); - }); - - it('should update a coupon by ID', async () => { - const updatedCouponData = { - description: 'Updated Coupon', - percentage: 20, - expirationDate: '2023-12-31', - applicableProducts: [productId], - }; - - const response = await request(app) - .put(`/api/v1/coupons/${couponId}`) - .set('Authorization', `Bearer ${token}`) - .send(updatedCouponData); - - expect(response.statusCode).toEqual(200); - expect(response.body).toBeDefined(); - }); - - it('should return a 404 for a non-existent coupon while updating', async () => { - const nonExistentCouponId = 999; - const updatedCouponData = { - description: 'Updated Coupon', - percentage: 20, - expirationDate: '2023-12-31', - applicableProducts: [productId], - }; - - const response = await request(app) - .put(`/api/v1/coupons/${nonExistentCouponId}`) - .set('Authorization', `Bearer ${token}`) - .send(updatedCouponData); - - expect(response.statusCode).toEqual(404); - expect(response.body.error).toEqual('Coupon not found'); - }); - - - it('should return a 403 for a user trying to update a coupon for another user', async () => { - const otherVendorToken = await getVendorToken( - 'email@example.com', - 'Password123', - 'OtherVendor', - 'OtherName' - ) - const couponData = { - description: 'Updated Coupon', - percentage: 30, - expirationDate: '2024-12-31', - applicableProducts: [productId], - }; - - const response = await request(app) - .put(`/api/v1/coupons/${couponId}`) - .set('Authorization', `Bearer ${otherVendorToken}`) - .send(couponData); - expect(response.statusCode).toEqual(403); - expect(response.body.error).toEqual('You can only create coupons for your own products'); - }) - - it('should return a 401 for an unauthenticated user', async () => { - const couponData = { - description: 'New Coupon', - percentage: 30, - expirationDate: '2024-12-31', - applicableProducts: [productId], - }; - - const response = await request(app) - .put(`/api/v1/coupons/${couponId}`) - .set('Authorization', 'Invalid Token') - .send(couponData); - expect(response.statusCode).toEqual(401); - }) - - it('should return a 401 for an unauthenticated user', async () => { - const couponData = { - description: 'New Coupon', - percentage: 30, - expirationDate: '2024-12-31', - applicableProducts: [productId], - }; - - const response = await request(app) - .post('/api/v1/coupons') - .set('Authorization', 'Invalid Token') - .send(couponData); - expect(response.statusCode).toEqual(401); - }) - - it ('should return a 404 for a non-existent product', async () => { - const nonExistentProductId = 999; - const couponData = { - description: 'New Coupon', - percentage: 30, - expirationDate: '2024-12-31', - applicableProducts: [nonExistentProductId], - }; - - const response = await request(app) - .put(`/api/v1/coupons/${couponId}`) - .set('Authorization', `Bearer ${token}`) - .send(couponData); - - expect(response.statusCode).toEqual(400); - expect(response.body.error).toEqual(`Product with id ${nonExistentProductId} not found`); - }) - - it('should return validation errors for invalid update data', async () => { - const invalidUpdateData = { - description: '', - percentage: 120, - expirationDate: '2022-12-31', - applicableProducts: [], - }; - - const response = await request(app) - .put(`/api/v1/coupons/${couponId}`) - .set('Authorization', `Bearer ${token}`) - .send(invalidUpdateData); - - expect(response.statusCode).toEqual(400); - expect(response.body.errors).toBeDefined(); - }); - - it('should return a 403 for a user trying to delete a coupon for another user', async () => { - const otherVendorToken = await getVendorToken( - 'email@example.com', - 'Password123', - 'OtherVendor', - 'OtherName' - ) - - const response = await request(app) - .delete(`/api/v1/coupons/${couponId}`) - .set('Authorization', `Bearer ${otherVendorToken}`) - expect(response.statusCode).toEqual(403); - expect(response.body.error).toEqual('You can only delete your own coupons'); - }) - - it('should delete a coupon by ID', async () => { - const response = await request(app) - .delete(`/api/v1/coupons/${couponId}`) - .set('Authorization', `Bearer ${token}`); - - expect(response.statusCode).toEqual(204); - }); - - it('should return a 404 for a non-existent coupon', async () => { - const nonExistentCouponId = 999; - const response = await request(app) - .delete(`/api/v1/coupons/${nonExistentCouponId}`) - .set('Authorization', `Bearer ${token}`); - - expect(response.statusCode).toEqual(404); - expect(response.body.error).toEqual('Coupon not found'); - }); -}); \ No newline at end of file + let token: string; + let couponId: number; + let productId: number; + + beforeAll(async () => { + token = await getVendorToken(); + }); + + it('should create a new coupon with valid data', async () => { + const categoryData = { + name: 'Category', + description: 'category description', + icon: 'category icon', + }; + + const categoryResponse = await request(app) + .post('/api/v1/category') + .set('Authorization', `Bearer ${token}`) + .send(categoryData); + + const categoryId = categoryResponse.body.data.id; + const productData = { + name: 'New Product', + image: 'new_product.jpg', + gallery: [], + shortDesc: 'This is a new product', + longDesc: 'Detailed description of the new product', + categoryId: categoryId, + quantity: 10, + regularPrice: 5, + salesPrice: 4, + tags: ['tag1', 'tag2'], + type: 'Simple', + isAvailable: true, + }; + + const productResponse = await request(app) + .post('/api/v1/product') + .set('Authorization', `Bearer ${token}`) + .send(productData); + + expect(productResponse.statusCode).toEqual(201); + expect(productResponse.body.message).toEqual( + 'Product successfully created' + ); + expect(productResponse.body.data).toBeDefined(); + productId = productResponse.body.data.id; + + const couponData = { + description: 'New Coupon', + percentage: 30, + expirationDate: '2024-12-31', + applicableProducts: [productId], + }; + + const response = await request(app) + .post('/api/v1/coupons') + .set('Authorization', `Bearer ${token}`) + .send(couponData); + + expect(response.statusCode).toEqual(201); + expect(response.body.message).toEqual('Coupon created successfully'); + expect(response.body.data).toBeDefined(); + couponId = response.body.data.id; + }); + + it('should return validation errors for invalid coupon data', async () => { + const invalidCouponData = { + description: '', + percentage: 120, + expirationDate: '2022-12-31', + applicableProducts: [], + }; + + const response = await request(app) + .post('/api/v1/coupons') + .set('Authorization', `Bearer ${token}`) + .send(invalidCouponData); + + expect(response.statusCode).toEqual(400); + expect(response.body.errors).toBeDefined(); + }); + + it('should return a 404 for a non-existent product', async () => { + const nonExistentProductId = 999; + const couponData = { + description: 'New Coupon', + percentage: 30, + expirationDate: '2024-12-31', + applicableProducts: [nonExistentProductId], + }; + + const response = await request(app) + .post('/api/v1/coupons') + .set('Authorization', `Bearer ${token}`) + .send(couponData); + + expect(response.statusCode).toEqual(400); + expect(response.body.error).toEqual( + `Product with id ${nonExistentProductId} not found` + ); + }); + + it('should return a 404 for a non-existent coupon', async () => { + const nonExistentCouponId = 999; + const response = await request(app).get( + `/api/v1/coupons/${nonExistentCouponId}` + ); + + expect(response.statusCode).toEqual(404); + expect(response.body.error).toEqual('Coupon not found'); + }); + + it('should return a 401 for an unauthenticated user', async () => { + const couponData = { + description: 'New Coupon', + percentage: 30, + expirationDate: '2024-12-31', + applicableProducts: [productId], + }; + + const response = await request(app) + .post('/api/v1/coupons') + .set('Authorization', 'Invalid Token') + .send(couponData); + expect(response.statusCode).toEqual(401); + }); + + it('should return a 403 for a user trying to create a coupon for another user\'s product', async () => { + const otherVendorToken = await getVendorToken( + 'email@example.com', + 'Password123', + 'OtherVendor', + 'OtherName' + ); + const couponData = { + description: 'New Coupon', + percentage: 30, + expirationDate: '2024-12-31', + applicableProducts: [productId], + }; + + const response = await request(app) + .post('/api/v1/coupons') + .set('Authorization', `Bearer ${otherVendorToken}`) + .send(couponData); + expect(response.statusCode).toEqual(403); + expect(response.body.error).toEqual( + 'You can only create coupons for your own products' + ); + }); + + it('should retrieve all coupons', async () => { + const response = await request(app).get('/api/v1/coupons'); + + expect(response.statusCode).toEqual(200); + expect(Array.isArray(response.body)).toBeTruthy(); + }); + + it('should retrieve all coupons by vendor', async () => { + const response = await request(app) + .get('/api/v1/coupons/mine') + .set('Authorization', `Bearer ${token}`); + + expect(response.statusCode).toEqual(200); + expect(Array.isArray(response.body)).toBeTruthy(); + }); + + it('should retrieve a single coupon by ID', async () => { + const response = await request(app).get(`/api/v1/coupons/${couponId}`); + + expect(response.statusCode).toEqual(200); + expect(response.body).toBeDefined(); + }); + + it('should update a coupon by ID', async () => { + const updatedCouponData = { + description: 'Updated Coupon', + percentage: 20, + expirationDate: '2023-12-31', + applicableProducts: [productId], + }; + + const response = await request(app) + .put(`/api/v1/coupons/${couponId}`) + .set('Authorization', `Bearer ${token}`) + .send(updatedCouponData); + + expect(response.statusCode).toEqual(200); + expect(response.body).toBeDefined(); + }); + + it('should return a 404 for a non-existent coupon while updating', async () => { + const nonExistentCouponId = 999; + const updatedCouponData = { + description: 'Updated Coupon', + percentage: 20, + expirationDate: '2023-12-31', + applicableProducts: [productId], + }; + + const response = await request(app) + .put(`/api/v1/coupons/${nonExistentCouponId}`) + .set('Authorization', `Bearer ${token}`) + .send(updatedCouponData); + + expect(response.statusCode).toEqual(404); + expect(response.body.error).toEqual('Coupon not found'); + }); + + it('should return a 403 for a user trying to update a coupon for another user', async () => { + const otherVendorToken = await getVendorToken( + 'email@example.com', + 'Password123', + 'OtherVendor', + 'OtherName' + ); + const couponData = { + description: 'Updated Coupon', + percentage: 30, + expirationDate: '2024-12-31', + applicableProducts: [productId], + }; + + const response = await request(app) + .put(`/api/v1/coupons/${couponId}`) + .set('Authorization', `Bearer ${otherVendorToken}`) + .send(couponData); + expect(response.statusCode).toEqual(403); + expect(response.body.error).toEqual( + 'You can only create coupons for your own products' + ); + }); + + it('should return a 401 for an unauthenticated user', async () => { + const couponData = { + description: 'New Coupon', + percentage: 30, + expirationDate: '2024-12-31', + applicableProducts: [productId], + }; + + const response = await request(app) + .put(`/api/v1/coupons/${couponId}`) + .set('Authorization', 'Invalid Token') + .send(couponData); + expect(response.statusCode).toEqual(401); + }); + + it('should return a 401 for an unauthenticated user', async () => { + const couponData = { + description: 'New Coupon', + percentage: 30, + expirationDate: '2024-12-31', + applicableProducts: [productId], + }; + + const response = await request(app) + .post('/api/v1/coupons') + .set('Authorization', 'Invalid Token') + .send(couponData); + expect(response.statusCode).toEqual(401); + }); + + it('should return a 404 for a non-existent product', async () => { + const nonExistentProductId = 999; + const couponData = { + description: 'New Coupon', + percentage: 30, + expirationDate: '2024-12-31', + applicableProducts: [nonExistentProductId], + }; + + const response = await request(app) + .put(`/api/v1/coupons/${couponId}`) + .set('Authorization', `Bearer ${token}`) + .send(couponData); + + expect(response.statusCode).toEqual(400); + expect(response.body.error).toEqual( + `Product with id ${nonExistentProductId} not found` + ); + }); + + it('should return validation errors for invalid update data', async () => { + const invalidUpdateData = { + description: '', + percentage: 120, + expirationDate: '2022-12-31', + applicableProducts: [], + }; + + const response = await request(app) + .put(`/api/v1/coupons/${couponId}`) + .set('Authorization', `Bearer ${token}`) + .send(invalidUpdateData); + + expect(response.statusCode).toEqual(400); + expect(response.body.errors).toBeDefined(); + }); + + it('should return a 403 for a user trying to delete a coupon for another user', async () => { + const otherVendorToken = await getVendorToken( + 'email@example.com', + 'Password123', + 'OtherVendor', + 'OtherName' + ); + + const response = await request(app) + .delete(`/api/v1/coupons/${couponId}`) + .set('Authorization', `Bearer ${otherVendorToken}`); + expect(response.statusCode).toEqual(403); + expect(response.body.error).toEqual('You can only delete your own coupons'); + }); + + it('should delete a coupon by ID', async () => { + const response = await request(app) + .delete(`/api/v1/coupons/${couponId}`) + .set('Authorization', `Bearer ${token}`); + + expect(response.statusCode).toEqual(204); + }); + + it('should return a 404 for a non-existent coupon', async () => { + const nonExistentCouponId = 999; + const response = await request(app) + .delete(`/api/v1/coupons/${nonExistentCouponId}`) + .set('Authorization', `Bearer ${token}`); + + expect(response.statusCode).toEqual(404); + expect(response.body.error).toEqual('Coupon not found'); + }); +}); diff --git a/src/__test__/handlePayment.test.ts b/src/__test__/handlePayment.test.ts new file mode 100644 index 00000000..d0d032c9 --- /dev/null +++ b/src/__test__/handlePayment.test.ts @@ -0,0 +1,88 @@ +import request from 'supertest'; +import app from '../app'; // Adjust the import based on your project structure +import { getBuyerToken } from './testSetup'; // Adjust the import based on your project structure +import { Order } from '../database/models/orderEntity'; +import dbConnection from '../database'; +import Stripe from 'stripe'; + +jest.mock('stripe'); +const MockedStripe = Stripe as jest.Mocked; + +describe('handlePayment', () => { + let token: string; + let order: Order; + + beforeAll(async () => { + await dbConnection.initialize(); + await dbConnection.synchronize(true); // This will drop all tables + token = await getBuyerToken(); + // Create a mock order in the database + const orderRepository = dbConnection.getRepository(Order); + order = orderRepository.create({ + totalAmount: 100, + status: 'Pending', + trackingNumber: '123456', + paid: false, + }); + await orderRepository.save(order); + }); + + afterAll(async () => { + await dbConnection.close(); + }); + + it('should process payment successfully', async () => { + const mockChargesCreate = jest.fn().mockResolvedValue({ + id: 'charge_id', + amount: 10000, + currency: 'usd', + } as Stripe.Charge); + + MockedStripe.prototype.charges = { + create: mockChargesCreate, + } as unknown as Stripe.ChargesResource; + + const response = await request(app) + .post('/api/v1/buyer/payment') + .set('Authorization', `Bearer ${token}`) + .send({ token: 'fake-token', orderId: order.id }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + expect(response.body.paid).toBe(true); + expect(response.body.charge.id).toBe('charge_id'); + expect(mockChargesCreate).toHaveBeenCalledWith({ + amount: 10000, + currency: 'usd', + description: 'Test Charge', + source: 'fake-token', + }); + }); + + it('should return 404 if order not found', async () => { + const response = await request(app) + .post('/api/v1/buyer/payment') + .set('Authorization', `Bearer ${token}`) + .send({ token: 'fake-token', orderId: 999 }); + + expect(response.status).toBe(404); + expect(response.body.success).toBe(false); + expect(response.body.message).toBe('Order not found'); + }); + + it('should return 400 if order already paid', async () => { + // Set the order as paid + const orderRepository = dbConnection.getRepository(Order); + order.paid = true; + await orderRepository.save(order); + + const response = await request(app) + .post('/api/v1/buyer/payment') + .set('Authorization', `Bearer ${token}`) + .send({ token: 'fake-token', orderId: order.id }); + + expect(response.status).toBe(400); + expect(response.body.success).toBe(false); + expect(response.body.message).toBe('Order has already been paid'); + }); +}); diff --git a/src/__test__/imageUpload.test.ts b/src/__test__/imageUpload.test.ts index d7256e1f..33b893b2 100644 --- a/src/__test__/imageUpload.test.ts +++ b/src/__test__/imageUpload.test.ts @@ -2,22 +2,22 @@ import request from 'supertest'; import app from '../app'; import { getBuyerToken, afterAllHook, beforeAllHook } from './testSetup'; - beforeAll(beforeAllHook); afterAll(afterAllHook); - describe('Image upload Tests', () => { - let token: string; + let token: string; - beforeAll(async () => { - token = await getBuyerToken() - }); + beforeAll(async () => { + token = await getBuyerToken(); + }); - it('should delete profile image successfully', async() => { - const response = await request(app).delete('/api/v1/user/profileImg').set('Authorization',`Bearer ${token}`) - expect(response.status).toBe(200) - expect(response.body.message).toBe('Profile image successfully deleted') - expect(response.body.data).toBeDefined() - }) -}) \ No newline at end of file + it('should delete profile image successfully', async () => { + const response = await request(app) + .delete('/api/v1/user/profileImg') + .set('Authorization', `Bearer ${token}`); + expect(response.status).toBe(200); + expect(response.body.message).toBe('Profile image successfully deleted'); + expect(response.body.data).toBeDefined(); + }); +}); diff --git a/src/__test__/notification.vendor.test.ts b/src/__test__/notification.vendor.test.ts index bb0c838f..dbcb3ca0 100644 --- a/src/__test__/notification.vendor.test.ts +++ b/src/__test__/notification.vendor.test.ts @@ -12,33 +12,31 @@ afterAll(afterAllHook); describe('Notification Controller Tests', () => { let token: string; let vendor_id: number; - let notification_id: number + let notification_id: number; beforeAll(async () => { token = await getVendorToken(); - + // make notification for test // ------------------------------------------ const notification = new Notification_box(); - notification.product_id= 20; - notification.vendor_email= 'ericniyibizi1998@gmail.com'; + notification.product_id = 20; + notification.vendor_email = 'ericniyibizi1998@gmail.com'; notification.message_title = 'Test Notification'; notification.message_content = 'This is a test notification'; - notification.vendor_id = 1; + notification.vendor_id = 1; const savedNotification = await notificationRepository.save(notification); // -------------------------------------------------- - vendor_id = savedNotification.vendor_id - notification_id = savedNotification.notification_id + vendor_id = savedNotification.vendor_id; + notification_id = savedNotification.notification_id; }); it('should retrieve all notifications', async () => { - const response = await request(app) - .get('/api/v1/notification/vendor') + const response = await request(app).get('/api/v1/notification/vendor'); expect(response.statusCode).toEqual(200); expect(response.body.msg).toEqual('Notification retrieved successfully'); expect(Array.isArray(response.body.notification)).toBeTruthy(); - }); it('should retrieve notifications by vendor ID', async () => { @@ -60,12 +58,9 @@ describe('Notification Controller Tests', () => { }); it('should delete all notifications', async () => { - const response = await request(app) - .delete('/api/v1/notification/vendor') + const response = await request(app).delete('/api/v1/notification/vendor'); expect(response.statusCode).toEqual(200); expect(response.body.msg).toEqual('All notifications deleted successfully'); }); - - }); diff --git a/src/__test__/review.test.ts b/src/__test__/review.test.ts index 9f2c0810..5fc0fdbb 100644 --- a/src/__test__/review.test.ts +++ b/src/__test__/review.test.ts @@ -10,95 +10,94 @@ beforeAll(beforeAllHook); afterAll(afterAllHook); describe('Review controller test', () => { - let buyerToken: string; - let vendorToken: string; - let productId: number; - let categoryId: number; - - beforeAll(async () => { - buyerToken = await getBuyerToken(); - vendorToken = await getVendorToken(); - }); + let buyerToken: string; + let vendorToken: string; + let productId: number; + let categoryId: number; - it('should return review product has successfully', async () => { - const categoryData = { - name: 'Category4', - description: 'category description', - icon: 'category icon', - }; - - const categoryResponse = await request(app) - .post('/api/v1/category') - .set('Authorization', `Bearer ${vendorToken}`) - .send(categoryData); - - categoryId = categoryResponse.body.data.id; - - const productData = { - name: 'New Product Two', - image: 'new_product.jpg', - gallery: [], - shortDesc: 'This is a new product', - longDesc: 'Detailed description of the new product', - categoryId: categoryId, - quantity: 10, - regularPrice: 5, - salesPrice: 4, - tags: ['tag1', 'tag2'], - type: 'Simple', - isAvailable: true, - }; - - const responseProduct = await request(app) - .post('/api/v1/product') - .set('Authorization', `Bearer ${vendorToken}`) - .send(productData); - - - productId = responseProduct.body.data.id; - const reviewBody = {content:'good', rating:5, productId} - const responseReview = await request(app) - .post('/api/v1/review') - .set('Authorization', `Bearer ${buyerToken}`) - .send(reviewBody); - expect(responseReview.statusCode).toEqual(201); - expect(responseReview.body.message).toEqual('Review created successfully'); - expect(responseReview.body.review).toBeDefined(); - }), + beforeAll(async () => { + buyerToken = await getBuyerToken(); + vendorToken = await getVendorToken(); + }); + + it('should return review product has successfully', async () => { + const categoryData = { + name: 'Category4', + description: 'category description', + icon: 'category icon', + }; + + const categoryResponse = await request(app) + .post('/api/v1/category') + .set('Authorization', `Bearer ${vendorToken}`) + .send(categoryData); + + categoryId = categoryResponse.body.data.id; + const productData = { + name: 'New Product Two', + image: 'new_product.jpg', + gallery: [], + shortDesc: 'This is a new product', + longDesc: 'Detailed description of the new product', + categoryId: categoryId, + quantity: 10, + regularPrice: 5, + salesPrice: 4, + tags: ['tag1', 'tag2'], + type: 'Simple', + isAvailable: true, + }; + + const responseProduct = await request(app) + .post('/api/v1/product') + .set('Authorization', `Bearer ${vendorToken}`) + .send(productData); + + productId = responseProduct.body.data.id; + const reviewBody = { content: 'good', rating: 5, productId }; + const responseReview = await request(app) + .post('/api/v1/review') + .set('Authorization', `Bearer ${buyerToken}`) + .send(reviewBody); + expect(responseReview.statusCode).toEqual(201); + expect(responseReview.body.message).toEqual('Review created successfully'); + expect(responseReview.body.review).toBeDefined(); + }), it('should return 404 if product is not found', async () => { - const reviewBody = {content:'good', rating:5, productId:99} - const responseReview = await request(app) + const reviewBody = { content: 'good', rating: 5, productId: 99 }; + const responseReview = await request(app) .post('/api/v1/review') .set('Authorization', `Bearer ${buyerToken}`) .send(reviewBody); - expect(responseReview.statusCode).toEqual(404); - expect(responseReview.body.message).toEqual('Product not found'); + expect(responseReview.statusCode).toEqual(404); + expect(responseReview.body.message).toEqual('Product not found'); }), it('should return 200 Ok to get all reviews ', async () => { - const responseReview = await request(app) + const responseReview = await request(app) .get('/api/v1/review') - .set('Authorization', `Bearer ${buyerToken}`) - expect(responseReview.statusCode).toEqual(200); - expect(responseReview.body.reviews).toBeDefined(); + .set('Authorization', `Bearer ${buyerToken}`); + expect(responseReview.statusCode).toEqual(200); + expect(responseReview.body.reviews).toBeDefined(); }), it('should return 409 if the review exist', async () => { - const reviewBody = {content:'good', rating:5, productId} - const responseReview = await request(app) + const reviewBody = { content: 'good', rating: 5, productId }; + const responseReview = await request(app) .post('/api/v1/review') .set('Authorization', `Bearer ${buyerToken}`) - .send(reviewBody) - expect(responseReview.statusCode).toEqual(409); - expect(responseReview.body.message).toEqual('you are already reviewed the product'); - - }) + .send(reviewBody); + expect(responseReview.statusCode).toEqual(409); + expect(responseReview.body.message).toEqual( + 'you are already reviewed the product' + ); + }); - it('should return 400 for failed validation on create review', async () => { - const reviewBody = {content:'good', rating:15, productId:'some id'} - const responseReview = await request(app) - .post('/api/v1/review') - .set('Authorization', `Bearer ${buyerToken}`) - .send(reviewBody) - expect(responseReview.statusCode).toEqual(400); - }) -}) \ No newline at end of file + it('should return 400 for failed validation on create review', async () => { + const reviewBody = { content: 'good', rating: 15, productId: 'some id' }; + const responseReview = await request(app) + .post('/api/v1/review') + .set('Authorization', `Bearer ${buyerToken}`) + .send(reviewBody); + expect(responseReview.statusCode).toEqual(400); + }); +}); diff --git a/src/__test__/searchProduct.test.ts b/src/__test__/searchProduct.test.ts index 83355e30..6dfbd3a2 100644 --- a/src/__test__/searchProduct.test.ts +++ b/src/__test__/searchProduct.test.ts @@ -13,7 +13,6 @@ describe('Search Products Controller Test', () => { }); it('should search products with keyword', async () => { - const response = await request(app) .get('/api/v1/search?keyword=keyword') .set('Authorization', `Bearer ${buyerToken}`); @@ -34,7 +33,7 @@ describe('Search Products Controller Test', () => { .get('/api/v1/search?productName=productName') .set('Authorization', `Bearer ${buyerToken}`); expect(response.status).toBe(200); - expect(response.body.data).toBeDefined(); + expect(response.body.data).toBeDefined(); }); it('should search products and apply sorting', async () => { @@ -44,4 +43,4 @@ describe('Search Products Controller Test', () => { expect(response.status).toBe(200); expect(response.body.data).toBeDefined(); }); -}); \ No newline at end of file +}); diff --git a/src/__test__/userController.test.ts b/src/__test__/userController.test.ts index 88144c3e..00baa525 100644 --- a/src/__test__/userController.test.ts +++ b/src/__test__/userController.test.ts @@ -333,34 +333,37 @@ describe('User Login Tests', () => { }); }); - describe('Password Recover Tests', () => { const userData = { firstName: 'Test', lastName: 'User', email: 'test@gmail.com', password: 'TestPassword123', - userType: 'vendor' + userType: 'vendor', }; it('should generate a password reset token and send an email', async () => { // Register a user await request(app).post('/api/v1/user/register').send(userData); - const recoverUser = await userRepository.findOne({ where: { email: userData.email } }); - + const recoverUser = await userRepository.findOne({ + where: { email: userData.email }, + }); + if (recoverUser) { const response = await request(app) .post('/api/v1/user/recover') .send({ email: recoverUser.email }); expect(response.status).toBe(200); - expect(response.body.message).toEqual('Password reset token generated successfully'); + expect(response.body.message).toEqual( + 'Password reset token generated successfully' + ); } else { throw new Error('User not found'); } }); - + it('should return a 404 error if the user email is not found', async () => { const nonExistingEmail = 'nonexisting@example.com'; - + // Send a request to the recover endpoint with a non-existing email const response = await request(app) .post('/api/v1/user/recover') @@ -369,18 +372,19 @@ describe('Password Recover Tests', () => { expect(response.body.message).toEqual('User not found'); }); - it('should return 200 and update the password if the recovery token is valid and the user exists', async () => { - const recoverToken = jwt.sign({ email: userData.email }, process.env.JWT_SECRET as jwt.Secret, { expiresIn: '1h' }); + const recoverToken = jwt.sign( + { email: userData.email }, + process.env.JWT_SECRET as jwt.Secret, + { expiresIn: '1h' } + ); const response = await request(app) .put(`/api/v1/user/recover/confirm?recoverToken=${recoverToken}`) .send({ password: 'newPassword123' }); expect(response.status).toBe(200); expect(response.body.message).toEqual('Password updated successfully'); - - }); - + it('should return 404 if the recovery token is invalid or missing', async () => { const response = await request(app) .put('/api/v1/user/recover/confirm') @@ -390,14 +394,17 @@ describe('Password Recover Tests', () => { }); it('should return 404 if the user associated with the token does not exist', async () => { - const invalidToken = jwt.sign({ email: 'nonexistent@gmail.com' }, process.env.JWT_SECRET as jwt.Secret, { expiresIn: '1h' }); + const invalidToken = jwt.sign( + { email: 'nonexistent@gmail.com' }, + process.env.JWT_SECRET as jwt.Secret, + { expiresIn: '1h' } + ); const response = await request(app) .put(`/api/v1/user/recover/confirm?recoverToken=${invalidToken}`) .send({ password: 'newPassword123' }); expect(response.status).toBe(404); expect(response.body.message).toEqual('User not found'); }); - }); describe('Get All Users Tests', () => { @@ -407,7 +414,6 @@ describe('Get All Users Tests', () => { expect(response.status).toBe(200); expect(response.body.message).toEqual('Users fetched successfully'); }); - it('should delete all users and return a success message with count', async () => { const response = await request(app).delete('/api/v1/user/deleteUsers'); @@ -423,13 +429,13 @@ describe('update user Profile', () => { lastName: string; email: string; password?: string; - userType?: Role; + userType?: Role; googleId?: string; facebookId?: string; picture?: string; provider?: string; isVerified: boolean; - twoFactorCode?: number; + twoFactorCode?: number; } interface Role { @@ -438,76 +444,75 @@ describe('update user Profile', () => { permissions: string[]; } + let user: IUser | undefined | null; + const userData = { + firstName: 'jan', + lastName: 'bosco', + email: 'bosco@gmail.com', + password: 'boscoPassword123', + }; -let user: IUser | undefined | null; -const userData = { -firstName: 'jan', -lastName: 'bosco', -email: 'bosco@gmail.com', -password: 'boscoPassword123', -}; - -beforeEach(async () => { - -await request(app).post('/api/v1/register').send(userData); -user = await userRepository.findOne({ where: { email: userData.email } }); -}); + beforeEach(async () => { + await request(app).post('/api/v1/register').send(userData); + user = await userRepository.findOne({ where: { email: userData.email } }); + }); -it('should update the user profile successfully', async () => { -if (user) { - const newUserData = { - firstName: 'NewFirstName', - lastName: 'NewLastName', - email: 'newemail@example.com', - password: 'bosco@gmail.com', - }; + it('should update the user profile successfully', async () => { + if (user) { + const newUserData = { + firstName: 'NewFirstName', + lastName: 'NewLastName', + email: 'newemail@example.com', + password: 'bosco@gmail.com', + }; - const response = await request(app) - .put(`/api/v1/updateProfile/${user?.id}`) - .send(newUserData); - expect(response.statusCode).toBe(201); - expect(response.body.message).toBe('User updated successfully'); -} -}); + const response = await request(app) + .put(`/api/v1/updateProfile/${user?.id}`) + .send(newUserData); + expect(response.statusCode).toBe(201); + expect(response.body.message).toBe('User updated successfully'); + } + }); -it('should return 404 when user not found', async () => { -const Id = 999; -const response = await request(app) - .put(`/api/v1/updateProfile/${Id}`) - .send(userData); -expect(response.statusCode).toBe(404); -expect(response.body.error).toBe('User not found'); -}); + it('should return 404 when user not found', async () => { + const Id = 999; + const response = await request(app) + .put(`/api/v1/updateProfile/${Id}`) + .send(userData); + expect(response.statusCode).toBe(404); + expect(response.body.error).toBe('User not found'); + }); -it('should return 400 when email already exists', async () => { -if (user) { - const newUserData = { - firstName: 'NewFirstName', - lastName: 'NewLastName', - email: 'newemail@example.com', - password: 'bosco@gmail.com', - }; + it('should return 400 when email already exists', async () => { + if (user) { + const newUserData = { + firstName: 'NewFirstName', + lastName: 'NewLastName', + email: 'newemail@example.com', + password: 'bosco@gmail.com', + }; - const response = await request(app) - .put(`/api/v1/updateProfile/${user.id}`) - .send(newUserData); - expect(response.statusCode).toBe(400); - expect(response.body.error).toBe('Email is already taken'); -} -}); + const response = await request(app) + .put(`/api/v1/updateProfile/${user.id}`) + .send(newUserData); + expect(response.statusCode).toBe(400); + expect(response.body.error).toBe('Email is already taken'); + } + }); }); describe('User metrics tests', () => { - let adminToken:string; - beforeAll(async() => { - adminToken = await getAdminToken() - }) + let adminToken: string; + beforeAll(async () => { + adminToken = await getAdminToken(); + }); it('should get user metrics successfully', async () => { - const response = await request(app).get('/api/v1/user/get_metrics') - .set('Authorization', `Bearer ${adminToken}`) - - expect(response.status).toBe(200) - expect(response.body.buyerData).toBeDefined() - expect(response.body.vendorData).toBeDefined() - }) -}) + const response = await request(app) + .get('/api/v1/user/get_metrics') + .set('Authorization', `Bearer ${adminToken}`); + + expect(response.status).toBe(200); + expect(response.body.buyerData).toBeDefined(); + expect(response.body.vendorData).toBeDefined(); + }); +}); diff --git a/src/controller/couponController.ts b/src/controller/couponController.ts index a7b42c48..82febbce 100644 --- a/src/controller/couponController.ts +++ b/src/controller/couponController.ts @@ -11,148 +11,177 @@ const couponRepository = dbConnection.getRepository(Coupon); const productRepository = dbConnection.getRepository(Product); interface couponRequestBody { - description: string; - percentage: number; - expirationDate: Date; - applicableProducts: number[]; + description: string; + percentage: number; + expirationDate: Date; + applicableProducts: number[]; } const createCouponRules = [ - check('description').isLength({ min: 1 }).withMessage('coupon description is required'), - check('percentage').isInt({ min: 0, max: 100 }).withMessage('percentage must be between 0 and 100'), - check('expirationDate').isDate().withMessage('expiration date must be a valid date'), - check('applicableProducts').isArray().withMessage('applicable products must be an array of product ids'), -] + check('description') + .isLength({ min: 1 }) + .withMessage('coupon description is required'), + check('percentage') + .isInt({ min: 0, max: 100 }) + .withMessage('percentage must be between 0 and 100'), + check('expirationDate') + .isDate() + .withMessage('expiration date must be a valid date'), + check('applicableProducts') + .isArray() + .withMessage('applicable products must be an array of product ids'), +]; class CouponController { - // GET /coupons - public getAllCoupons = errorHandler(async (req: Request, res: Response) => { - const coupons = await couponRepository.find(); - return res.status(200).json(coupons); - }) + // GET /coupons + public getAllCoupons = errorHandler(async (req: Request, res: Response) => { + const coupons = await couponRepository.find(); + return res.status(200).json(coupons); + }); - public getCouponsByVendor = errorHandler(async (req: Request, res: Response) => { - const vendorId = (req.user as UserModel).id; - const coupons = await couponRepository - .createQueryBuilder('coupon') - .innerJoinAndSelect('coupon.applicableProducts', 'product') - .innerJoinAndSelect('product.vendor', 'vendor') - .where('vendor.id = :vendorId', { vendorId }) - .getMany(); - return res.status(200).json(coupons); - }) + public getCouponsByVendor = errorHandler( + async (req: Request, res: Response) => { + const vendorId = (req.user as UserModel).id; + const coupons = await couponRepository + .createQueryBuilder('coupon') + .innerJoinAndSelect('coupon.applicableProducts', 'product') + .innerJoinAndSelect('product.vendor', 'vendor') + .where('vendor.id = :vendorId', { vendorId }) + .getMany(); + return res.status(200).json(coupons); + } + ); - // POST /coupons - public createCoupon = [ - ...createCouponRules, - errorHandler(async (req: Request, res: Response) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - const { description, percentage, expirationDate, applicableProducts } = req.body as couponRequestBody; - const products: Product[] = [] - for (const productId of applicableProducts) { - const product = await productRepository.findOne({ - where: { id: productId }, - relations: ['vendor'] - }); - if (!product) { - return res.status(400).json({ error: `Product with id ${productId} not found` }); - } - if (product.vendor.id !== (req.user as UserModel).id) { - return res.status(403).json({ error: 'You can only create coupons for your own products' }); - } - products.push(product); - } - let code = crypto.randomBytes(4).toString('hex'); - while (await couponRepository.findOne({ where: { code } })) { - code = crypto.randomBytes(4).toString('hex'); - } - const coupon = new Coupon({ - code, - description, - percentage, - expirationDate, - applicableProducts: products - }); - const newCoupon = await couponRepository.save(coupon); - res.status(201).json({ - message: 'Coupon created successfully', - data: newCoupon - }); - }) - ] - - // GET /coupons/:id - public getCouponById = errorHandler(async(req: Request, res: Response) => { - const { id } = req.params; - const coupon = await couponRepository.findOne({ - where: { id: Number(id) }, - relations: ['applicableProducts'] + // POST /coupons + public createCoupon = [ + ...createCouponRules, + errorHandler(async (req: Request, res: Response) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + const { description, percentage, expirationDate, applicableProducts } = + req.body as couponRequestBody; + const products: Product[] = []; + for (const productId of applicableProducts) { + const product = await productRepository.findOne({ + where: { id: productId }, + relations: ['vendor'], }); - if (!coupon) { - return res.status(404).json({ error: 'Coupon not found' }); - } else { - return res.status(200).json(coupon); + if (!product) { + return res + .status(400) + .json({ error: `Product with id ${productId} not found` }); } - }) - - // PUT /coupons/:id - public updateCoupon = [ - ...createCouponRules, - errorHandler(async(req: Request, res: Response) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - const { id } = req.params; - const { description, percentage, expirationDate, applicableProducts } = req.body as couponRequestBody; - const coupon = await couponRepository.findOne({ - where: { id: Number(id) }, + if (product.vendor.id !== (req.user as UserModel).id) { + return res + .status(403) + .json({ + error: 'You can only create coupons for your own products', }); - if (!coupon) { - return res.status(404).json({ error: 'Coupon not found' }); - } - coupon.description = description; - coupon.percentage = percentage; - coupon.expirationDate = expirationDate; - const products: Product[] = [] - for (const productId of applicableProducts) { - const product = await productRepository.findOne({ - where: { id: productId }, - relations: ['vendor'] - }); - if (!product) { - return res.status(400).json({ error: `Product with id ${productId} not found` }); - } - if (product.vendor.id !== (req.user as UserModel).id) { - return res.status(403).json({ error: 'You can only create coupons for your own products' }); - } - products.push(product); - } - coupon.applicableProducts = products; - const updatedCoupon = await couponRepository.save(coupon) - return res.status(200).json(updatedCoupon); - }) - ] + } + products.push(product); + } + let code = crypto.randomBytes(4).toString('hex'); + while (await couponRepository.findOne({ where: { code } })) { + code = crypto.randomBytes(4).toString('hex'); + } + const coupon = new Coupon({ + code, + description, + percentage, + expirationDate, + applicableProducts: products, + }); + const newCoupon = await couponRepository.save(coupon); + res.status(201).json({ + message: 'Coupon created successfully', + data: newCoupon, + }); + }), + ]; - // DELETE /coupons/:id - public deleteCoupon = errorHandler(async(req: Request, res: Response) => { - const { id } = req.params; - const deletedCoupon = await couponRepository.findOne({ - where: { id: Number(id) }, - relations: ['applicableProducts', 'applicableProducts.vendor'] + // GET /coupons/:id + public getCouponById = errorHandler(async (req: Request, res: Response) => { + const { id } = req.params; + const coupon = await couponRepository.findOne({ + where: { id: Number(id) }, + relations: ['applicableProducts'], + }); + if (!coupon) { + return res.status(404).json({ error: 'Coupon not found' }); + } else { + return res.status(200).json(coupon); + } + }); + + // PUT /coupons/:id + public updateCoupon = [ + ...createCouponRules, + errorHandler(async (req: Request, res: Response) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + const { id } = req.params; + const { description, percentage, expirationDate, applicableProducts } = + req.body as couponRequestBody; + const coupon = await couponRepository.findOne({ + where: { id: Number(id) }, + }); + if (!coupon) { + return res.status(404).json({ error: 'Coupon not found' }); + } + coupon.description = description; + coupon.percentage = percentage; + coupon.expirationDate = expirationDate; + const products: Product[] = []; + for (const productId of applicableProducts) { + const product = await productRepository.findOne({ + where: { id: productId }, + relations: ['vendor'], }); - if (!deletedCoupon) { - return res.status(404).json({ error: 'Coupon not found' }); + if (!product) { + return res + .status(400) + .json({ error: `Product with id ${productId} not found` }); } - if (deletedCoupon.applicableProducts[0].vendor.id !== (req.user as UserModel).id) { - return res.status(403).json({ error: 'You can only delete your own coupons' }); - } else { - await couponRepository.delete({ id: Number(id) }); - return res.status(204).end(); + if (product.vendor.id !== (req.user as UserModel).id) { + return res + .status(403) + .json({ + error: 'You can only create coupons for your own products', + }); } - }) + products.push(product); + } + coupon.applicableProducts = products; + const updatedCoupon = await couponRepository.save(coupon); + return res.status(200).json(updatedCoupon); + }), + ]; + + // DELETE /coupons/:id + public deleteCoupon = errorHandler(async (req: Request, res: Response) => { + const { id } = req.params; + const deletedCoupon = await couponRepository.findOne({ + where: { id: Number(id) }, + relations: ['applicableProducts', 'applicableProducts.vendor'], + }); + if (!deletedCoupon) { + return res.status(404).json({ error: 'Coupon not found' }); + } + if ( + deletedCoupon.applicableProducts[0].vendor.id !== + (req.user as UserModel).id + ) { + return res + .status(403) + .json({ error: 'You can only delete your own coupons' }); + } else { + await couponRepository.delete({ id: Number(id) }); + return res.status(204).end(); + } + }); } export default CouponController; diff --git a/src/controller/notificationController.ts b/src/controller/notificationController.ts index 66be0365..d58b3e65 100644 --- a/src/controller/notificationController.ts +++ b/src/controller/notificationController.ts @@ -2,72 +2,68 @@ import { Request, Response } from 'express'; import Notification_box from '../database/models/inbox_notification'; import dbConnection from '../database'; import errorHandler from '../middlewares/errorHandler'; -const NotificationRepository = dbConnection.getRepository(Notification_box) +const NotificationRepository = dbConnection.getRepository(Notification_box); +export const getallNotification = errorHandler( + async (req: Request, res: Response) => { + const notification = await NotificationRepository.find(); + if (!notification) { + return res.status(400).json({ msg: 'notification is empty' }); + } -export const getallNotification= errorHandler( - async(req:Request, res:Response)=>{ - - const notification = await NotificationRepository.find() - - if(!notification) - { - return res.status(400).json({msg:'notification is empty'}) - } - - - return res.status(200).json({msg:'Notification retrieved successfully', notification}) - -} -) + return res + .status(200) + .json({ msg: 'Notification retrieved successfully', notification }); + } +); export const deleteallNotification = errorHandler( - async (req: Request , res: Response)=>{ - const Notication = await NotificationRepository.find() - if(!Notication) - { - return res.status(400).json({msg:'notification is empty'}) - } - - await NotificationRepository.remove(Notication) - - return res.status(200).json({msg:'All notifications deleted successfully'}) + async (req: Request, res: Response) => { + const Notication = await NotificationRepository.find(); + if (!Notication) { + return res.status(400).json({ msg: 'notification is empty' }); } -) + await NotificationRepository.remove(Notication); -export const deletenotification = errorHandler( - async (req: Request, res: Response) => { - const id: number = parseInt(req.params.id); + return res + .status(200) + .json({ msg: 'All notifications deleted successfully' }); + } +); - const notification = await NotificationRepository.findOne({ - where: { notification_id: id } - }); - +export const deletenotification = errorHandler( + async (req: Request, res: Response) => { + const id: number = parseInt(req.params.id); - if (!notification) { - return res.status(400).json({ msg: 'No Notifications was found' }); - } + const notification = await NotificationRepository.findOne({ + where: { notification_id: id }, + }); - await NotificationRepository.remove(notification); - return res.status(200).json({ msg: 'Notification removed successfully' }); + if (!notification) { + return res.status(400).json({ msg: 'No Notifications was found' }); } + + await NotificationRepository.remove(notification); + return res.status(200).json({ msg: 'Notification removed successfully' }); + } ); export const getvendorNotifications = errorHandler( - async (req: Request, res:Response)=>{ - - const id:number= parseInt(req.params.id) + async (req: Request, res: Response) => { + const id: number = parseInt(req.params.id); + + const notification = await NotificationRepository.find({ + where: { vendor_id: id }, + }); - const notification = await NotificationRepository.find({ - where:{vendor_id:id}}) - - if(!notification) - { - return res.status(400).json({msg: 'No Notifications was found'}) - } - - return res.status(200).json({msg:'Notifications retrieved successfully',notification}) - } -) \ No newline at end of file + if (!notification) { + return res.status(400).json({ msg: 'No Notifications was found' }); + } + + return res + .status(200) + .json({ msg: 'Notifications retrieved successfully', notification }); + } +); diff --git a/src/controller/orderController.ts b/src/controller/orderController.ts index e4903c68..ea23bd56 100644 --- a/src/controller/orderController.ts +++ b/src/controller/orderController.ts @@ -2,7 +2,7 @@ import { Request, Response } from 'express'; import dbConnection from '../database'; import errorHandler from '../middlewares/errorHandler'; import { Order } from '../database/models/orderEntity'; -import {eventEmitter} from '../Notification.vendor/event.services' +import { eventEmitter } from '../Notification.vendor/event.services'; const orderRepository = dbConnection.getRepository(Order); export const updateOrderStatus = errorHandler( @@ -43,8 +43,8 @@ export const updateOrderStatus = errorHandler( await orderRepository.save(order); - eventEmitter.emit('order_status_change',order.id) - + eventEmitter.emit('order_status_change', order.id); + return res .status(200) .json({ msg: `Order status updated to ${order.status}` }); diff --git a/src/controller/productController.ts b/src/controller/productController.ts index 857c27ce..37ea36cf 100644 --- a/src/controller/productController.ts +++ b/src/controller/productController.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { In } from 'typeorm'; +// import { In } from 'typeorm'; import Product from '../database/models/productEntity'; import Category from '../database/models/categoryEntity'; import { OrderDetails } from '../database/models/orderDetailsEntity'; @@ -478,10 +478,12 @@ export const getBestSellingProducts = async (req: Request, res: Response) => { const productIds = bestSellingProducts.map((item) => item.productId); - const products = await productRepository.findBy({ - id: In(productIds), - }); - + const products = await productRepository + .createQueryBuilder('product') + .leftJoinAndSelect('product.vendor', 'vendor') + .leftJoinAndSelect('product.category', 'category') + .where('product.id IN (:...ids)', { ids: productIds }) + .getMany(); if (!products || products.length === 0) { return res .status(404) @@ -494,13 +496,14 @@ export const getBestSellingProducts = async (req: Request, res: Response) => { ); return { ...product, + category: product.category.name, + vendor: { + firstName: product.vendor.firstName, + lastName: product.vendor.lastName, + }, sales: parseInt(productData.totalQuantity, 10), }; }); res.json(result); }; - - - - diff --git a/src/controller/reviewController.ts b/src/controller/reviewController.ts index 8e07a232..d802ce6f 100644 --- a/src/controller/reviewController.ts +++ b/src/controller/reviewController.ts @@ -4,99 +4,105 @@ import Product from '../database/models/productEntity'; import dbConnection from '../database'; import errorHandler from '../middlewares/errorHandler'; import UserModel from '../database/models/userModel'; -import {createReviewSchema} from '../middlewares/createReviewSchema'; +import { createReviewSchema } from '../middlewares/createReviewSchema'; const productRepository = dbConnection.getRepository(Product); const reviewRepository = dbConnection.getRepository(Review); - const userRepository = dbConnection.getRepository(UserModel); +const userRepository = dbConnection.getRepository(UserModel); -export const createReview = errorHandler(async (req: Request, res: Response) => { - const formData = req.body; +export const createReview = errorHandler( + async (req: Request, res: Response) => { + const formData = req.body; - const validationResult = createReviewSchema.validate(formData); - if (validationResult.error) { - return res - .status(400) - .json({ msg: validationResult.error.details[0].message }); - } + const validationResult = createReviewSchema.validate(formData); + if (validationResult.error) { + return res + .status(400) + .json({ msg: validationResult.error.details[0].message }); + } - const {content, rating, productId} = formData + const { content, rating, productId } = formData; - const userId = req.user!.id; + const userId = req.user!.id; - const product = await productRepository.findOne({ - where:{ - id:productId + const product = await productRepository.findOne({ + where: { + id: productId, + }, + }); + if (!product) { + return res.status(404).json({ message: 'Product not found' }); } - }); - if (!product) { - return res.status(404).json({ message: 'Product not found' }); - } - const user = await userRepository.findOne({ - where: { - id: userId, - }, - select: { - id: true, - }, - }); + const user = await userRepository.findOne({ + where: { + id: userId, + }, + select: { + id: true, + }, + }); - const existingReview = await reviewRepository.findOne({ - where:{ - user:{ - id:userId + const existingReview = await reviewRepository.findOne({ + where: { + user: { + id: userId, + }, + product: { + id: productId, + }, }, - product:{ - id:productId - } + }); + + if (existingReview) { + return res + .status(409) + .json({ message: 'you are already reviewed the product' }); } - }) - if(existingReview){ - return res.status(409).json({ message: 'you are already reviewed the product' }); - } + const newReview = new Review(); + newReview.content = content; + newReview.rating = parseInt(rating); + newReview.user = user!; + newReview.product = product; + + const review = await reviewRepository.save(newReview); - const newReview = new Review(); - newReview.content= content; - newReview.rating=parseInt(rating) ; - newReview.user = user!; - newReview.product = product; + const reviews = await reviewRepository.find({ + where: { + product: { + id: productId, + }, + }, + relations: ['user', 'product'], + }); + let totalRating = 0; + for (const review of reviews) { + totalRating += review.rating; + } + product.averageRating = Number( + (totalRating / reviews.length).toPrecision(2) + ); - const review = await reviewRepository.save(newReview); + await productRepository.save(product); + return res + .status(201) + .json({ message: 'Review created successfully', review }); + } +); + +export const getReviews = errorHandler(async (req: Request, res: Response) => { const reviews = await reviewRepository.find({ - where:{ - product:{ - id:productId - } + select: { + user: { + firstName: true, + }, + product: { + name: true, + }, }, - relations:['user','product'] + relations: ['user', 'product'], }); - let totalRating = 0; - for (const review of reviews) { - totalRating += review.rating; - } - product.averageRating = Number((totalRating / reviews.length).toPrecision(2)); - - - await productRepository.save(product); - - return res.status(201).json({ message: 'Review created successfully', review }); + return res.status(200).json({ reviews }); }); - - -export const getReviews = errorHandler(async (req: Request, res: Response) => { - const reviews = await reviewRepository.find({ - select:{ - user:{ - firstName:true - }, - product:{ - name:true - } - }, - relations:['user','product'] - }) - return res.status(200).json({ reviews }); -}); \ No newline at end of file diff --git a/src/controller/searchProducts.ts b/src/controller/searchProducts.ts index a72eefe2..131802a4 100644 --- a/src/controller/searchProducts.ts +++ b/src/controller/searchProducts.ts @@ -11,20 +11,34 @@ interface searchParams { category?: number[]; productName?: string; rating?: number[]; - minPrice?:number; - maxPrice?:number; + minPrice?: number; + maxPrice?: number; sort?: string; - page?:number; - limit?:number; + page?: number; + limit?: number; } -export const paginate = (query: SelectQueryBuilder, page: number, limit: number) => { +export const paginate = ( + query: SelectQueryBuilder, + page: number, + limit: number +) => { return query.skip((page - 1) * limit).take(limit); }; export const searchProducts = errorHandler( async (req: Request, res: Response) => { - const { keyword, category, productName, rating, minPrice, maxPrice, sort='DESC', page=1, limit=9 }: searchParams = req.query; + const { + keyword, + category, + productName, + rating, + minPrice, + maxPrice, + sort = 'DESC', + page = 1, + limit = 9, + }: searchParams = req.query; let queryBuilder = productRepository.createQueryBuilder('product'); @@ -36,24 +50,33 @@ export const searchProducts = errorHandler( } if (category && category.length > 0) { - queryBuilder = queryBuilder.andWhere('product.categoryId IN (:...category)', { category }); + queryBuilder = queryBuilder.andWhere( + 'product.categoryId IN (:...category)', + { category } + ); } if (productName) { queryBuilder = queryBuilder.andWhere('product.name ILIKE :productName', { - productName: `%${productName}%` + productName: `%${productName}%`, }); } if (rating && rating.length > 0) { - queryBuilder = queryBuilder.andWhere('product.averageRating IN (:...rating)', { rating }); + queryBuilder = queryBuilder.andWhere( + 'product.averageRating IN (:...rating)', + { rating } + ); } if (minPrice && maxPrice) { - queryBuilder = queryBuilder.andWhere('product.salesPrice BETWEEN :minPrice AND :maxPrice', { - minPrice, - maxPrice, - }); + queryBuilder = queryBuilder.andWhere( + 'product.salesPrice BETWEEN :minPrice AND :maxPrice', + { + minPrice, + maxPrice, + } + ); } if (sort) { @@ -66,7 +89,7 @@ export const searchProducts = errorHandler( return res.status(200).json({ data: products, - total + total, }); } -); \ No newline at end of file +); diff --git a/src/controller/userController.ts b/src/controller/userController.ts index 54a5dae4..11597322 100644 --- a/src/controller/userController.ts +++ b/src/controller/userController.ts @@ -257,14 +257,15 @@ export const recoverPassword = errorHandler( process.env.JWT_SECRET as jwt.Secret, { expiresIn: '1h' } ); - - const frontend_url = process.env.FRONTEND_URL || 'https://dynamite-frontend.netlify.app' + + const frontend_url = + process.env.FRONTEND_URL || 'https://dynamite-frontend.netlify.app'; const confirmLink = `${frontend_url}/reset-password/${recoverToken}`; process.env.NODE_ENV !== 'test' && - (await sendEmail('reset', user.email, { - name: user.firstName, - link: confirmLink, - })); + (await sendEmail('reset', user.email, { + name: user.firstName, + link: confirmLink, + })); return res.status(200).json({ message: 'Password reset token generated successfully', @@ -383,13 +384,11 @@ export const changeProfileImg = errorHandler( user!.picture = uploadResult.url; await userRepository.save(user!); - return res - .status(200) - .json({ - status: 'success', - message: 'Profile Image successfully updated', - data: { picture: uploadResult.url }, - }); + return res.status(200).json({ + status: 'success', + message: 'Profile Image successfully updated', + data: { picture: uploadResult.url }, + }); } ); @@ -405,29 +404,29 @@ export const removeProfileImg = errorHandler( user!.picture = process.env.DEFAULT_PROFILE_URL as string; await userRepository.save(user!); - return res - .status(200) - .json({ - message: 'Profile image successfully deleted', - data: { picture: process.env.DEFAULT_PROFILE_URL }, - }); + return res.status(200).json({ + message: 'Profile image successfully deleted', + data: { picture: process.env.DEFAULT_PROFILE_URL }, + }); } ); -export const getUserMetrics = errorHandler(async (req: Request, res: Response) => { - const users = await userRepository.find({relations:['userType']}); +export const getUserMetrics = errorHandler( + async (req: Request, res: Response) => { + const users = await userRepository.find({ relations: ['userType'] }); - const buyerData: number[] = Array(12).fill(0); - const vendorData: number[] = Array(12).fill(0); + const buyerData: number[] = Array(12).fill(0); + const vendorData: number[] = Array(12).fill(0); - for(const user of users){ - const monthIndex = user.createdAt.getMonth() - if(user.userType.name === 'Buyer'){ - buyerData[monthIndex] += 1 - }else if(user.userType.name === 'Vendor'){ - vendorData[monthIndex] += 1 + for (const user of users) { + const monthIndex = user.createdAt.getMonth(); + if (user.userType.name === 'Buyer') { + buyerData[monthIndex] += 1; + } else if (user.userType.name === 'Vendor') { + vendorData[monthIndex] += 1; + } } - } - return res.status(200).json({buyerData, vendorData}) -}) + return res.status(200).json({ buyerData, vendorData }); + } +); diff --git a/src/database/models/buyerWishList.ts b/src/database/models/buyerWishList.ts index f7aaaf47..20884ec8 100644 --- a/src/database/models/buyerWishList.ts +++ b/src/database/models/buyerWishList.ts @@ -1,26 +1,33 @@ -import { Column, Entity, ManyToMany, PrimaryGeneratedColumn, OneToOne, JoinColumn, JoinTable, ManyToOne } from 'typeorm'; +import { + Column, + Entity, + ManyToMany, + PrimaryGeneratedColumn, + OneToOne, + JoinColumn, + JoinTable, + ManyToOne, +} from 'typeorm'; import Product from './productEntity'; import UserModel from './userModel'; import Category from './categoryEntity'; @Entity() export default class BuyerWishList { + @PrimaryGeneratedColumn() + id: number; - @PrimaryGeneratedColumn() - id: number; + @OneToOne(() => UserModel) + @JoinColumn() + user: UserModel; - @OneToOne(() => UserModel) - @JoinColumn() - user: UserModel; + @ManyToMany(() => Product) + @JoinTable() + product: Product[]; - @ManyToMany(() => Product) - @JoinTable() - product: Product[]; - - @ManyToOne(() => Category) - category: Category; - - @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) - time: Date; + @ManyToOne(() => Category) + category: Category; + @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) + time: Date; } diff --git a/src/database/models/cartEntity.ts b/src/database/models/cartEntity.ts index c3b623dd..b59a75ba 100644 --- a/src/database/models/cartEntity.ts +++ b/src/database/models/cartEntity.ts @@ -28,4 +28,4 @@ export class Cart { @UpdateDateColumn() updatedAt: Date; -} \ No newline at end of file +} diff --git a/src/database/models/chatbotModel.ts b/src/database/models/chatbotModel.ts index 57d0a966..822333c4 100644 --- a/src/database/models/chatbotModel.ts +++ b/src/database/models/chatbotModel.ts @@ -1,20 +1,26 @@ -import { Entity,PrimaryGeneratedColumn,Column,ManyToOne,CreateDateColumn } from 'typeorm'; +import { + Entity, + PrimaryGeneratedColumn, + Column, + ManyToOne, + CreateDateColumn, +} from 'typeorm'; import User from './userModel'; @Entity() -export default class chat{ - @PrimaryGeneratedColumn() - id:number; +export default class chat { + @PrimaryGeneratedColumn() + id: number; - @ManyToOne(()=>User) - user: User; + @ManyToOne(() => User) + user: User; - @Column() - message:string; + @Column() + message: string; - @Column() - response:string; + @Column() + response: string; - @CreateDateColumn() - createdAt:Date; -} \ No newline at end of file + @CreateDateColumn() + createdAt: Date; +} diff --git a/src/database/models/couponEntity.ts b/src/database/models/couponEntity.ts index 092476bc..6eee7355 100644 --- a/src/database/models/couponEntity.ts +++ b/src/database/models/couponEntity.ts @@ -1,42 +1,42 @@ import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, - ManyToMany, - JoinTable - } from 'typeorm'; - import Product from './productEntity'; - - @Entity() - export default class Coupon { - @PrimaryGeneratedColumn() - id: number; - - @Column() - percentage: number; - - @Column() - code: string; - - @Column('date') - expirationDate: Date; - - @ManyToMany(() => Product) - @JoinTable() - applicableProducts: Product[]; - - @Column({ length: 250 }) - description: string; - - @CreateDateColumn() - createdAt: Date; - - @UpdateDateColumn() - updatedAt: Date; - - constructor(coupon: Partial) { - Object.assign(this, coupon); - } - } \ No newline at end of file + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + ManyToMany, + JoinTable, +} from 'typeorm'; +import Product from './productEntity'; + +@Entity() +export default class Coupon { + @PrimaryGeneratedColumn() + id: number; + + @Column() + percentage: number; + + @Column() + code: string; + + @Column('date') + expirationDate: Date; + + @ManyToMany(() => Product) + @JoinTable() + applicableProducts: Product[]; + + @Column({ length: 250 }) + description: string; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + + constructor(coupon: Partial) { + Object.assign(this, coupon); + } +} diff --git a/src/database/models/inbox_notification.ts b/src/database/models/inbox_notification.ts index f31d9069..161dcbfd 100644 --- a/src/database/models/inbox_notification.ts +++ b/src/database/models/inbox_notification.ts @@ -1,5 +1,10 @@ -import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn} from 'typeorm'; - +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, +} from 'typeorm'; @Entity() export default class Notification_box { @@ -7,26 +12,26 @@ export default class Notification_box { notification_id: number; @Column() - message_title:string; + message_title: string; @Column() message_content: string; - + @Column() product_id: number; - + @Column() vendor_id: number; - + @Column() - vendor_email:string; + vendor_email: string; @Column({ default: false }) isRead: boolean; @CreateDateColumn() createdAt: Date; - + @UpdateDateColumn() - updatedAt:Date + updatedAt: Date; } diff --git a/src/database/models/index.ts b/src/database/models/index.ts index d3ab2119..83728f0f 100644 --- a/src/database/models/index.ts +++ b/src/database/models/index.ts @@ -2,4 +2,4 @@ export * from './userModel'; export * from './roleEntity'; export * from './productEntity'; export * from './cartEntity'; -export * from './inbox_notification' \ No newline at end of file +export * from './inbox_notification'; diff --git a/src/database/models/reviewEntity.ts b/src/database/models/reviewEntity.ts index 7c1b8180..3b96f64e 100644 --- a/src/database/models/reviewEntity.ts +++ b/src/database/models/reviewEntity.ts @@ -1,7 +1,6 @@ - import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm'; -import Product from './productEntity'; -import User from './userModel'; +import Product from './productEntity'; +import User from './userModel'; @Entity() export class Review { diff --git a/src/database/models/serviceEntity.ts b/src/database/models/serviceEntity.ts index a9423911..b6757152 100644 --- a/src/database/models/serviceEntity.ts +++ b/src/database/models/serviceEntity.ts @@ -1,30 +1,29 @@ import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, - } from 'typeorm'; - - @Entity() - export default class Service { - @PrimaryGeneratedColumn() - id: number; - - @Column({ unique: true }) - name: string; - - @Column({ length: 250 }) - description: string; - - @CreateDateColumn() - createdAt: Date; - - @UpdateDateColumn() - updatedAt: Date; - - constructor(service: Partial) { - Object.assign(this, service); - } + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity() +export default class Service { + @PrimaryGeneratedColumn() + id: number; + + @Column({ unique: true }) + name: string; + + @Column({ length: 250 }) + description: string; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + + constructor(service: Partial) { + Object.assign(this, service); } - \ No newline at end of file +} diff --git a/src/database/models/userModel.ts b/src/database/models/userModel.ts index 7a724930..85dfd35a 100644 --- a/src/database/models/userModel.ts +++ b/src/database/models/userModel.ts @@ -9,8 +9,8 @@ import { } from 'typeorm'; import { Role } from './roleEntity'; import { Order } from './orderEntity'; -import dotenv from 'dotenv' -dotenv.config() +import dotenv from 'dotenv'; +dotenv.config(); @Entity() export default class UserModel { diff --git a/src/docs/couponDocs.ts b/src/docs/couponDocs.ts index 9ae05869..966bc2f9 100644 --- a/src/docs/couponDocs.ts +++ b/src/docs/couponDocs.ts @@ -25,7 +25,7 @@ * expirationDate: * type: string * format: date - * percentage: + * percentage: * type: number * applicableProducts: * type: array @@ -160,7 +160,7 @@ * expirationDate: * type: string * format: date - * percentage: + * percentage: * type: number * applicableProducts: * type: array diff --git a/src/docs/couponDoct.ts b/src/docs/couponDoct.ts index 36e6696c..9856143e 100644 --- a/src/docs/couponDoct.ts +++ b/src/docs/couponDoct.ts @@ -25,7 +25,7 @@ * expirationDate: * type: string * format: date - * percentage: + * percentage: * type: number * applicableProducts: * type: array @@ -143,7 +143,7 @@ * expirationDate: * type: string * format: date - * percentage: + * percentage: * type: number * applicableProducts: * type: array diff --git a/src/docs/notification.vendor.Docs.ts b/src/docs/notification.vendor.Docs.ts index 2b16bbe3..7436c8f1 100644 --- a/src/docs/notification.vendor.Docs.ts +++ b/src/docs/notification.vendor.Docs.ts @@ -1,4 +1,3 @@ - /** * @swagger * tags: @@ -26,7 +25,6 @@ * $ref: '#/components/schemas/Notification' */ - /** * @swagger * /api/v1/notification/vendor: @@ -38,7 +36,6 @@ * description: All notifications deleted successfully */ - /** * @swagger * /api/v1/notification/vendor/{id}: @@ -66,7 +63,6 @@ * $ref: '#/components/schemas/Notification' */ - /** * @swagger * /api/v1/notification/vendor/{id}: @@ -83,4 +79,4 @@ * responses: * '200': * description: Notification deleted successfully - */ \ No newline at end of file + */ diff --git a/src/docs/reviewDocs.ts b/src/docs/reviewDocs.ts index f5b66566..2fdd6d73 100644 --- a/src/docs/reviewDocs.ts +++ b/src/docs/reviewDocs.ts @@ -53,4 +53,4 @@ * properties: * reviews: * type: array - */ \ No newline at end of file + */ diff --git a/src/docs/searchProduct.ts b/src/docs/searchProduct.ts index bbdf4dca..c09170fb 100644 --- a/src/docs/searchProduct.ts +++ b/src/docs/searchProduct.ts @@ -50,4 +50,4 @@ * description: Invalid search parameters provided. * '500': * description: Internal Server Error. - */ \ No newline at end of file + */ diff --git a/src/docs/userRegisterDocs.ts b/src/docs/userRegisterDocs.ts index 63720bbe..89167856 100644 --- a/src/docs/userRegisterDocs.ts +++ b/src/docs/userRegisterDocs.ts @@ -252,7 +252,7 @@ * email: * type: string * format: email - * description: The updated email address of the user. + * description: The updated email address of the user. * responses: * '200': * description: User profile updated successfully. @@ -296,8 +296,6 @@ * description: A message indicating an internal server error occurred. */ - - /** * @swagger * /api/v1/user/recover/confirm: diff --git a/src/emails/index.ts b/src/emails/index.ts index 3a710c07..afeeca88 100644 --- a/src/emails/index.ts +++ b/src/emails/index.ts @@ -57,4 +57,4 @@ async function sendEmail(emailType: EmailType, recipient: string, data: Data) { } } -export default sendEmail; \ No newline at end of file +export default sendEmail; diff --git a/src/index.ts b/src/index.ts index 9167cce4..46b02233 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,7 @@ /* eslint-disable no-console */ import app from './app'; import { DbConnection } from './database'; -import cron_tasks from '../src/Notification.vendor/node.cron.services' - +import cron_tasks from '../src/Notification.vendor/node.cron.services'; declare module 'express-serve-static-core' { interface Request { @@ -22,5 +21,5 @@ const PORT = process.env.PORT; app.listen(PORT, () => console.log(`App is up and listening to ${PORT}`)); - cron_tasks.start() + cron_tasks.start(); })(); diff --git a/src/middlewares/availabilityValidator.ts b/src/middlewares/availabilityValidator.ts index abf40d69..b61a9825 100644 --- a/src/middlewares/availabilityValidator.ts +++ b/src/middlewares/availabilityValidator.ts @@ -2,14 +2,14 @@ import { body, validationResult } from 'express-validator'; import { Request, Response, NextFunction } from 'express'; const validateAvailability = [ - body('availability').isBoolean(), - (req: Request, res: Response, next: NextFunction) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - next(); - }, - ]; + body('availability').isBoolean(), + (req: Request, res: Response, next: NextFunction) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + next(); + }, +]; -export default validateAvailability; \ No newline at end of file +export default validateAvailability; diff --git a/src/middlewares/cloudinary.ts b/src/middlewares/cloudinary.ts index 1c73f9bc..14dd63f2 100644 --- a/src/middlewares/cloudinary.ts +++ b/src/middlewares/cloudinary.ts @@ -1,12 +1,11 @@ -import { v2 as cloudinary } from 'cloudinary' -import dotenv from 'dotenv' -dotenv.config() +import { v2 as cloudinary } from 'cloudinary'; +import dotenv from 'dotenv'; +dotenv.config(); - -cloudinary.config({ +cloudinary.config({ cloud_name: process.env.CLOUD_NAME, api_key: process.env.CLOUD_API_KEY, - api_secret: process.env.CLOUD_API_SECRET + api_secret: process.env.CLOUD_API_SECRET, }); -export default cloudinary \ No newline at end of file +export default cloudinary; diff --git a/src/middlewares/createReviewSchema.ts b/src/middlewares/createReviewSchema.ts index 3eba04df..4241413e 100644 --- a/src/middlewares/createReviewSchema.ts +++ b/src/middlewares/createReviewSchema.ts @@ -1,8 +1,7 @@ import Joi from 'joi'; - export const createReviewSchema = Joi.object({ - content: Joi.string().required(), - rating: Joi.number().min(0).max(5).required(), - productId: Joi.number().required() -}) \ No newline at end of file + content: Joi.string().required(), + rating: Joi.number().min(0).max(5).required(), + productId: Joi.number().required(), +}); diff --git a/src/middlewares/isLoggedIn.ts b/src/middlewares/isLoggedIn.ts index dd7706b3..17fea984 100644 --- a/src/middlewares/isLoggedIn.ts +++ b/src/middlewares/isLoggedIn.ts @@ -30,4 +30,4 @@ export const IsLoggedIn = (req: Request, res: Response, next: NextFunction) => { } catch (error) { return res.status(401).json({ message: 'Unauthorized: Invalid token' }); } -}; \ No newline at end of file +}; diff --git a/src/middlewares/multer.ts b/src/middlewares/multer.ts index 433fedf0..da86a180 100644 --- a/src/middlewares/multer.ts +++ b/src/middlewares/multer.ts @@ -1,5 +1,5 @@ -import multer from 'multer' +import multer from 'multer'; const storage = multer.memoryStorage(); const upload = multer({ storage }); -export default upload \ No newline at end of file +export default upload; diff --git a/src/middlewares/productAvailabilityWatch.ts b/src/middlewares/productAvailabilityWatch.ts index eab146c1..fd76ddaa 100644 --- a/src/middlewares/productAvailabilityWatch.ts +++ b/src/middlewares/productAvailabilityWatch.ts @@ -1,7 +1,6 @@ import Product from '../database/models/productEntity'; import dbConnection from '../database'; - const productRepository = dbConnection.getRepository(Product); /** * Function to check the availability of a product based on its quantity. @@ -11,17 +10,17 @@ const productRepository = dbConnection.getRepository(Product); * @param product - The ProductModel. */ const productQuantityWatch = async (product: Product) => { - const quantity = product.quantity; + const quantity = product.quantity; - if (quantity > 0) { - product.isAvailable = true; - } else { - product.isAvailable = false; - } + if (quantity > 0) { + product.isAvailable = true; + } else { + product.isAvailable = false; + } - await productRepository.save(product) + await productRepository.save(product); - return product; + return product; }; -export default productQuantityWatch; \ No newline at end of file +export default productQuantityWatch; diff --git a/src/middlewares/validateSearchParams.ts b/src/middlewares/validateSearchParams.ts index 5a134b6b..207a5124 100644 --- a/src/middlewares/validateSearchParams.ts +++ b/src/middlewares/validateSearchParams.ts @@ -1,20 +1,38 @@ import { query, validationResult } from 'express-validator'; import { Request, Response, NextFunction } from 'express'; export const validateSearchParams = [ - query('keyword').optional().isString().withMessage('Keyword must be a string'), - query('category').optional().isArray().withMessage('Category must be an array'), + query('keyword') + .optional() + .isString() + .withMessage('Keyword must be a string'), + query('category') + .optional() + .isArray() + .withMessage('Category must be an array'), query('rating').optional().isArray().withMessage('Rating must be an array'), query('page').optional().isString().withMessage('Page must be a number'), - query('minPrice').optional().isString().withMessage('minPrice must be a number'), - query('maxPrice').optional().isString().withMessage('maxPrice must be a number'), + query('minPrice') + .optional() + .isString() + .withMessage('minPrice must be a number'), + query('maxPrice') + .optional() + .isString() + .withMessage('maxPrice must be a number'), query('brand').optional().isString().withMessage('Brand must be a string'), - query('productName').optional().isString().withMessage('Product name must be a string'), - query('sort').optional().isIn(['asc', 'desc']).withMessage('Sort order must be either asc or desc'), + query('productName') + .optional() + .isString() + .withMessage('Product name must be a string'), + query('sort') + .optional() + .isIn(['asc', 'desc']) + .withMessage('Sort order must be either asc or desc'), (req: Request, res: Response, next: NextFunction) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } next(); - } -]; \ No newline at end of file + }, +]; diff --git a/src/routes/couponRoute.ts b/src/routes/couponRoute.ts index 5aa4465c..455fa583 100644 --- a/src/routes/couponRoute.ts +++ b/src/routes/couponRoute.ts @@ -2,21 +2,21 @@ import express from 'express'; import CouponController from '../controller/couponController'; import { IsLoggedIn } from '../middlewares/isLoggedIn'; - const couponRouter = express.Router(); const controller = new CouponController(); -couponRouter.route('/') - .get(controller.getAllCoupons) - .post(IsLoggedIn, controller.createCoupon); +couponRouter + .route('/') + .get(controller.getAllCoupons) + .post(IsLoggedIn, controller.createCoupon); -couponRouter.route('/mine') - .get(IsLoggedIn, controller.getCouponsByVendor); +couponRouter.route('/mine').get(IsLoggedIn, controller.getCouponsByVendor); -couponRouter.route('/:id') - .get(controller.getCouponById) - .put(IsLoggedIn, controller.updateCoupon) - .delete(IsLoggedIn, controller.deleteCoupon); +couponRouter + .route('/:id') + .get(controller.getCouponById) + .put(IsLoggedIn, controller.updateCoupon) + .delete(IsLoggedIn, controller.deleteCoupon); -export default couponRouter; \ No newline at end of file +export default couponRouter; diff --git a/src/routes/index.ts b/src/routes/index.ts index 71bb400b..c8abbd60 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -9,7 +9,7 @@ import couponRouter from './couponRoute'; import chekoutRoutes from './checkoutRoutes'; import reviewRoute from './reviewRoutes'; import orderRoutes from './orderRoutes'; -import noticificationRoute from './notificationRoutes' +import noticificationRoute from './notificationRoutes'; const router = Router(); router.use('/user', userRouter); @@ -22,5 +22,5 @@ router.use('/coupons', couponRouter); router.use('/checkout', chekoutRoutes); router.use('/review', reviewRoute); router.use('/order', orderRoutes); -router.use('/notification',noticificationRoute) +router.use('/notification', noticificationRoute); export default router; diff --git a/src/routes/notificationRoutes.ts b/src/routes/notificationRoutes.ts index 1b2b184e..f9d65eae 100644 --- a/src/routes/notificationRoutes.ts +++ b/src/routes/notificationRoutes.ts @@ -2,17 +2,20 @@ import { Router } from 'express'; import { IsLoggedIn } from '../middlewares/isLoggedIn'; import { checkRole } from '../middlewares/authorize'; import { - getallNotification, - deleteallNotification, - deletenotification, - getvendorNotifications} from '../controller/notificationController' + getallNotification, + deleteallNotification, + deletenotification, + getvendorNotifications, +} from '../controller/notificationController'; const notificationRouter = Router(); -notificationRouter.route('/vendor') - .get(getallNotification) - .delete(deleteallNotification) -notificationRouter.route('/vendor/:id') - .delete(IsLoggedIn,checkRole(['Vendor']),deletenotification) - .get(IsLoggedIn,checkRole(['Vendor']),getvendorNotifications) +notificationRouter + .route('/vendor') + .get(getallNotification) + .delete(deleteallNotification); +notificationRouter + .route('/vendor/:id') + .delete(IsLoggedIn, checkRole(['Vendor']), deletenotification) + .get(IsLoggedIn, checkRole(['Vendor']), getvendorNotifications); -export default notificationRouter \ No newline at end of file +export default notificationRouter; diff --git a/src/routes/reviewRoutes.ts b/src/routes/reviewRoutes.ts index 871000a1..cb7f995c 100644 --- a/src/routes/reviewRoutes.ts +++ b/src/routes/reviewRoutes.ts @@ -1,11 +1,10 @@ - -import {createReview ,getReviews} from '../controller/reviewController'; +import { createReview, getReviews } from '../controller/reviewController'; import { Router } from 'express'; import { IsLoggedIn } from '../middlewares/isLoggedIn'; import { checkRole } from '../middlewares/authorize'; const reviewRoute = Router(); -reviewRoute.use(IsLoggedIn , checkRole(['Buyer'])) -reviewRoute.route('/').post( createReview).get(getReviews) +reviewRoute.use(IsLoggedIn, checkRole(['Buyer'])); +reviewRoute.route('/').post(createReview).get(getReviews); export default reviewRoute; diff --git a/src/routes/searchRoutes.ts b/src/routes/searchRoutes.ts index 90cebeac..3a295292 100644 --- a/src/routes/searchRoutes.ts +++ b/src/routes/searchRoutes.ts @@ -1,8 +1,8 @@ import { Router } from 'express'; -import { searchProducts } from '../controller/searchProducts' +import { searchProducts } from '../controller/searchProducts'; import { validateSearchParams } from '../middlewares/validateSearchParams'; const searchRouter = Router(); searchRouter.get('/search', validateSearchParams, searchProducts); -export default searchRouter; \ No newline at end of file +export default searchRouter; diff --git a/src/routes/serviceRoutes.ts b/src/routes/serviceRoutes.ts index 0318acfb..11c48a19 100644 --- a/src/routes/serviceRoutes.ts +++ b/src/routes/serviceRoutes.ts @@ -5,9 +5,11 @@ import { checkRole } from '../middlewares/authorize'; const router = Router(); -router.post('/service', -// IsLoggedIn, checkRole(['Admin']), - createService); +router.post( + '/service', + // IsLoggedIn, checkRole(['Admin']), + createService +); router.get('/services', IsLoggedIn, checkRole(['Buyer']), getAllServices); diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index 74a0d712..80b02148 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -12,7 +12,7 @@ import { deleteUser, changeProfileImg, removeProfileImg, - getUserMetrics + getUserMetrics, } from '../controller/userController'; import { @@ -26,7 +26,7 @@ import { removeSubscriber, getAllSubscriber, } from '../controller/subscribeController'; -import upload from '../middlewares/multer' +import upload from '../middlewares/multer'; const userRouter = Router(); userRouter.post('/register', registerUser); @@ -56,6 +56,14 @@ userRouter.put('/updateProfile/:id', updateProfile); userRouter.post('/subscribe', subscribe); userRouter.get('/subscribe/delete/:id', removeSubscriber); userRouter.get('/subscribe/getAll', getAllSubscriber); -userRouter.route('/profileImg').patch(IsLoggedIn, upload.fields([{name:'image'}]), changeProfileImg).delete(IsLoggedIn, removeProfileImg) -userRouter.get('/get_metrics', IsLoggedIn, checkRole(['Admin']), getUserMetrics) +userRouter + .route('/profileImg') + .patch(IsLoggedIn, upload.fields([{ name: 'image' }]), changeProfileImg) + .delete(IsLoggedIn, removeProfileImg); +userRouter.get( + '/get_metrics', + IsLoggedIn, + checkRole(['Admin']), + getUserMetrics +); export default userRouter; diff --git a/src/utilis/couponCalculator.ts b/src/utilis/couponCalculator.ts index 5f23f470..b8f2dc64 100644 --- a/src/utilis/couponCalculator.ts +++ b/src/utilis/couponCalculator.ts @@ -4,16 +4,27 @@ import dbConnection from '../database'; const couponRepository = dbConnection.getRepository(Coupon); -export default async function applyCoupon(product: Product, couponCode: string, price: number): Promise { - const coupon = await couponRepository.findOne({ where: { code: couponCode }, relations: ['applicableProducts'] }); +export default async function applyCoupon( + product: Product, + couponCode: string, + price: number +): Promise { + const coupon = await couponRepository.findOne({ + where: { code: couponCode }, + relations: ['applicableProducts'], + }); - if (!coupon) { - return price; - } + if (!coupon) { + return price; + } - if (!coupon.applicableProducts.find(applicableProduct => applicableProduct.id === product.id)) { - return price; - } - const percentage = coupon.percentage / 100; - return price * (1 - percentage); -} \ No newline at end of file + if ( + !coupon.applicableProducts.find( + (applicableProduct) => applicableProduct.id === product.id + ) + ) { + return price; + } + const percentage = coupon.percentage / 100; + return price * (1 - percentage); +} diff --git a/src/utilis/nlp.ts b/src/utilis/nlp.ts index 98f2681e..690043e4 100644 --- a/src/utilis/nlp.ts +++ b/src/utilis/nlp.ts @@ -9,8 +9,7 @@ import { getCartItems, getTotalCartAmount, getCartItemQuantity, - getProductCategories - + getProductCategories, } from '../service/chatbotService'; export const extractKeyword = (message: string, keyword: string): string => { @@ -47,56 +46,91 @@ export const identifyIntent = ( ) return { intent: 'listProducts', keyword: '' }; - if ( - (message.includes('do you have') && message.includes('from product stock')) || - (message.includes('is there') && message.includes('in your stock')) || - (message.includes('can I find') && message.includes('in the product stock')) || - (message.includes('do you carry') && message.includes('from your stock')) || - (message.includes('is it available') && message.includes('from the stock')) || - (message.includes('do you offer') && message.includes('from your product stock')) || - (message.includes('did you have') && message.includes('from product stock')) || - (message.includes('had') && message.includes('from your stock')) || - (message.includes('has') && message.includes('in stock')) || - (message.includes('will you have') && message.includes('from product stock')) || - (message.includes('would you have') && message.includes('from your stock')) + if ( + (message.includes('do you have') && + message.includes('from product stock')) || + (message.includes('is there') && message.includes('in your stock')) || + (message.includes('can I find') && + message.includes('in the product stock')) || + (message.includes('do you carry') && message.includes('from your stock')) || + (message.includes('is it available') && + message.includes('from the stock')) || + (message.includes('do you offer') && + message.includes('from your product stock')) || + (message.includes('did you have') && + message.includes('from product stock')) || + (message.includes('had') && message.includes('from your stock')) || + (message.includes('has') && message.includes('in stock')) || + (message.includes('will you have') && + message.includes('from product stock')) || + (message.includes('would you have') && message.includes('from your stock')) ) { - const productName = message - .split(/do you have |is there |can I find |do you carry |is it available |do you offer |did you have |had |has |will you have |would you have /i)[1] - .split(/ from product stock| in your stock| in the product stock| from your stock| from the stock| from your product stock/i)[0]; - return { intent: 'checkProductStock', keyword: productName.trim() }; + const productName = message + .split( + /do you have |is there |can I find |do you carry |is it available |do you offer |did you have |had |has |will you have |would you have /i + )[1] + .split( + / from product stock| in your stock| in the product stock| from your stock| from the stock| from your product stock/i + )[0]; + return { intent: 'checkProductStock', keyword: productName.trim() }; } - const detailKeywords = [ - 'tell me more about', 'give me more information on','give me more info on', 'give me info on', 'more details about', - 'info about', 'details on', 'can you tell me details about', 'what are the details of', - 'give me the details on', 'i need more details about', 'i am looking for information about', - 'info on', 'i want details on' + 'tell me more about', + 'give me more information on', + 'give me more info on', + 'give me info on', + 'more details about', + 'info about', + 'details on', + 'can you tell me details about', + 'what are the details of', + 'give me the details on', + 'i need more details about', + 'i am looking for information about', + 'info on', + 'i want details on', ]; - - const detailRegex = new RegExp(`(?:${detailKeywords.join('|')})\\s*([\\w\\s]+)`, 'i'); - - if (detailKeywords.some(keyword => message.toLowerCase().includes(keyword))) { + + const detailRegex = new RegExp( + `(?:${detailKeywords.join('|')})\\s*([\\w\\s]+)`, + 'i' + ); + + if ( + detailKeywords.some((keyword) => message.toLowerCase().includes(keyword)) + ) { const productNameMatch = message.match(detailRegex); const productName = productNameMatch ? productNameMatch[1].trim() : ''; return { intent: 'productDetails', keyword: productName }; } - const priceKeywords = [ - 'price of', 'cost of', 'how much is', 'how much does', 'how much for', - 'what is the price of', 'what\'s the price of', 'what is the cost of', - 'can you tell me the price of', 'i want to know the cost of' + 'price of', + 'cost of', + 'how much is', + 'how much does', + 'how much for', + 'what is the price of', + 'what\'s the price of', + 'what is the cost of', + 'can you tell me the price of', + 'i want to know the cost of', ]; - - const priceRegex = new RegExp(`(?:${priceKeywords.join('|')})\\s*([\\w\\s]+)`, 'i'); - - if (priceKeywords.some(keyword => message.toLowerCase().includes(keyword))) { + + const priceRegex = new RegExp( + `(?:${priceKeywords.join('|')})\\s*([\\w\\s]+)`, + 'i' + ); + + if ( + priceKeywords.some((keyword) => message.toLowerCase().includes(keyword)) + ) { const productNameMatch = message.match(priceRegex); const productName = productNameMatch ? productNameMatch[1].trim() : ''; return { intent: 'productPrice', keyword: productName }; } - + if ( lowerMessage.includes('what product categories do you have') || lowerMessage.includes('list of product categories') || @@ -127,96 +161,179 @@ export const identifyIntent = ( lowerMessage.includes('would you list your product categories?') || lowerMessage.includes('could you show me the product categories?') || lowerMessage.includes('is there a list of your product categories?') || - lowerMessage.includes('can you explain what product categories you offer?') || - lowerMessage.includes('what are the types of product categories you have?') || + lowerMessage.includes( + 'can you explain what product categories you offer?' + ) || + lowerMessage.includes( + 'what are the types of product categories you have?' + ) || lowerMessage.includes('I need to know the product categories you offer.') || - lowerMessage.includes('Could you give me specifics on your product categories?') || - lowerMessage.includes('What are the kinds of product categories available?') || - lowerMessage.includes('Tell me more about the product categories you have.') || - lowerMessage.includes('Are there any descriptions of your product categories?') || - lowerMessage.includes('Can you elaborate on the product categories you offer?') || - lowerMessage.includes('What are the names of the product categories you have?') || + lowerMessage.includes( + 'Could you give me specifics on your product categories?' + ) || + lowerMessage.includes( + 'What are the kinds of product categories available?' + ) || + lowerMessage.includes( + 'Tell me more about the product categories you have.' + ) || + lowerMessage.includes( + 'Are there any descriptions of your product categories?' + ) || + lowerMessage.includes( + 'Can you elaborate on the product categories you offer?' + ) || + lowerMessage.includes( + 'What are the names of the product categories you have?' + ) || lowerMessage.includes('Do you have a variety of product categories?') || lowerMessage.includes('Show me the range of product categories you offer.') -) { + ) { return { intent: 'listProductCategories', keyword: '' }; -} -if (lowerMessage.includes('products from')) { - const categoryName = lowerMessage - .split('products from')[1] - .trim(); - return { intent: 'listProductsByCategoryName', keyword: categoryName }; -} - -if ( - lowerMessage.includes('what products do you have from') || - lowerMessage.includes('list products from') || - lowerMessage.includes('show me products from') || - lowerMessage.includes('products in the category') || - lowerMessage.includes('products from the category') || - lowerMessage.includes('products with category') -) { - const categoryName = lowerMessage - .split(/what products do you have from |list products from |show me products from |products in the category |products from the category |products with category /i)[1] - .trim(); - return { intent: 'listProductsByCategory', keyword: categoryName }; -} + } + if (lowerMessage.includes('products from')) { + const categoryName = lowerMessage.split('products from')[1].trim(); + return { intent: 'listProductsByCategoryName', keyword: categoryName }; + } + if ( + lowerMessage.includes('what products do you have from') || + lowerMessage.includes('list products from') || + lowerMessage.includes('show me products from') || + lowerMessage.includes('products in the category') || + lowerMessage.includes('products from the category') || + lowerMessage.includes('products with category') + ) { + const categoryName = lowerMessage + .split( + /what products do you have from |list products from |show me products from |products in the category |products from the category |products with category /i + )[1] + .trim(); + return { intent: 'listProductsByCategory', keyword: categoryName }; + } const reviewKeywords = [ - 'review of', 'reviews of', 'what are the reviews of', 'can you tell me the reviews of', - 'i want to know the reviews of', 'what\'s the review of', 'how are the reviews of' + 'review of', + 'reviews of', + 'what are the reviews of', + 'can you tell me the reviews of', + 'i want to know the reviews of', + 'what\'s the review of', + 'how are the reviews of', ]; - - const reviewRegex = new RegExp(`(?:${reviewKeywords.join('|')})\\s*([\\w\\s]+)`, 'i'); - - if (reviewKeywords.some(keyword => message.toLowerCase().includes(keyword))) { + + const reviewRegex = new RegExp( + `(?:${reviewKeywords.join('|')})\\s*([\\w\\s]+)`, + 'i' + ); + + if ( + reviewKeywords.some((keyword) => message.toLowerCase().includes(keyword)) + ) { const productNameMatch = message.match(reviewRegex); const productName = productNameMatch ? productNameMatch[1].trim() : ''; return { intent: 'productReview', keyword: productName }; } - const cancelOrderKeywords = [ - 'cancel my order', 'cancel order', 'can i cancel my order', 'how to cancel my order', - 'how do i cancel my order', 'order cancellation', 'stop my order', 'cancel this order', - 'cancel the order', 'am i able to cancel my order?', 'will i be able to cancel my order?', - 'could i cancel my order?', 'i want to cancel my order', 'please cancel my order', - 'what is the process to cancel my order?', 'how can i stop my order?', 'i need to cancel my order', - 'i wish to cancel my order', 'i\'d like to cancel my order', 'i have decided to cancel my order', - 'i\'m thinking of canceling my order', 'i intend to cancel my order', 'i plan to cancel my order', - 'i aim to cancel my order', 'i\'m considering canceling my order', 'i\'m pondering over canceling my order', - 'i\'m mulling over canceling my order', 'i\'m deliberating whether to cancel my order', 'i\'m hesitating to cancel my order', - 'i\'m uncertain about canceling my order', 'i\'m skeptical about canceling my order', 'i\'m hesitant to cancel my order', - 'i\'m indecisive about canceling my order', 'i\'m wavering on canceling my order', 'i\'m contemplating canceling my order', - 'i\'m second-guessing canceling my order', 'i\'m reconsidering canceling my order', 'i\'m reevaluating canceling my order', - 'i\'m reassessing canceling my order', 'i\'m reviewing canceling my order', 'i\'m evaluating canceling my order', - 'i\'m assessing canceling my order', 'i\'m analyzing canceling my order', 'i\'m scrutinizing canceling my order', - 'i\'m examining canceling my order', 'i\'m inspecting canceling my order', 'i\'m probing canceling my order', - 'i\'m investigating canceling my order', 'i\'m researching canceling my order', 'i\'m studying canceling my order', - 'i\'m looking into canceling my order', 'i\'m delving into canceling my order', 'i\'m diving into canceling my order', - 'i\'m getting into canceling my order', 'i\'m stepping into canceling my order', 'i\'m venturing into canceling my order', - 'i\'m plunging into canceling my order', 'i\'m sinking into canceling my order', 'i\'m immersing myself into canceling my order', - 'i\'m submerging myself into canceling my order', 'i\'m engaging in canceling my order', 'i\'m participating in canceling my order', - 'i\'m involved in canceling my order', 'i\'m implicated in canceling my order', 'i\'m complicit in canceling my order', - 'i\'m party to canceling my order', 'i\'m privy to canceling my order', 'i\'m aware of canceling my order', - 'i\'m cognizant of canceling my order', 'i\'m mindful of canceling my order', 'i\'m conscious of canceling my order', - 'i\'m alert to canceling my order', 'i\'m awake to canceling my order', 'i\'m vigilant about canceling my order', - 'i\'m watchful of canceling my order', 'i\'m observant of canceling my order', 'i\'m attentive to canceling my order', - 'i\'m keen on canceling my order', 'i\'m eager to cancel my order' + 'cancel my order', + 'cancel order', + 'can i cancel my order', + 'how to cancel my order', + 'how do i cancel my order', + 'order cancellation', + 'stop my order', + 'cancel this order', + 'cancel the order', + 'am i able to cancel my order?', + 'will i be able to cancel my order?', + 'could i cancel my order?', + 'i want to cancel my order', + 'please cancel my order', + 'what is the process to cancel my order?', + 'how can i stop my order?', + 'i need to cancel my order', + 'i wish to cancel my order', + 'i\'d like to cancel my order', + 'i have decided to cancel my order', + 'i\'m thinking of canceling my order', + 'i intend to cancel my order', + 'i plan to cancel my order', + 'i aim to cancel my order', + 'i\'m considering canceling my order', + 'i\'m pondering over canceling my order', + 'i\'m mulling over canceling my order', + 'i\'m deliberating whether to cancel my order', + 'i\'m hesitating to cancel my order', + 'i\'m uncertain about canceling my order', + 'i\'m skeptical about canceling my order', + 'i\'m hesitant to cancel my order', + 'i\'m indecisive about canceling my order', + 'i\'m wavering on canceling my order', + 'i\'m contemplating canceling my order', + 'i\'m second-guessing canceling my order', + 'i\'m reconsidering canceling my order', + 'i\'m reevaluating canceling my order', + 'i\'m reassessing canceling my order', + 'i\'m reviewing canceling my order', + 'i\'m evaluating canceling my order', + 'i\'m assessing canceling my order', + 'i\'m analyzing canceling my order', + 'i\'m scrutinizing canceling my order', + 'i\'m examining canceling my order', + 'i\'m inspecting canceling my order', + 'i\'m probing canceling my order', + 'i\'m investigating canceling my order', + 'i\'m researching canceling my order', + 'i\'m studying canceling my order', + 'i\'m looking into canceling my order', + 'i\'m delving into canceling my order', + 'i\'m diving into canceling my order', + 'i\'m getting into canceling my order', + 'i\'m stepping into canceling my order', + 'i\'m venturing into canceling my order', + 'i\'m plunging into canceling my order', + 'i\'m sinking into canceling my order', + 'i\'m immersing myself into canceling my order', + 'i\'m submerging myself into canceling my order', + 'i\'m engaging in canceling my order', + 'i\'m participating in canceling my order', + 'i\'m involved in canceling my order', + 'i\'m implicated in canceling my order', + 'i\'m complicit in canceling my order', + 'i\'m party to canceling my order', + 'i\'m privy to canceling my order', + 'i\'m aware of canceling my order', + 'i\'m cognizant of canceling my order', + 'i\'m mindful of canceling my order', + 'i\'m conscious of canceling my order', + 'i\'m alert to canceling my order', + 'i\'m awake to canceling my order', + 'i\'m vigilant about canceling my order', + 'i\'m watchful of canceling my order', + 'i\'m observant of canceling my order', + 'i\'m attentive to canceling my order', + 'i\'m keen on canceling my order', + 'i\'m eager to cancel my order', ]; - - const cancelOrderRegex = new RegExp(`(?:${cancelOrderKeywords.join('|')})\\s*([\\w\\s]+)`, 'i'); - - if (cancelOrderKeywords.some(keyword => lowerMessage.toLowerCase().includes(keyword))) { + + const cancelOrderRegex = new RegExp( + `(?:${cancelOrderKeywords.join('|')})\\s*([\\w\\s]+)`, + 'i' + ); + + if ( + cancelOrderKeywords.some((keyword) => + lowerMessage.toLowerCase().includes(keyword) + ) + ) { const productNameMatch = lowerMessage.match(cancelOrderRegex); - const productName = productNameMatch? productNameMatch[1].trim() : ''; + const productName = productNameMatch ? productNameMatch[1].trim() : ''; return { intent: 'cancelOrder', keyword: productName, }; } - if ( lowerMessage.includes('return my order') || @@ -325,43 +442,82 @@ if ( lowerMessage.includes('speed up shipping') ) return { intent: 'expediteShipping', keyword: '' }; -const serviceQueryKeywords = [ - 'what services do you offer', 'services', 'available services', 'list of services', 'services provided', - 'what can you do', 'services you offer', 'what kind of services do you provide', 'what services are available', - 'could you tell me about your services', 'do you offer any services', 'what are your service offerings', - 'i\'m interested in learning about your services', 'i would like to know more about your services', - 'can you give me information on your services', 'could you describe your services', 'what are the services you provide', - 'what services can i get from you', 'which services do you specialize in', 'could you list your service offerings', - 'i\'d like to know what services you have', 'i\'m curious about the services you offer', 'what are the different services you offer', - 'i want to understand the services you provide', 'i\'m looking for information on your service options', - 'can you tell me about the various services you offer', 'i\'d appreciate if you could explain your service portfolio', - 'i\'m trying to find out what kind of services you have', 'i\'d like you to elaborate on the services you provide', - 'could you give me an overview of the services you offer', 'i\'m inquiring about the services available from your company', - 'i\'m wondering what kind of services you specialize in', 'i\'d love to learn more about the services you have available', - 'i\'m interested in exploring the different services you provide', 'i\'m hoping you can tell me more about the services you offer', - 'i\'m eager to understand the range of services you have', 'i\'d be grateful if you could share details about your service offerings', - 'i\'m keen to know what kind of services you can assist me with', 'i\'m hoping you can enlighten me on the services you provide', - 'i\'m desirous of getting information on the services you offer', 'i\'m yearning to learn about the services you have available', - 'i\'m craving to understand the services you can render', 'i\'m longing to discover the services you specialize in', - 'i\'m hankering to find out about the services you provide', 'i\'m aching to get details on the services you offer', - 'i\'m pining to learn more about the services you have', 'i\'m itching to know what kind of services you can deliver', - 'i\'ve been dying to inquire about the services you offer', 'i\'ve been burning to get information on your service offerings', - 'i\'ve been aching to understand the services you provide', 'i\'ve been yearning to discover the services you have available', - 'i\'ve been craving to learn about the services you can render', 'i\'ve been hankering to find out about the services you specialize in', - 'i\'ve been pining to get details on the services you offer', 'i\'ve been longing to explore the services you have available', - 'i\'ve been itching to know what kind of services you can assist me with' -]; - -const serviceQueryRegex = new RegExp(`(?:${serviceQueryKeywords.join('|')})`, 'i'); - -if (serviceQueryKeywords.some(keyword => lowerMessage.toLowerCase().includes(keyword))) { - const serviceNameMatch = lowerMessage.match(serviceQueryRegex); - const serviceName = serviceNameMatch ? serviceNameMatch[0].trim() : ''; - return { - intent: 'listServices', - keyword: serviceName, - }; -} + const serviceQueryKeywords = [ + 'what services do you offer', + 'services', + 'available services', + 'list of services', + 'services provided', + 'what can you do', + 'services you offer', + 'what kind of services do you provide', + 'what services are available', + 'could you tell me about your services', + 'do you offer any services', + 'what are your service offerings', + 'i\'m interested in learning about your services', + 'i would like to know more about your services', + 'can you give me information on your services', + 'could you describe your services', + 'what are the services you provide', + 'what services can i get from you', + 'which services do you specialize in', + 'could you list your service offerings', + 'i\'d like to know what services you have', + 'i\'m curious about the services you offer', + 'what are the different services you offer', + 'i want to understand the services you provide', + 'i\'m looking for information on your service options', + 'can you tell me about the various services you offer', + 'i\'d appreciate if you could explain your service portfolio', + 'i\'m trying to find out what kind of services you have', + 'i\'d like you to elaborate on the services you provide', + 'could you give me an overview of the services you offer', + 'i\'m inquiring about the services available from your company', + 'i\'m wondering what kind of services you specialize in', + 'i\'d love to learn more about the services you have available', + 'i\'m interested in exploring the different services you provide', + 'i\'m hoping you can tell me more about the services you offer', + 'i\'m eager to understand the range of services you have', + 'i\'d be grateful if you could share details about your service offerings', + 'i\'m keen to know what kind of services you can assist me with', + 'i\'m hoping you can enlighten me on the services you provide', + 'i\'m desirous of getting information on the services you offer', + 'i\'m yearning to learn about the services you have available', + 'i\'m craving to understand the services you can render', + 'i\'m longing to discover the services you specialize in', + 'i\'m hankering to find out about the services you provide', + 'i\'m aching to get details on the services you offer', + 'i\'m pining to learn more about the services you have', + 'i\'m itching to know what kind of services you can deliver', + 'i\'ve been dying to inquire about the services you offer', + 'i\'ve been burning to get information on your service offerings', + 'i\'ve been aching to understand the services you provide', + 'i\'ve been yearning to discover the services you have available', + 'i\'ve been craving to learn about the services you can render', + 'i\'ve been hankering to find out about the services you specialize in', + 'i\'ve been pining to get details on the services you offer', + 'i\'ve been longing to explore the services you have available', + 'i\'ve been itching to know what kind of services you can assist me with', + ]; + + const serviceQueryRegex = new RegExp( + `(?:${serviceQueryKeywords.join('|')})`, + 'i' + ); + + if ( + serviceQueryKeywords.some((keyword) => + lowerMessage.toLowerCase().includes(keyword) + ) + ) { + const serviceNameMatch = lowerMessage.match(serviceQueryRegex); + const serviceName = serviceNameMatch ? serviceNameMatch[0].trim() : ''; + return { + intent: 'listServices', + keyword: serviceName, + }; + } if ( lowerMessage.includes('provide gift wrapping services') || @@ -441,26 +597,49 @@ if (serviceQueryKeywords.some(keyword => lowerMessage.toLowerCase().includes(key } const changeQuantityKeywords = [ - 'update quantity of item in cart', 'change item quantity in cart', 'modify item quantity in cart', - 'increase item quantity in cart', 'decrease item quantity in cart', 'add more of this item to my cart', - 'remove some of this item from my cart', 'adjust item quantity in my cart', 'alter item quantity in my cart', - 'change item quantity in my cart', 'update item quantity in my cart', 'modify item quantity in my cart', - 'increase item quantity in my cart', 'decrease item quantity in my cart', 'I want to change item quantity in my cart', - 'Can I change item quantity in my cart?', 'How do I change item quantity in my cart?', 'What is the process to change item quantity in my cart?', - 'Will I be able to change item quantity in my cart?', 'Could I change item quantity in my cart?', 'I need to change item quantity in my cart', - 'I wish to change item quantity in my cart', 'I\'d like to change item quantity in my cart', 'I have decided to change item quantity in my cart', - 'I\'m thinking of changing item quantity in my cart', 'I intend to change item quantity in my cart', 'I plan to change item quantity in my cart', - + 'update quantity of item in cart', + 'change item quantity in cart', + 'modify item quantity in cart', + 'increase item quantity in cart', + 'decrease item quantity in cart', + 'add more of this item to my cart', + 'remove some of this item from my cart', + 'adjust item quantity in my cart', + 'alter item quantity in my cart', + 'change item quantity in my cart', + 'update item quantity in my cart', + 'modify item quantity in my cart', + 'increase item quantity in my cart', + 'decrease item quantity in my cart', + 'I want to change item quantity in my cart', + 'Can I change item quantity in my cart?', + 'How do I change item quantity in my cart?', + 'What is the process to change item quantity in my cart?', + 'Will I be able to change item quantity in my cart?', + 'Could I change item quantity in my cart?', + 'I need to change item quantity in my cart', + 'I wish to change item quantity in my cart', + 'I\'d like to change item quantity in my cart', + 'I have decided to change item quantity in my cart', + 'I\'m thinking of changing item quantity in my cart', + 'I intend to change item quantity in my cart', + 'I plan to change item quantity in my cart', ]; - - const changeQuantityRegex = new RegExp(`(?:${changeQuantityKeywords.join('|')})\\s*([\\w\\s]+)`, 'i'); - - if (changeQuantityKeywords.some(keyword => lowerMessage.toLowerCase().includes(keyword))) { + + const changeQuantityRegex = new RegExp( + `(?:${changeQuantityKeywords.join('|')})\\s*([\\w\\s]+)`, + 'i' + ); + + if ( + changeQuantityKeywords.some((keyword) => + lowerMessage.toLowerCase().includes(keyword) + ) + ) { const itemNameMatch = lowerMessage.match(changeQuantityRegex); - const itemName = itemNameMatch? itemNameMatch[1].trim() : ''; + const itemName = itemNameMatch ? itemNameMatch[1].trim() : ''; return { intent: 'updateCartQuantity', keyword: itemName }; } - if ( lowerMessage.includes('is there anything i should remove from my cart') || @@ -522,90 +701,90 @@ if (serviceQueryKeywords.some(keyword => lowerMessage.toLowerCase().includes(key lowerMessage.includes('add to wishlist') || lowerMessage.includes('wishlist creation') || lowerMessage.includes('making a wishlist') || - lowerMessage.includes('set up a wishlist') || - lowerMessage.includes('start a wishlist') || - lowerMessage.includes('build a wishlist') || - lowerMessage.includes('initiate a wishlist') || - lowerMessage.includes('establish a wishlist') || - lowerMessage.includes('what is a wishlist?') || - lowerMessage.includes('how do I create a wishlist?') || - lowerMessage.includes('I want to create a wishlist') || + lowerMessage.includes('set up a wishlist') || + lowerMessage.includes('start a wishlist') || + lowerMessage.includes('build a wishlist') || + lowerMessage.includes('initiate a wishlist') || + lowerMessage.includes('establish a wishlist') || + lowerMessage.includes('what is a wishlist?') || + lowerMessage.includes('how do I create a wishlist?') || + lowerMessage.includes('I want to create a wishlist') || lowerMessage.includes('show me how to make a wishlist') || - lowerMessage.includes('teach me about creating a wishlist') || - lowerMessage.includes('guide me in setting up a wishlist') || - lowerMessage.includes('help me start a wishlist') || + lowerMessage.includes('teach me about creating a wishlist') || + lowerMessage.includes('guide me in setting up a wishlist') || + lowerMessage.includes('help me start a wishlist') || lowerMessage.includes('assist me in building a wishlist') || - lowerMessage.includes('initiate my wishlist') || + lowerMessage.includes('initiate my wishlist') || lowerMessage.includes('establish my wishlist') || lowerMessage.includes('will you show me how to create a wishlist?') || - lowerMessage.includes('did you teach me how to set up a wishlist?') || - lowerMessage.includes('could you guide me in starting a wishlist?') || - lowerMessage.includes('would you assist me in building a wishlist?') || + lowerMessage.includes('did you teach me how to set up a wishlist?') || + lowerMessage.includes('could you guide me in starting a wishlist?') || + lowerMessage.includes('would you assist me in building a wishlist?') || lowerMessage.includes('should I create a wishlist?') || lowerMessage.includes('can I add something to my wishlist?') || lowerMessage.includes('let me know how to add to my wishlist') || - lowerMessage.includes('tell me how to add items to my wishlist') || - lowerMessage.includes('show me how to add items to my wishlist') || - lowerMessage.includes('explain how to add items to my wishlist') || - lowerMessage.includes('walk me through adding items to my wishlist') || - lowerMessage.includes('lead me in adding items to my wishlist') || - lowerMessage.includes('me to adding items to my wishlist') || - lowerMessage.includes('how to add items to my wishlist') || - lowerMessage.includes('me to creating a wishlist') || - lowerMessage.includes('how to create a wishlist') || + lowerMessage.includes('tell me how to add items to my wishlist') || + lowerMessage.includes('show me how to add items to my wishlist') || + lowerMessage.includes('explain how to add items to my wishlist') || + lowerMessage.includes('walk me through adding items to my wishlist') || + lowerMessage.includes('lead me in adding items to my wishlist') || + lowerMessage.includes('me to adding items to my wishlist') || + lowerMessage.includes('how to add items to my wishlist') || + lowerMessage.includes('me to creating a wishlist') || + lowerMessage.includes('how to create a wishlist') || lowerMessage.includes('me to setting up a wishlist') || - lowerMessage.includes('how to set up a wishlist') || + lowerMessage.includes('how to set up a wishlist') || lowerMessage.includes('me to starting a wishlist') || - lowerMessage.includes('how to start a wishlist') || + lowerMessage.includes('how to start a wishlist') || lowerMessage.includes('me to building a wishlist') || - lowerMessage.includes('how to build a wishlist') || + lowerMessage.includes('how to build a wishlist') || lowerMessage.includes('me to initiating a wishlist') || - lowerMessage.includes('how to initiate a wishlist') || - lowerMessage.includes('me to establishing a wishlist') || - lowerMessage.includes('how to establish a wishlist') || - lowerMessage.includes('me to making a wishlist') || - lowerMessage.includes('how to make a wishlist') || + lowerMessage.includes('how to initiate a wishlist') || + lowerMessage.includes('me to establishing a wishlist') || + lowerMessage.includes('how to establish a wishlist') || + lowerMessage.includes('me to making a wishlist') || + lowerMessage.includes('how to make a wishlist') || lowerMessage.includes('me to wishlist creation') || - lowerMessage.includes('how to create a wishlist') || - lowerMessage.includes('me to wishlist management') || - lowerMessage.includes('how to manage a wishlist') || - lowerMessage.includes('me to wishlist addition') || - lowerMessage.includes('how to add to a wishlist') || - lowerMessage.includes('me to wishlist editing') || + lowerMessage.includes('how to create a wishlist') || + lowerMessage.includes('me to wishlist management') || + lowerMessage.includes('how to manage a wishlist') || + lowerMessage.includes('me to wishlist addition') || + lowerMessage.includes('how to add to a wishlist') || + lowerMessage.includes('me to wishlist editing') || lowerMessage.includes('how to edit a wishlist') || - lowerMessage.includes('me to wishlist removal') || - lowerMessage.includes('how to remove from a wishlist') || - lowerMessage.includes('me to wishlist sharing') || - lowerMessage.includes('how to share a wishlist') || + lowerMessage.includes('me to wishlist removal') || + lowerMessage.includes('how to remove from a wishlist') || + lowerMessage.includes('me to wishlist sharing') || + lowerMessage.includes('how to share a wishlist') || lowerMessage.includes('me to wishlist collaboration') || - lowerMessage.includes('how to collaborate on a wishlist') || - lowerMessage.includes('me to wishlist customization') || - lowerMessage.includes('how to customize a wishlist') || + lowerMessage.includes('how to collaborate on a wishlist') || + lowerMessage.includes('me to wishlist customization') || + lowerMessage.includes('how to customize a wishlist') || lowerMessage.includes('me to wishlist organization') || - lowerMessage.includes('how to organize a wishlist') || - lowerMessage.includes('me to wishlist maintenance') || - lowerMessage.includes('how to maintain a wishlist') || + lowerMessage.includes('how to organize a wishlist') || + lowerMessage.includes('me to wishlist maintenance') || + lowerMessage.includes('how to maintain a wishlist') || lowerMessage.includes('me to wishlist enhancement') || - lowerMessage.includes('how to enhance a wishlist') || + lowerMessage.includes('how to enhance a wishlist') || lowerMessage.includes('me to wishlist improvement') || - lowerMessage.includes('how to improve a wishlist') || + lowerMessage.includes('how to improve a wishlist') || lowerMessage.includes('me to wishlist optimization') || - lowerMessage.includes('how to optimize a wishlist') || + lowerMessage.includes('how to optimize a wishlist') || lowerMessage.includes('me to wishlist expansion') || - lowerMessage.includes('how to expand a wishlist') || - lowerMessage.includes('me to wishlist extension') || - lowerMessage.includes('how to extend a wishlist') || - lowerMessage.includes('me to wishlist adjustment') || - lowerMessage.includes('how to adjust a wishlist') || - lowerMessage.includes('me to wishlist modification') || - lowerMessage.includes('how to modify a wishlist') || + lowerMessage.includes('how to expand a wishlist') || + lowerMessage.includes('me to wishlist extension') || + lowerMessage.includes('how to extend a wishlist') || + lowerMessage.includes('me to wishlist adjustment') || + lowerMessage.includes('how to adjust a wishlist') || + lowerMessage.includes('me to wishlist modification') || + lowerMessage.includes('how to modify a wishlist') || lowerMessage.includes('me to wishlist alteration') || - lowerMessage.includes('how to alter a wishlist') || - lowerMessage.includes('me to wishlist amendment') || - lowerMessage.includes('how to amend a wishlist') || + lowerMessage.includes('how to alter a wishlist') || + lowerMessage.includes('me to wishlist amendment') || + lowerMessage.includes('how to amend a wishlist') || lowerMessage.includes('me to wishlist revision') || lowerMessage.includes('how to revise a wishlist') || - lowerMessage.includes('me to wishlist update') + lowerMessage.includes('me to wishlist update') ) return { intent: 'createWishlist', keyword: '' }; @@ -911,25 +1090,25 @@ export const generateResponse = async ( const products = await getProducts(); return `We sell the following products: ${products.map((p) => p.name).join(', ')}.`; - case 'checkProductStock': - const product = await getProductByName(keyword); - return product - ? `Yes, we have ${product.name} in stock.` - : `Sorry, we do not have ${keyword} in stock.`; + case 'checkProductStock': + const product = await getProductByName(keyword); + return product + ? `Yes, we have ${product.name} in stock.` + : `Sorry, we do not have ${keyword} in stock.`; case 'productDetails': const productDetails = await getProductDetails(keyword); return productDetails ? `Sure, here is more information about ${keyword}: ${productDetails.longDesc}. This product, features an average rating of ${productDetails.averageRating}. It is currently ${productDetails.isAvailable ? 'available' : 'unavailable'} and is priced at $${productDetails.salesPrice} (regular price: $${productDetails.regularPrice}).` : `Sorry, we couldn't find detailed information about ${keyword}. It might not exist or there might be a typo in the product name.`; - + case 'productPrice': const productprice = await getProductByName(keyword); return productprice ? `The price of ${productprice.name} is $${productprice.salesPrice}.` : `Sorry, we do not have pricing information for ${keyword}.`; - case 'listProductCategories': - const categories = await getProductCategories(); - return `We offer the following product categories: ${categories.map((c) => c.name).join(', ')}.`; + case 'listProductCategories': + const categories = await getProductCategories(); + return `We offer the following product categories: ${categories.map((c) => c.name).join(', ')}.`; case 'productReview': const reviews = await getProductReviews(keyword); diff --git a/src/utilis/sendEmail.ts b/src/utilis/sendEmail.ts index 0eafd075..15c67f72 100644 --- a/src/utilis/sendEmail.ts +++ b/src/utilis/sendEmail.ts @@ -1,6 +1,10 @@ import nodemailer from 'nodemailer'; -const sendEmail = async (data: { email: string; subject: string; html: string }) => { +const sendEmail = async (data: { + email: string; + subject: string; + html: string; +}) => { const transporter = nodemailer.createTransport({ service: 'Gmail', auth: {