From da959cdda6ae44f746c45194e0774ba7e8ef1da2 Mon Sep 17 00:00:00 2001 From: Joslyn Manzi Karenzi Date: Fri, 31 May 2024 11:59:36 +0200 Subject: [PATCH] * fix(create-review): fix failing create review function -add validation before processing data [Fixes #116] * fix(create-review): fix failing create review function -add validation before processing data [Fixes #116] --- package.json | 8 +- src/__test__/cartController.test.ts | 4 - src/__test__/chatbot.test.ts | 42 ++ src/__test__/chatbotService.test.ts | 184 +++++++++ src/__test__/npl.test.ts | 136 +++++++ src/__test__/review.test.ts | 9 + src/app.ts | 3 +- src/controller/cartController.ts | 9 +- src/controller/chatbotController.ts | 25 ++ src/controller/productController.ts | 29 +- src/controller/reviewController.ts | 13 +- src/controller/serviceController.ts | 18 + src/database/models/chatbotModel.ts | 20 + src/database/models/serviceEntity.ts | 30 ++ src/docs/cartDocs.ts | 4 - src/docs/chatbotDocs.ts | 76 ++++ src/docs/reviewDocs.ts | 42 +- src/middlewares/createReviewSchema.ts | 8 + src/middlewares/errorHandler.ts | 1 + src/routes/chatbotRoutes.ts | 11 + src/routes/index.ts | 2 +- src/routes/serviceRoutes.ts | 12 + src/service/chatbotService.ts | 94 +++++ src/service/serviceService.ts | 13 + src/utilis/nlp.ts | 565 ++++++++++++++++++++++++++ src/utils/couponCalculator.ts | 19 - 26 files changed, 1295 insertions(+), 82 deletions(-) create mode 100644 src/__test__/chatbot.test.ts create mode 100644 src/__test__/chatbotService.test.ts create mode 100644 src/__test__/npl.test.ts create mode 100644 src/controller/chatbotController.ts create mode 100644 src/controller/serviceController.ts create mode 100644 src/database/models/chatbotModel.ts create mode 100644 src/database/models/serviceEntity.ts create mode 100644 src/docs/chatbotDocs.ts create mode 100644 src/middlewares/createReviewSchema.ts create mode 100644 src/routes/chatbotRoutes.ts create mode 100644 src/routes/serviceRoutes.ts create mode 100644 src/service/chatbotService.ts create mode 100644 src/service/serviceService.ts create mode 100644 src/utilis/nlp.ts delete mode 100644 src/utils/couponCalculator.ts diff --git a/package.json b/package.json index 3f3ff677..3e78368c 100644 --- a/package.json +++ b/package.json @@ -67,12 +67,16 @@ ], "coveragePathIgnorePatterns": [ "/node_modules/", - "/src/emails/" + "/src/emails/", + "/src/utilis/" + ], "testPathIgnorePatterns": [ "/node_modules/", "/src/emails/", - "/src/middlewares/" + "/src/middlewares/", + "/src/utilis/" + ] }, "devDependencies": { diff --git a/src/__test__/cartController.test.ts b/src/__test__/cartController.test.ts index c78bce48..946d85db 100644 --- a/src/__test__/cartController.test.ts +++ b/src/__test__/cartController.test.ts @@ -218,7 +218,6 @@ describe('Checkout Tests', () => { .set('Authorization', `Bearer ${buyerToken}`) .send({ deliveryInfo: '', - paymentInfo: '', couponCode: 'DISCOUNT10', }); @@ -244,7 +243,6 @@ describe('Checkout Tests', () => { .set('Authorization', `Bearer ${buyerToken}`) .send({ deliveryInfo: '123 Delivery St.', - paymentInfo: 'VISA 1234', couponCode: 'DISCOUNT10', }); @@ -283,7 +281,6 @@ describe('Checkout Tests', () => { .set('Authorization', invalidUserToken) .send({ deliveryInfo: '123 Delivery St.', - paymentInfo: 'VISA 1234', couponCode: 'DISCOUNT10', }); @@ -309,7 +306,6 @@ describe('Checkout Tests', () => { .set('Authorization', `Bearer ${buyerToken}`) .send({ deliveryInfo: '123 Delivery St.', - paymentInfo: 'VISA 1234', couponCode: 'DISCOUNT10', }); diff --git a/src/__test__/chatbot.test.ts b/src/__test__/chatbot.test.ts new file mode 100644 index 00000000..ade39dc3 --- /dev/null +++ b/src/__test__/chatbot.test.ts @@ -0,0 +1,42 @@ +import request from 'supertest'; +import app from '../app'; +import { afterAllHook, beforeAllHook, getBuyerToken } from './testSetup'; + +beforeAll(beforeAllHook); +afterAll(afterAllHook); +let buyerToken: string; + +describe('Chatbot Interactions', () => { + beforeAll(async () => { + buyerToken = await getBuyerToken(); + }); + + describe('Ask Question Endpoint', () => { + it('should respond to a valid question with the correct answer for logged-in users', async () => { + const chatData = { + message: 'What do you sell?', + }; + + const response = await request(app) + .post('/api/v1/chat') + .set('Authorization', `Bearer ${buyerToken}`) + .send(chatData); + + expect(response.statusCode).toEqual(200); + expect(response.body.message).toContain( + 'We sell the following products:' + ); + }); + }); + + describe('Fetch All Chat History', () => { + it('should return the full chat history for the authenticated user', async () => { + const response = await request(app) + .get('/api/v1/chat/history') + .set('Authorization', `Bearer ${buyerToken}`); + + expect(response.statusCode).toEqual(200); + expect(Array.isArray(response.body.history)).toBeTruthy(); + }); + }); +}); diff --git a/src/__test__/chatbotService.test.ts b/src/__test__/chatbotService.test.ts new file mode 100644 index 00000000..40ad9d65 --- /dev/null +++ b/src/__test__/chatbotService.test.ts @@ -0,0 +1,184 @@ +import { + getProducts, + getProductByName, + getOrders, + getOrderByUserId, + getOrderStatusByTrackingNumber, + getServices, + getServiceByName, + getChatHistory, + } from '../service/chatbotService'; + import dbConnection from '../database'; + import { analyzeMessage, generateResponse } from '../utilis/nlp'; + import User from '../database/models/userModel'; + import Chat from '../database/models/chatbotModel'; + import { processMessage } from '../service/chatbotService'; + jest.mock('../database'); + jest.mock('../utilis/nlp'); + + describe('Service Service', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('processMessage', () => { + const userId = 1; + const user = { id: userId, firstName: 'John', lastName: 'Doe' } as User; + const mockDate = (hour: number) => { + const RealDate = Date; + const mockDate = new RealDate(); + jest.spyOn(global, 'Date').mockImplementation(() => { + mockDate.setHours(hour); + return mockDate; + }); + }; + + const setupMocks = () => { + const userRepoMock = { + findOne: jest.fn().mockResolvedValue(user), + }; + const chatRepoMock = { + save: jest.fn().mockImplementation(async (chat: Chat) => chat), + }; + (dbConnection.getRepository as jest.Mock).mockImplementation((entity) => { + if (entity === User) return userRepoMock; + if (entity === Chat) return chatRepoMock; + }); + (analyzeMessage as jest.Mock).mockReturnValue('analyzed message'); + (generateResponse as jest.Mock).mockResolvedValue('response'); + }; + + it('should return a complete response with a morning greeting', async () => { + mockDate(9); + setupMocks(); + + const expectedGreeting = 'Good morning, John Doe! response'; + const result = await processMessage(userId, 'test message'); + + expect(result.trim()).toEqual(expectedGreeting.trim()); + }); + + it('should return a complete response with an afternoon greeting', async () => { + mockDate(15); + setupMocks(); + + const expectedGreeting = 'Good afternoon, John Doe! response'; + const result = await processMessage(userId, 'test message'); + + expect(result.trim()).toEqual(expectedGreeting.trim()); + }); + + it('should return a complete response with an evening greeting', async () => { + mockDate(20); + setupMocks(); + + const expectedGreeting = 'Good evening, John Doe! response'; + const result = await processMessage(userId, 'test message'); + + expect(result.trim()).toEqual(expectedGreeting.trim()); + }); + + it('should throw an error if user is not found', async () => { + const userRepoMock = { + findOne: jest.fn().mockResolvedValue(null), + }; + (dbConnection.getRepository as jest.Mock).mockReturnValue(userRepoMock); + + await expect(processMessage(userId, 'test message')).rejects.toThrow('User not found'); + + expect(userRepoMock.findOne).toHaveBeenCalledWith({ where: { id: userId } }); + }); + }); + + it('should return a list of products', async () => { + const productRepo = { + find: jest.fn().mockResolvedValue([{ name: 'Product1' }]), + }; + dbConnection.getRepository = jest.fn().mockReturnValue(productRepo); + + const products = await getProducts(); + expect(products).toEqual([{ name: 'Product1' }]); + }); + }); + + describe('getProductByName', () => { + it('should return a product by name', async () => { + const productRepo = { + findOne: jest.fn().mockResolvedValue({ name: 'Product1' }), + }; + dbConnection.getRepository = jest.fn().mockReturnValue(productRepo); + + const product = await getProductByName('Product1'); + expect(product).toEqual({ name: 'Product1' }); + }); + }); + + describe('getOrders', () => { + it('should return a list of orders for a user', async () => { + const orderRepo = { find: jest.fn().mockResolvedValue([{ id: 1 }]) }; + dbConnection.getRepository = jest.fn().mockReturnValue(orderRepo); + + const orders = await getOrders(1); + expect(orders).toEqual([{ id: 1 }]); + }); + }); + + describe('getOrderByUserId', () => { + it('should return an order for a user by userId', async () => { + const orderRepo = { findOne: jest.fn().mockResolvedValue({ id: 1 }) }; + dbConnection.getRepository = jest.fn().mockReturnValue(orderRepo); + + const order = await getOrderByUserId(1); + expect(order).toEqual({ id: 1 }); + }); + }); + + describe('getOrderStatusByTrackingNumber', () => { + it('should return the status of an order by tracking number', async () => { + const orderRepo = { findOne: jest.fn().mockResolvedValue({ status: 'Shipped' }) }; + dbConnection.getRepository = jest.fn().mockReturnValue(orderRepo); + + const status = await getOrderStatusByTrackingNumber('12345'); + expect(status).toBe('Shipped'); + }); + + it('should return "Tracking number not found" if order is not found', async () => { + const orderRepo = { findOne: jest.fn().mockResolvedValue(null) }; + dbConnection.getRepository = jest.fn().mockReturnValue(orderRepo); + + const status = await getOrderStatusByTrackingNumber('12345'); + expect(status).toBe('Tracking number not found'); + }); + }); + + describe('getServices', () => { + it('should return a list of services', async () => { + const serviceRepo = { find: jest.fn().mockResolvedValue([{ name: 'Service1' }]) }; + dbConnection.getRepository = jest.fn().mockReturnValue(serviceRepo); + + const services = await getServices(); + expect(services).toEqual([{ name: 'Service1' }]); + }); + }); + + describe('getServiceByName', () => { + it('should return a service by name', async () => { + const serviceRepo = { findOne: jest.fn().mockResolvedValue({ name: 'Service1' }) }; + dbConnection.getRepository = jest.fn().mockReturnValue(serviceRepo); + + const service = await getServiceByName('Service1'); + expect(service).toEqual({ name: 'Service1' }); + }); + }); + + describe('getChatHistory', () => { + it('should return chat history for a user', async () => { + const userId = 1; + const chatRepo = { find: jest.fn().mockResolvedValue([{ message: 'Hello', createdAt: '2024-05-31' }]) }; + dbConnection.getRepository = jest.fn().mockReturnValue(chatRepo); + + const chatHistory = await getChatHistory(userId); + expect(chatHistory).toEqual([{ message: 'Hello', createdAt: '2024-05-31' }]); + }); + }); + \ No newline at end of file diff --git a/src/__test__/npl.test.ts b/src/__test__/npl.test.ts new file mode 100644 index 00000000..ac731af7 --- /dev/null +++ b/src/__test__/npl.test.ts @@ -0,0 +1,136 @@ +import { + analyzeMessage, + generateResponse, + identifyIntent, + extractKeyword, +} from '../utilis/nlp'; + +import { + getProductByName, + getProducts, + getOrderByUserId, +} from '../service/chatbotService'; + +// Mock the services used in generateResponse +jest.mock('../service/chatbotService', () => ({ + getProductByName: jest.fn(), + getProducts: jest.fn(), + getOrderByUserId: jest.fn(), + getOrderStatusByTrackingNumber: jest.fn(), + getServices: jest.fn(), + getServiceByName: jest.fn(), +})); + +describe('npl.ts tests', () => { + describe('extractKeyword', () => { + it('should extract keyword correctly', () => { + expect( + extractKeyword('tell me more about product', 'tell me more about') + ).toBe('product'); + }); + }); + + describe('analyzeMessage', () => { + it('should convert message to lowercase', () => { + expect(analyzeMessage('Hello World')).toBe('hello world'); + }); + }); + + describe('identifyIntent', () => { + it('should identify greeting intent', () => { + expect(identifyIntent('hello')).toEqual({ + intent: 'greeting', + keyword: '', + }); + }); + + it('should identify listProducts intent', () => { + expect(identifyIntent('what products do you sell')).toEqual({ + intent: 'listProducts', + keyword: '', + }); + }); + + it('should return unknown intent for unrecognized message', () => { + expect(identifyIntent('random message')).toEqual({ + intent: 'unknown', + keyword: '', + }); + }); + }); + + describe('generateResponse', () => { + const userId = 1; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return greeting message', async () => { + const response = await generateResponse('hello', userId); + expect(response).toBe('How can I assist you today?'); + }); + + it('should list products', async () => { + const products = [{ name: 'Product1' }, { name: 'Product2' }]; + (getProducts as jest.Mock).mockResolvedValue(products); + + const response = await generateResponse( + 'what products do you sell', + userId + ); + expect(response).toBe( + 'We sell the following products: Product1, Product2.' + ); + }); + + it('should check stock for a product', async () => { + const product = { name: 'iPhone' }; + (getProductByName as jest.Mock).mockResolvedValue(product); + + const response = await generateResponse( + 'do you have iPhone in stock', + userId + ); + expect(response).toBe('iPhone is in stock.'); + }); + + it('should return product information', async () => { + const product = { name: 'iPhone', averageRating: '4.5 stars' }; + (getProductByName as jest.Mock).mockResolvedValue(product); + + const response = await generateResponse( + 'tell me more about iPhone', + userId + ); + expect(response).toBe('Here is more information about iPhone: 4.5 stars'); + }); + + it('should handle unknown product information', async () => { + (getProductByName as jest.Mock).mockResolvedValue(null); + + const response = await generateResponse( + 'tell me more about iPhone', + userId + ); + expect(response).toBe('Sorry, we do not have information on iphone.'); + }); + + it('should track order', async () => { + const order = { status: 'Shipped' }; + (getOrderByUserId as jest.Mock).mockResolvedValue(order); + + const response = await generateResponse('track my order', userId); + expect(response).toBe('Your order status is: Shipped.'); + }); + + it('should handle unknown order for tracking', async () => { + (getOrderByUserId as jest.Mock).mockResolvedValue(null); + + const response = await generateResponse('track my order', userId); + expect(response).toBe( + 'Sorry, we could not find any orders for your account.' + ); + }); + }); +}); diff --git a/src/__test__/review.test.ts b/src/__test__/review.test.ts index d038ecc7..f9bc96f1 100644 --- a/src/__test__/review.test.ts +++ b/src/__test__/review.test.ts @@ -91,4 +91,13 @@ describe('Review controller test', () => { 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 diff --git a/src/app.ts b/src/app.ts index bb088615..ea5816b5 100644 --- a/src/app.ts +++ b/src/app.ts @@ -12,6 +12,7 @@ import cookieSession from 'cookie-session'; import passport from 'passport'; import userRouter from './routes/userRoutes'; import searchRoutes from './routes/searchRoutes' +import chatRoutes from './routes/chatbotRoutes' // Require Passport midleware import './middlewares/passport-setup'; @@ -80,7 +81,7 @@ app.get('/', (req: Request, res: Response) => { app.use('/api/v1', router); app.use('/api/v1', userRouter); app.use('/api/v1', searchRoutes); - +app.use('/api/v1', chatRoutes); // Endpoints for serving social login app.use('/auth', authRoutes); diff --git a/src/controller/cartController.ts b/src/controller/cartController.ts index d9841970..274b2b3a 100644 --- a/src/controller/cartController.ts +++ b/src/controller/cartController.ts @@ -16,7 +16,6 @@ const orderRepository = dbConnection.getRepository(Order); interface CheckoutRequestBody { deliveryInfo: string; - paymentInfo: string; couponCode?: string; } @@ -24,9 +23,6 @@ const checkoutRules = [ check('deliveryInfo') .isLength({ min: 1 }) .withMessage('Delivery info is required'), - check('paymentInfo') - .isLength({ min: 1 }) - .withMessage('Payment info is required'), ]; export const addToCart = errorHandler(async (req: Request, res: Response) => { @@ -187,7 +183,7 @@ export const checkout = [ if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } - const { deliveryInfo, paymentInfo, couponCode } = req.body as CheckoutRequestBody; + const { deliveryInfo, couponCode } = req.body as CheckoutRequestBody; const userId = req.user?.id; // Fetch the user who is checking out @@ -240,7 +236,6 @@ export const checkout = [ order.totalAmount = totalAmount; order.status = 'Pending'; order.deliveryInfo = deliveryInfo; - order.paymentInfo = paymentInfo; order.trackingNumber = trackingNumber; order.orderDetails = orderDetails; @@ -256,8 +251,6 @@ export const checkout = [ }), ]; - - export const deleteAllOrders = errorHandler( async (req: Request, res: Response) => { const deletedOrders = await orderRepository.delete({}); diff --git a/src/controller/chatbotController.ts b/src/controller/chatbotController.ts new file mode 100644 index 00000000..47c7823a --- /dev/null +++ b/src/controller/chatbotController.ts @@ -0,0 +1,25 @@ +import { Request, Response } from 'express'; +import * as chatbotService from '../service/chatbotService'; +import errorHandler from '../middlewares/errorHandler'; + +export const getChatResponse = errorHandler( + async ( + req: Request, + res: Response + ): Promise> | undefined> => { + const { message } = req.body; + const userId = req.user?.id; + const response = await chatbotService.processMessage(userId, message); + return res.status(200).json({ message: response }); + } +); +export const getChatHistory = errorHandler( + async ( + req: Request, + res: Response + ): Promise> | undefined> => { + const userId = req.user?.id; + const history = await chatbotService.getChatHistory(userId); + return res.status(200).json({ history }); + } +); diff --git a/src/controller/productController.ts b/src/controller/productController.ts index 6049c1e3..ec29705d 100644 --- a/src/controller/productController.ts +++ b/src/controller/productController.ts @@ -7,9 +7,6 @@ import { check, validationResult } from 'express-validator'; import errorHandler from '../middlewares/errorHandler'; import productQuantityWatch from '../middlewares/productAvailabilityWatch'; - - - const userRepository = dbConnection.getRepository(UserModel); const productRepository = dbConnection.getRepository(Product); @@ -70,7 +67,7 @@ export const createProduct = [ if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } - + const vendorId = req.user!.id; const { @@ -373,17 +370,21 @@ export const AvailableProducts = errorHandler( message: 'Items retrieved successfully.', availableProducts, totalPages: Math.ceil(totalCount / limit), - currentPage: page - }); -}) + currentPage: page, + }); + } +); // From Bernard #38 -export const updateProductAvailability = async (req: Request, res: Response) => { +export const updateProductAvailability = async ( + req: Request, + res: Response +) => { const { productId } = req.params; const { availability } = req.body; const user = await userRepository.findOne({ - where: { id: (req.user as User).id}, + where: { id: (req.user as User).id }, }); if (!user) { @@ -392,7 +393,7 @@ export const updateProductAvailability = async (req: Request, res: Response) => const product = await productRepository.findOne({ where: { id: Number(productId) }, - relations: ['vendor'] + relations: ['vendor'], }); if (!product) { @@ -409,18 +410,16 @@ export const updateProductAvailability = async (req: Request, res: Response) => res.json({ msg: 'Product availability updated' }); }; - export const checkProductAvailability = async (req: Request, res: Response) => { const { productId } = req.params; const product = await productRepository.findOne({ where: { id: Number(productId) }, - relations: ['vendor'] + relations: ['vendor'], }); - const user = await userRepository.findOne({ - where: { id: (req.user as User).id}, + where: { id: (req.user as User).id }, }); if (!user) { @@ -430,7 +429,7 @@ export const checkProductAvailability = async (req: Request, res: Response) => { if (!product) { return res.status(404).json({ msg: 'Product not found' }); } - if (product.vendor.id!== user.id) { + if (product.vendor.id !== user.id) { return res.status(403).json({ msg: 'Product not owned by vendor' }); } diff --git a/src/controller/reviewController.ts b/src/controller/reviewController.ts index 069b6c7a..8e07a232 100644 --- a/src/controller/reviewController.ts +++ b/src/controller/reviewController.ts @@ -4,13 +4,24 @@ 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'; const productRepository = dbConnection.getRepository(Product); const reviewRepository = dbConnection.getRepository(Review); const userRepository = dbConnection.getRepository(UserModel); export const createReview = errorHandler(async (req: Request, res: Response) => { - const { content, rating, productId } = req.body; + const formData = req.body; + + 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 userId = req.user!.id; const product = await productRepository.findOne({ diff --git a/src/controller/serviceController.ts b/src/controller/serviceController.ts new file mode 100644 index 00000000..a7492b3a --- /dev/null +++ b/src/controller/serviceController.ts @@ -0,0 +1,18 @@ +import { Request, Response } from 'express'; +import * as serviceService from '../service/serviceService'; +import errorHandler from '../middlewares/errorHandler'; + +export const createService = errorHandler( + async (req: Request, res: Response): Promise> | undefined> => { + const { name, description } = req.body; + const newService = await serviceService.createService({ name, description }); + return res.status(201).json({ message: 'Service created successfully', service: newService }); + } +); + +export const getAllServices = errorHandler( + async (_req: Request, res: Response): Promise> | undefined> => { + const services = await serviceService.getAllServices(); + return res.status(200).json({ services }); + } +); diff --git a/src/database/models/chatbotModel.ts b/src/database/models/chatbotModel.ts new file mode 100644 index 00000000..57d0a966 --- /dev/null +++ b/src/database/models/chatbotModel.ts @@ -0,0 +1,20 @@ +import { Entity,PrimaryGeneratedColumn,Column,ManyToOne,CreateDateColumn } from 'typeorm'; +import User from './userModel'; + +@Entity() +export default class chat{ + @PrimaryGeneratedColumn() + id:number; + + @ManyToOne(()=>User) + user: User; + + @Column() + message:string; + + @Column() + response:string; + + @CreateDateColumn() + createdAt:Date; +} \ No newline at end of file diff --git a/src/database/models/serviceEntity.ts b/src/database/models/serviceEntity.ts new file mode 100644 index 00000000..a9423911 --- /dev/null +++ b/src/database/models/serviceEntity.ts @@ -0,0 +1,30 @@ +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); + } + } + \ No newline at end of file diff --git a/src/docs/cartDocs.ts b/src/docs/cartDocs.ts index 131f0bfa..690918e9 100644 --- a/src/docs/cartDocs.ts +++ b/src/docs/cartDocs.ts @@ -140,10 +140,6 @@ * type: object * description: Delivery information for the order * example: { "address": "123 Main St", "city": "Anytown", "zip": "12345" } - * paymentInfo: - * type: object - * description: Payment information for the order - * example: { "method": "credit card", "details": "**** **** **** 1234" } * couponCode: * type: string * description: Optional coupon code for discount diff --git a/src/docs/chatbotDocs.ts b/src/docs/chatbotDocs.ts new file mode 100644 index 00000000..0d81d442 --- /dev/null +++ b/src/docs/chatbotDocs.ts @@ -0,0 +1,76 @@ +/** + * @swagger + * tags: + * name: Chat bot + * description: Operations related to Chat bot + * /api/v1/chat: + * post: + * summary: Send a message to the chat bot + * tags: [Chat bot] + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: The message to send to the chat bot + * responses: + * '200': + * description: Successfully received response from the chat bot + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: The response from the chat bot + * /api/v1/chat/history: + * get: + * summary: Get chat history + * tags: [Chat bot] + * security: + * - bearerAuth: [] + * responses: + * '200': + * description: Successfully retrieved chat history + * content: + * application/json: + * schema: + * type: object + * properties: + * history: + * type: array + * items: + * $ref: '#/components/schemas/Chat' + * components: + * schemas: + * Chat: + * type: object + * properties: + * id: + * type: integer + * description: The ID of the chat + * userId: + * type: integer + * description: The ID of the user + * message: + * type: string + * description: The message sent by the user + * response: + * type: string + * description: The response from the chat bot + * createdAt: + * type: string + * format: date-time + * description: The timestamp when the chat was created + * updatedAt: + * type: string + * format: date-time + * description: The timestamp when the chat was last updated + */ diff --git a/src/docs/reviewDocs.ts b/src/docs/reviewDocs.ts index 05ae5c45..f5b66566 100644 --- a/src/docs/reviewDocs.ts +++ b/src/docs/reviewDocs.ts @@ -6,31 +6,19 @@ * tags: [Reviews] * security: * - bearerAuth: [] - * consumes: - * - application/json - * produces: - * - application/json - * parameters: - * - in: body - * name: review - * description: The review object - * required: true - * schema: - * type: object - * properties: - * content: - * type: string - * description: The content of the review - * rating: - * type: integer - * description: The rating of the review (1 to 5) - * productId: - * type: integer - * description: The ID of the product being reviewed - * example: - * content: "This product is amazing!" - * rating: 5 - * productId: 50 + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * content: + * type: string + * rating: + * type: number + * productId: + * type: number * responses: * '201': * description: Review created successfully @@ -42,7 +30,7 @@ * description: A success message * review: * '400': - * description: Bad request, check the request body + * description: Bad request, failed validation * '404': * description: Product not found * '409': @@ -65,4 +53,4 @@ * properties: * reviews: * type: array - */ + */ \ No newline at end of file diff --git a/src/middlewares/createReviewSchema.ts b/src/middlewares/createReviewSchema.ts new file mode 100644 index 00000000..3eba04df --- /dev/null +++ b/src/middlewares/createReviewSchema.ts @@ -0,0 +1,8 @@ +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 diff --git a/src/middlewares/errorHandler.ts b/src/middlewares/errorHandler.ts index 3d741642..a4cd06a2 100644 --- a/src/middlewares/errorHandler.ts +++ b/src/middlewares/errorHandler.ts @@ -10,6 +10,7 @@ function errorHandler(func: MiddlewareFunction): MiddlewareFunction { try { return await func(req, res); } catch (error) { + // console.log({'Error':error}) const message = (error as { detail?: string }).detail || 'Internal Server Error'; return res.status(500).send(message); diff --git a/src/routes/chatbotRoutes.ts b/src/routes/chatbotRoutes.ts new file mode 100644 index 00000000..6f9d3a8e --- /dev/null +++ b/src/routes/chatbotRoutes.ts @@ -0,0 +1,11 @@ +import { Router } from 'express'; +import { getChatResponse, getChatHistory } from '../controller/chatbotController'; +import { IsLoggedIn } from '../middlewares/isLoggedIn'; +import { checkRole } from '../middlewares/authorize'; + +const router = Router(); + +router.post('/chat', IsLoggedIn,checkRole(['Buyer']), getChatResponse); +router.get('/chat/history', IsLoggedIn,checkRole(['Buyer']), getChatHistory); + +export default router; diff --git a/src/routes/index.ts b/src/routes/index.ts index b2fb9092..0cb3adb3 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -21,4 +21,4 @@ router.use('/coupons', couponRouter); router.use('/checkout', chekoutRoutes); router.use('/review',reviewRoute) -export default router; +export default router; \ No newline at end of file diff --git a/src/routes/serviceRoutes.ts b/src/routes/serviceRoutes.ts new file mode 100644 index 00000000..00a311da --- /dev/null +++ b/src/routes/serviceRoutes.ts @@ -0,0 +1,12 @@ +import { Router } from 'express'; +import { createService, getAllServices } from '../controller/serviceController'; +import { IsLoggedIn } from '../middlewares/isLoggedIn'; +import { checkRole } from '../middlewares/authorize'; + +const router = Router(); + +router.post('/service', IsLoggedIn, checkRole(['Admin']), createService); + +router.get('/services', IsLoggedIn, checkRole(['Buyer', 'Admin']), getAllServices); + +export default router; diff --git a/src/service/chatbotService.ts b/src/service/chatbotService.ts new file mode 100644 index 00000000..0b17f467 --- /dev/null +++ b/src/service/chatbotService.ts @@ -0,0 +1,94 @@ +import { analyzeMessage, generateResponse } from '../utilis/nlp'; +import Chat from '../database/models/chatbotModel'; +import User from '../database/models/userModel'; +import dbConnection from '../database'; +import Product from '../database/models/productEntity'; +import { Order } from '../database/models/orderEntity'; +import Service from '../database/models/serviceEntity'; + +export const processMessage = async ( + userId: number, + message: string +): Promise => { + const userRepo = dbConnection.getRepository(User); + const chatRepo = dbConnection.getRepository(Chat); + + const user = await userRepo.findOne({ where: { id: userId } }); + + if (!user) { + throw new Error('User not found'); + } + + const currentHour = new Date().getHours(); + let greeting = ''; + + if (currentHour < 12) { + greeting = 'Good morning'; + } else if (currentHour < 18) { + greeting = 'Good afternoon'; + } else { + greeting = 'Good evening'; + } + + const initialGreeting = `${greeting}, ${user.firstName} ${user.lastName}!`; + + const analyzedMessage = analyzeMessage(message); + + const response = await generateResponse(analyzedMessage, userId); + + const completeResponse = `${initialGreeting} ${response}`; + + const chat = new Chat(); + chat.user = user; + chat.message = message; + chat.response = completeResponse; + await chatRepo.save(chat); + + return completeResponse; +}; + +export const getChatHistory = async (userId: number): Promise => { + const chatRepo = dbConnection.getRepository(Chat); + return chatRepo.find({ + where: { user: { id: userId } }, + order: { createdAt: 'DESC' }, + }); +}; + +export const getProducts = async () => { + const productRepo = dbConnection.getRepository(Product); + return productRepo.find(); +}; + +export const getProductByName = async (name: string) => { + const productRepo = dbConnection.getRepository(Product); + return productRepo.findOne({ where: { name } }); +}; + +export const getOrders = async (userId: number) => { + const orderRepo = dbConnection.getRepository(Order); + return orderRepo.find({ where: { user: { id: userId } } }); +}; + +export const getOrderByUserId = async (userId: number) => { + const orderRepo = dbConnection.getRepository(Order); + return orderRepo.findOne({ where: { user: { id: userId } } }); +}; + +export const getOrderStatusByTrackingNumber = async ( + trackingNumber: string +) => { + const orderRepo = dbConnection.getRepository(Order); + const order = await orderRepo.findOne({ where: { trackingNumber } }); + return order ? order.status : 'Tracking number not found'; +}; + +export const getServices = async () => { + const serviceRepo = dbConnection.getRepository(Service); + return serviceRepo.find(); +}; + +export const getServiceByName = async (name: string) => { + const serviceRepo = dbConnection.getRepository(Service); + return serviceRepo.findOne({ where: { name } }); +}; diff --git a/src/service/serviceService.ts b/src/service/serviceService.ts new file mode 100644 index 00000000..0f501816 --- /dev/null +++ b/src/service/serviceService.ts @@ -0,0 +1,13 @@ +import Service from '../database/models/serviceEntity'; +import dbConnection from '../database'; + +export const createService = async (serviceData) => { + const serviceRepo = dbConnection.getRepository(Service); + const service = new Service(serviceData); + return serviceRepo.save(service); +}; + +export const getAllServices = async () => { + const serviceRepo = dbConnection.getRepository(Service); + return serviceRepo.find(); +}; diff --git a/src/utilis/nlp.ts b/src/utilis/nlp.ts new file mode 100644 index 00000000..900ea8ff --- /dev/null +++ b/src/utilis/nlp.ts @@ -0,0 +1,565 @@ +import { + getProductByName, + getProducts, + getOrderByUserId, + getOrderStatusByTrackingNumber, + getServices, + getServiceByName, +} from '../service/chatbotService'; + +export const extractKeyword = (message: string, keyword: string): string => { + return message.replace(keyword, '').trim(); +}; + +export const analyzeMessage = (message: string): string => { + return message.toLowerCase(); +}; + +export const identifyIntent = ( + message: string +): { intent: string; keyword: string } => { + const lowerMessage = message.toLowerCase(); + + if ( + lowerMessage.includes('hi') || + lowerMessage.includes('hello') || + lowerMessage.includes('hey') || + lowerMessage.includes('good morning') || + lowerMessage.includes('good afternoon') || + lowerMessage.includes('good evening') || + lowerMessage.includes('good to see you again') || + lowerMessage.includes('nice to see you again') || + lowerMessage.includes('i’m glad to see you') || + lowerMessage.includes('i’m happy to see you') || + lowerMessage.includes('i’m pleased to see you') + ) + return { intent: 'greeting', keyword: '' }; + + if ( + lowerMessage.includes('what products do you sell') || + lowerMessage.includes('sell') + ) + return { intent: 'listProducts', keyword: '' }; + if (lowerMessage.includes('do you have') && lowerMessage.includes('in stock')) + return { + intent: 'checkStock', + keyword: extractKeyword(lowerMessage, 'do you have in stock'), + }; + if ( + lowerMessage.includes('tell me more about') || + lowerMessage.includes('more about') + ) + return { + intent: 'productInfo', + keyword: extractKeyword(lowerMessage, 'tell me more about'), + }; + if ( + lowerMessage.includes('available for purchase') || + lowerMessage.includes('purchase') + ) + return { + intent: 'productAvailability', + keyword: extractKeyword(lowerMessage, 'is available for purchase'), + }; + if (lowerMessage.includes('price of') || lowerMessage.includes('cost of')) + return { + intent: 'productPrice', + keyword: extractKeyword(lowerMessage, 'what is the price of'), + }; + if ( + lowerMessage.includes('see product reviews for') || + lowerMessage.includes('product reviews') + ) + return { + intent: 'productReviews', + keyword: extractKeyword(lowerMessage, 'can i see product reviews for'), + }; + if ( + lowerMessage.includes('similar products to') || + lowerMessage.includes('similar to') + ) + return { + intent: 'similarProducts', + keyword: extractKeyword( + lowerMessage, + 'are there any similar products to' + ), + }; + if ( + lowerMessage.includes('best-selling product in') || + lowerMessage.includes('best-selling') + ) + return { + intent: 'bestSellingProduct', + keyword: extractKeyword( + lowerMessage, + 'what is the best-selling product in' + ), + }; + if ( + lowerMessage.includes('offer warranties for') || + lowerMessage.includes('warranty') + ) + return { + intent: 'productWarranty', + keyword: extractKeyword(lowerMessage, 'do you offer warranties for'), + }; + if ( + lowerMessage.includes('track my order') || + lowerMessage.includes('where is my order') || + lowerMessage.includes('order status') + ) + return { intent: 'trackOrder', keyword: '' }; + if ( + lowerMessage.includes('cancel my order') || + lowerMessage.includes('cancel order') + ) + return { + intent: 'cancelOrder', + keyword: extractKeyword(lowerMessage, 'how can i cancel my order'), + }; + if ( + lowerMessage.includes('return my order') || + lowerMessage.includes('return order') + ) + return { + intent: 'returnOrder', + keyword: extractKeyword(lowerMessage, 'how can i return my order'), + }; + if ( + lowerMessage.includes('status of my order') || + lowerMessage.includes('order status') + ) + return { + intent: 'orderStatus', + keyword: extractKeyword(lowerMessage, 'what is the status of my order'), + }; + if ( + lowerMessage.includes('change the delivery address for my order') || + lowerMessage.includes('change address') + ) + return { + intent: 'changeOrderAddress', + keyword: extractKeyword( + lowerMessage, + 'can i change the delivery address for my order' + ), + }; + if ( + lowerMessage.includes('know if my order has been shipped') || + lowerMessage.includes('order shipped') + ) + return { + intent: 'orderShipped', + keyword: extractKeyword( + lowerMessage, + 'how do i know if my order has been shipped' + ), + }; + if ( + lowerMessage.includes('add items to an existing order') || + lowerMessage.includes('add items to order') + ) + return { + intent: 'addItemsToOrder', + keyword: extractKeyword( + lowerMessage, + 'can i add items to an existing order' + ), + }; + if (lowerMessage.includes('return policy')) + return { intent: 'returnPolicy', keyword: '' }; + if ( + lowerMessage.includes('request a refund') || + lowerMessage.includes('refund') + ) + return { intent: 'requestRefund', keyword: '' }; + if ( + lowerMessage.includes('shipping charges on my order') || + lowerMessage.includes('shipping charges') + ) + return { intent: 'shippingCharges', keyword: '' }; + if ( + lowerMessage.includes('expedite my shipping') || + lowerMessage.includes('expedite shipping') + ) + return { intent: 'expediteShipping', keyword: '' }; + if ( + lowerMessage.includes('what services do you offer') || + lowerMessage.includes('services') + ) + return { intent: 'listServices', keyword: '' }; + if ( + lowerMessage.includes('provide gift wrapping services') || + lowerMessage.includes('gift wrapping') + ) + return { intent: 'giftWrapping', keyword: '' }; + if ( + lowerMessage.includes('schedule a delivery') || + lowerMessage.includes('schedule delivery') + ) + return { intent: 'scheduleDelivery', keyword: '' }; + if ( + lowerMessage.includes('installation services for') || + lowerMessage.includes('installation') + ) + return { + intent: 'installationServices', + keyword: extractKeyword( + lowerMessage, + 'do you offer installation services for' + ), + }; + if ( + lowerMessage.includes('international shipping') || + lowerMessage.includes('international') + ) + return { intent: 'internationalShipping', keyword: '' }; + if (lowerMessage.includes('shipping options')) + return { intent: 'shippingOptions', keyword: '' }; + if ( + lowerMessage.includes('create a wishlist') || + lowerMessage.includes('wishlist') + ) + return { intent: 'createWishlist', keyword: '' }; + if ( + lowerMessage.includes('offer customer support') || + lowerMessage.includes('customer support') + ) + return { intent: 'customerSupport', keyword: '' }; + if ( + lowerMessage.includes('contact customer support') || + lowerMessage.includes('contact support') + ) + return { intent: 'contactSupport', keyword: '' }; + if ( + lowerMessage.includes('customer service hours') || + lowerMessage.includes('service hours') + ) + return { intent: 'customerServiceHours', keyword: '' }; + if ( + lowerMessage.includes('loyalty program') || + lowerMessage.includes('loyalty') + ) + return { intent: 'loyaltyProgram', keyword: '' }; + if ( + lowerMessage.includes('sign up for your newsletter') || + lowerMessage.includes('newsletter') + ) + return { intent: 'newsletterSignUp', keyword: '' }; + if ( + lowerMessage.includes('create an account') || + lowerMessage.includes('sign up') + ) + return { intent: 'createAccount', keyword: '' }; + if ( + lowerMessage.includes('log in to my account') || + lowerMessage.includes('log in') + ) + return { intent: 'logInAccount', keyword: '' }; + if ( + lowerMessage.includes('reset my password') || + lowerMessage.includes('reset password') + ) + return { intent: 'resetPassword', keyword: '' }; + if ( + lowerMessage.includes('update my account information') || + lowerMessage.includes('update account') + ) + return { intent: 'updateAccountInfo', keyword: '' }; + if ( + lowerMessage.includes('view my order history') || + lowerMessage.includes('order history') + ) + return { intent: 'viewOrderHistory', keyword: '' }; + if ( + lowerMessage.includes('manage my addresses') || + lowerMessage.includes('manage addresses') + ) + return { intent: 'manageAddresses', keyword: '' }; + if ( + lowerMessage.includes('delete my account') || + lowerMessage.includes('delete account') + ) + return { intent: 'deleteAccount', keyword: '' }; + if ( + lowerMessage.includes('payment methods do you accept') || + lowerMessage.includes('payment methods') + ) + return { intent: 'paymentMethods', keyword: '' }; + if ( + lowerMessage.includes('use a promo code') || + lowerMessage.includes('promo code') + ) + return { intent: 'usePromoCode', keyword: '' }; + if ( + lowerMessage.includes('safe to use my credit card on your site') || + lowerMessage.includes('credit card safety') + ) + return { intent: 'creditCardSafety', keyword: '' }; + if ( + lowerMessage.includes('multiple payment methods for a single purchase') || + lowerMessage.includes('multiple payment methods') + ) + return { intent: 'multiplePaymentMethods', keyword: '' }; + if ( + lowerMessage.includes('apply a gift card to my purchase') || + lowerMessage.includes('apply gift card') + ) + return { intent: 'applyGiftCard', keyword: '' }; + if ( + lowerMessage.includes('financing options') || + lowerMessage.includes('financing') + ) + return { intent: 'financingOptions', keyword: '' }; + if ( + lowerMessage.includes('invoice for my purchase') || + lowerMessage.includes('invoice') + ) + return { intent: 'getInvoice', keyword: '' }; + if (lowerMessage.includes('business hours') || lowerMessage.includes('hours')) + return { intent: 'businessHours', keyword: '' }; + if ( + lowerMessage.includes('where are you located') || + lowerMessage.includes('location') + ) + return { intent: 'location', keyword: '' }; + if ( + lowerMessage.includes('do you have a physical store') || + lowerMessage.includes('physical store') + ) + return { intent: 'physicalStore', keyword: '' }; + if ( + lowerMessage.includes('how can i contact you') || + lowerMessage.includes('contact') + ) + return { intent: 'contactInfo', keyword: '' }; + if ( + lowerMessage.includes('ongoing sales or promotions') || + lowerMessage.includes('sales promotions') + ) + return { intent: 'salesPromotions', keyword: '' }; + if ( + lowerMessage.includes('provide feedback on your service') || + lowerMessage.includes('feedback') + ) + return { intent: 'provideFeedback', keyword: '' }; + if ( + lowerMessage.includes('follow you on social media') || + lowerMessage.includes('social media') + ) + return { intent: 'socialMedia', keyword: '' }; + return { intent: 'unknown', keyword: '' }; +}; + +export const generateResponse = async ( + message: string, + userId: number +): Promise => { + const { intent, keyword } = identifyIntent(message); + + switch (intent) { + case 'greeting': + return 'How can I assist you today?'; + case 'listProducts': + const products = await getProducts(); + return `We sell the following products: ${products.map((p) => p.name).join(', ')}.`; + + case 'checkStock': + const productStock = await getProductByName(keyword); + return productStock + ? `${productStock.name} is in stock.` + : `Sorry, we do not have ${keyword}`; + + case 'productInfo': + const productInfo = await getProductByName(keyword); + return productInfo + ? `Here is more information about ${productInfo.name}: ${productInfo.averageRating}` + : `Sorry, we do not have information on ${keyword}.`; + + case 'productAvailability': + const productAvailability = await getProductByName(keyword); + return productAvailability + ? `${productAvailability.name} is available for purchase.` + : `Sorry, ${keyword} is not available for purchase.`; + + 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 'productReviews': + const productReviews = await getProductByName(keyword); + return productReviews + ? `${productReviews.name} has the following reviews: ${productReviews.reviews.join(', ')}` + : `Sorry, there are no reviews available for ${keyword}.`; + + case 'similarProducts': + const similarProducts = await getProducts(); + return similarProducts + ? `Here are some products similar to ${keyword}: ${similarProducts.map((p) => p.name).join(', ')}` + : `Sorry, we do not have similar products to ${keyword}.`; + + case 'bestSellingProduct': + const bestSellingProducts = await getProducts(); + return bestSellingProducts + ? `The best-selling product in ${keyword} is ${bestSellingProducts[0].name}.` + : `Sorry, we do not have best-selling products information for ${keyword}.`; + + case 'productWarranty': + const productWarranty = await getProductByName(keyword); + return productWarranty + ? `${productWarranty.name} comes with a ${productWarranty.salesPrice} warranty.` + : `Sorry, we do not have warranty information for ${keyword}.`; + + case 'trackOrder': + const order = await getOrderByUserId(userId); + return order + ? `Your order status is: ${order.status}.` + : 'Sorry, we could not find any orders for your account.'; + + case 'cancelOrder': + return 'To cancel your order on our website, please follow these steps: First, log in to your account using your credentials. Next, navigate to the Order History section, where you can view a list of your recent orders. Identify the order you wish to cancel and select the option to cancel it. If the order is eligible for cancellation, you will be prompted to confirm your decision. Once confirmed, your order will be canceled, and you will receive a confirmation message. If you encounter any difficulties or have any questions during the process, please don not hesitate to contact our customer support team for assistance'; + + case 'returnOrder': + return 'You can return your order by following the return instructions sent with your order.'; + + case 'orderStatus': + const orderStatus = await getOrderByUserId(userId); + return orderStatus + ? `Your order status is: ${orderStatus.status}.` + : 'Sorry, we could not find any orders for your account.'; + + case 'changeOrderAddress': + return ' To change the delivery address of your order, follow these steps: First, ensure you are logged into your account on our website. Then, navigate to the Account Settings section, where you will find options to manage your order details. Locate the specific order for which you would like to update the delivery address. Once you have identified the order, you should see an option to modify the delivery information. Click on this option, and you will be prompted to enter the new delivery address. Finally, review the changes carefully before confirming to ensure accuracy. If you encounter any difficulties or need further assistance, dot hesitate to reach out to our customer support team for guidance and contacting our customer support.'; + + case 'orderShipped': + const shippedOrder = await getOrderStatusByTrackingNumber( + userId.toString() + ); + return shippedOrder + ? 'To find shipping information for your requested service, please follow these guidelines: Navigate to Checkout: Proceed to the checkout page by adding your desired service to the cart and clicking on the Checkout button.Then, Provide Delivery Information: Enter your shipping address and any additional delivery instructions in the designated fields. Ensure that all details are accurate to avoid any delays or delivery issues.Select Shipping Method: During the checkout process, you will be prompted to choose a shipping method. Select the option that best suits your preferences and delivery timeline.Review Order Summary: Before finalizing your order, review the summary to ensure that all information, including shipping details and costs, is correct. Make any necessary adjustments if needed.Then Place Your Order: Once you are satisfied with the shipping information provided, proceed to place your order. Upon completion, you will receive a confirmation email with your order details and shipping information.' + : 'Sorry, we could not find any shipping information for your account.'; + + case 'addItemsToOrder': + return 'Items have been added to your order.'; + + case 'returnPolicy': + return 'Our return policy allows you to return products within 30 days of purchase. Please ensure the items are in their original condition.'; + + case 'requestRefund': + return 'To request a refund, please contact our customer support with your order details.'; + + case 'shippingCharges': + return 'Shipping charges depend on your location and the shipping method selected. You can view the charges at checkout.'; + + case 'expediteShipping': + return 'Yes, we offer expedited shipping options at checkout. Please select the expedited shipping method during the checkout process.'; + + case 'listServices': + const services = await getServices(); + return `We offer the following services: ${services.map((s) => s.name).join(', ')}.`; + case 'giftWrapping': + return 'Yes, we offer gift wrapping services at an additional cost. You can select this option during checkout.'; + + case 'scheduleDelivery': + return 'Yes, you can schedule a delivery date during the checkout process.'; + + case 'installationServices': + const service = await getServiceByName(keyword); + return service + ? `Yes, we offer installation services for ${keyword}.` + : `Sorry, we do not offer installation services for ${keyword}.`; + + case 'internationalShipping': + return 'Yes, we offer international shipping. Please select your destination country during checkout to see the available options.'; + + case 'shippingOptions': + return 'We offer standard, expedited, and international shipping options. Please choose your preferred method during checkout.'; + + case 'createWishlist': + return 'To create a wishlist of your requested services, log in to your account on our website to access your personalized wishlist page. Navigate to the services section, where you can browse through our offerings and select the services you are interested in adding to your wishlist. For each service you wish to add, click on the Add to Wishlist button or icon located near the service description. Once you have added all desired services to your wishlist, you can view and manage them by accessing your wishlist from your account dashboard. In your wishlist, you will be able to review the selected services, make any changes if needed, and proceed to request or purchase them at your convenience'; + + case 'customerSupport': + return 'To get customer support, simply contact our team via phone, email, on our website. For immediate assistance, send an email to dynamitesecommerce@gmail.com, or start a live chat session. Our support operates Monday to sunday, from 9 AM to 6 PM. Depending on your concern, our team will guide you through the appropriate steps or provide relevant information. Expect prompt and personalized assistance tailored to your needs. Your feedback is valuable, so feel free to share any suggestions after interacting with our team. We are dedicated to improving our support services to better serve you'; + + case 'contactSupport': + return 'You can Text us to our email at dynamitesecommerce@gmail.com, or live chat on our website.'; + + case 'customerServiceHours': + return 'Our customer service hours are 9 AM to 6 PM, Monday to Friday.'; + + case 'loyaltyProgram': + return 'Yes, we offer a loyalty program where you can earn points for every purchase and redeem them for discounts on future orders.'; + + case 'newsletterSignUp': + return 'You can sign up for our newsletter on our website to receive updates on new products, promotions, and special offers.'; + + case 'createAccount': + return 'To sign up for our services, visit our website homepage and click on the Sign Up or Register option. You will be directed to a registration form where you will need to provide essential details such as your name, email address, and password. After filling out the required information, review it for accuracy, and then click on the Sign Up or Register button to proceed. Once registered, you may receive a confirmation email containing a verification link according your user type like vendor or buyer; click on the link to verify your email address and complete the sign-up process. By following these steps, you will successfully create an account, gaining access to our range of services and features. If you encounter any issues, our customer support team is available to assist you.'; + + case 'logInAccount': + return 'To log in to your account, click on the Log In button on our website and enter your email and password.'; + + case 'resetPassword': + return 'To reset your password, click on the Forgot Password link on the login page and follow the instructions.'; + + case 'updateAccountInfo': + return 'To update your account information, log in to your account and go to the Account Settings section.'; + + case 'viewOrderHistory': + return 'To view your order history, log in to your account and go to the Order History section.'; + + case 'manageAddresses': + return 'To manage your addresses, log in to your account and go to the Address Book section.'; + + case 'deleteAccount': + return 'To delete your account, please contact our customer support and they will assist you with the process.'; + + case 'paymentMethods': + return 'We accept Visa, MasterCard, Credit Card,MOMO payment, PayPal, and gift cards.'; + + case 'usePromoCode': + return 'To use a promo code, enter the code at checkout in the Promo Code field and the discount will be applied to your order.'; + + case 'creditCardSafety': + return 'Yes, it is safe to use your credit card on our site. We use secure encryption to protect your personal information.'; + + case 'multiplePaymentMethods': + return 'Yes, you can use multiple payment methods for a single purchase by selecting Split Payment at checkout.'; + + case 'applyGiftCard': + return 'To apply a gift card to your purchase, enter the gift card number at checkout in the Gift Card field.'; + + case 'financingOptions': + return 'Yes, we offer financing options through our partner, Affirm. You can select Affirm at checkout to apply for financing.'; + + case 'getInvoice': + return 'Yes, you can get an invoice for your purchase by logging into your account and going to the Order History section.'; + + case 'businessHours': + return 'Our business hours are 24 weekly hours, Monday to Sunday.'; + + case 'location': + return 'We are located online and wherever you are around the world.'; + + case 'physicalStore': + return 'Yes, we have a physical store located in every country you belong too.'; + + case 'contactInfo': + return 'You can contact email at dynamitesecommerce@gmail.com, or live chat on our website.'; + + case 'salesPromotions': + return 'Yes, we have ongoing sales and promotions. Please visit our website and check the Promotions section for the latest offers.'; + + case 'provideFeedback': + return 'You can provide feedback on our service by filling out the feedback form on our website or contacting our customer support.'; + + case 'socialMedia': + return 'Yes, you can follow us on social media. Here are mostly use, like: Facebook, Twitter, Instagram.'; + + default: + return 'I am sorry, I did not understand your request. Can you please provide more details?'; + } +}; diff --git a/src/utils/couponCalculator.ts b/src/utils/couponCalculator.ts deleted file mode 100644 index 5f23f470..00000000 --- a/src/utils/couponCalculator.ts +++ /dev/null @@ -1,19 +0,0 @@ -import Coupon from '../database/models/couponEntity'; -import Product from '../database/models/productEntity'; -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'] }); - - 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