From 605f46333f4474aea9db631c86d81d84c2e2bc09 Mon Sep 17 00:00:00 2001 From: Ndevu12 Date: Wed, 22 May 2024 17:05:04 +0200 Subject: [PATCH] ft adding discount coupon feature --- src/__test__/cart.test.test.ts | 2 +- src/__test__/coupon.test.ts | 233 ++++++++++ src/__test__/getProduct.test.ts | 200 +++++---- src/__test__/userServices.test.ts | 2 +- src/__test__/vendorProduct.test.ts | 16 +- src/__test__/wishList.test.ts | 398 +++++++++--------- src/controllers/couponController.ts | 26 ++ src/docs/couponDocs.yml | 182 ++++++++ src/entities/Product.ts | 7 + src/entities/coupon.ts | 59 +++ src/helper/couponValidator.ts | 58 +++ src/routes/couponRoutes.ts | 14 + src/routes/index.ts | 4 +- .../couponServices/accessAllCoupon.ts | 37 ++ .../couponServices/createCouponService.ts | 55 +++ src/services/couponServices/deleteCoupon.ts | 23 + src/services/couponServices/readCoupon.ts | 23 + src/services/couponServices/updateService.ts | 59 +++ 18 files changed, 1075 insertions(+), 323 deletions(-) create mode 100644 src/__test__/coupon.test.ts create mode 100644 src/controllers/couponController.ts create mode 100644 src/docs/couponDocs.yml create mode 100644 src/entities/coupon.ts create mode 100644 src/helper/couponValidator.ts create mode 100644 src/routes/couponRoutes.ts create mode 100644 src/services/couponServices/accessAllCoupon.ts create mode 100644 src/services/couponServices/createCouponService.ts create mode 100644 src/services/couponServices/deleteCoupon.ts create mode 100644 src/services/couponServices/readCoupon.ts create mode 100644 src/services/couponServices/updateService.ts diff --git a/src/__test__/cart.test.test.ts b/src/__test__/cart.test.test.ts index feb3798..e7238d0 100644 --- a/src/__test__/cart.test.test.ts +++ b/src/__test__/cart.test.test.ts @@ -521,4 +521,4 @@ describe('Cart management for guest/buyer', () => { expect(response.body.data.cart).toBeDefined; }); }); -}); +}); \ No newline at end of file diff --git a/src/__test__/coupon.test.ts b/src/__test__/coupon.test.ts new file mode 100644 index 0000000..ed5fe13 --- /dev/null +++ b/src/__test__/coupon.test.ts @@ -0,0 +1,233 @@ +import request from 'supertest'; +import jwt from 'jsonwebtoken'; +import { app, server } from '../index'; +import { getConnection } from 'typeorm'; +import { dbConnection } from '../startups/dbConnection'; +import { User, UserInterface } from '../entities/User'; +import { Coupon } from '../entities/coupon'; +import { Product } from '../entities/Product'; +import { v4 as uuid } from 'uuid'; + +const vendor1Id = uuid(); +const product1Id = uuid(); +const couponCode = 'DISCOUNT20'; +const couponCode1 = 'DISCOUNT10'; +const invalidCouponCode = 'INVALIDCODE'; + +const jwtSecretKey = process.env.JWT_SECRET || ''; + +const getAccessToken = (id: string, email: string) => { + return jwt.sign( + { + id: id, + email: email, + }, + jwtSecretKey + ); +}; + +const sampleVendor1: UserInterface = { + id: vendor1Id, + firstName: 'Vendor', + lastName: 'User', + email: 'vendor@example.com', + password: 'password', + userType: 'Vendor', + gender: 'Male', + phoneNumber: '1234567890', + photoUrl: 'https://example.com/photo.jpg', + role: 'VENDOR', +}; + +const sampleProduct1 = { + id: product1Id, + name: 'Test Product', + description: 'Amazing product', + images: ['photo1.jpg', 'photo2.jpg', 'photo3.jpg'], + newPrice: 200, + quantity: 10, + vendor: sampleVendor1, +}; + +const sampleCoupon = { + code: couponCode, + discountRate: 20, + expirationDate: new Date('2025-01-01'), + maxUsageLimit: 100, + discountType: 'PERCENTAGE', + product: sampleProduct1, + vendor: sampleVendor1, +}; + +const sampleCoupon1 = { + code: couponCode1, + discountRate: 20, + expirationDate: new Date('2025-01-01'), + maxUsageLimit: 100, + discountType: 'PERCENTAGE', + product: sampleProduct1, + vendor: sampleVendor1, +}; + +beforeAll(async () => { + const connection = await dbConnection(); + + const userRepository = connection?.getRepository(User); + await userRepository?.save(sampleVendor1); + + const productRepository = connection?.getRepository(Product); + await productRepository?.save(sampleProduct1); + + const couponRepository = connection?.getRepository(Coupon); + await couponRepository?.save(sampleCoupon); + + const couponRepository1 = connection?.getRepository(Coupon); + await couponRepository1?.save(sampleCoupon1); +}); + +afterAll(async () => { + const connection = getConnection(); + + const couponRepository = connection.getRepository(Coupon); + await couponRepository.delete({}); + + const productRepository = connection.getRepository(Product); + await productRepository.delete({}); + + const userRepository = connection.getRepository(User); + await userRepository.delete({}); + + await connection.close(); + server.close(); +}); + +describe('Coupon Management System', () => { + describe('Create Coupon', () => { + it('should create a new coupon', async () => { + const response = await request(app) + .post(`/coupons/vendor/${vendor1Id}/`) + .send({ + code: 'NEWCOUPON10', + discountRate: 10, + expirationDate: '2025-12-31', + maxUsageLimit: 50, + discountType: 'PERCENTAGE', + product: product1Id, + }) + .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); + + expect(response.status).toBe(201); + expect(response.body.status).toBe('success'); + }, 10000); + + it('should return 400 for invalid coupon data', async () => { + const response = await request(app) + .post(`/coupons/vendor/${vendor1Id}/`) + .send({ + code: '', + discountRate: 'invalid', + expirationDate: 'invalid-date', + maxUsageLimit: 'invalid', + discountType: 'INVALID', + product: 'invalid-product-id', + }) + .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); + + expect(response.status).toBe(400); + }, 10000); + }); + + describe('Get All Coupons', () => { + it('should retrieve all coupons for a vendor', async () => { + const response = await request(app) + .get(`/coupons/vendor/${vendor1Id}/access-coupons`) + .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); + + expect(response.status).toBe(200); + expect(response.body.status).toBe('success'); + expect(response.body.data).toBeInstanceOf(Object); + }, 10000); + + it('should return 404 if no coupons found', async () => { + const newVendorId = uuid(); + const response = await request(app) + .get(`/coupons/vendor/${newVendorId}/access-coupons`) + .set('Authorization', `Bearer ${getAccessToken(newVendorId, 'newvendor@example.com')}`); + + expect(response.status).toBe(401); + }, 10000); + }); + + describe('Read Coupon', () => { + it('should read a single coupon by code', async () => { + const response = await request(app) + .get(`/coupons/vendor/${vendor1Id}/checkout/${couponCode}`) + .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); + + expect(response.status).toBe(200); + }, 10000); + + it('should return 404 for invalid coupon code', async () => { + const response = await request(app) + .get(`/coupons/vendor/${vendor1Id}/checkout/${invalidCouponCode}`) + .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); + + expect(response.status).toBe(404); + expect(response.body.status).toBe('error'); + expect(response.body.message).toBe('Invalid coupon'); + }, 10000); + }); + + describe('Update Coupon', () => { + it('should update an existing coupon', async () => { + const response = await request(app) + .put(`/coupons/vendor/${vendor1Id}/update-coupon/${couponCode1}`) + .send({ + code: 'KAGAHEBUZO04', + }) + .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); + + expect(response.status).toBe(200); + expect(response.body.status).toBe('success'); + }, 10000); + + it('should return 404 for updating a non-existent coupon', async () => { + const response = await request(app) + .put(`/coupons/vendor/${vendor1Id}/update-coupon/${invalidCouponCode}`) + .send({ + discountRate: 25, + }) + .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); + + expect(response.status).toBe(404); + expect(response.body.message).toBe('Coupon not found'); + }, 10000); + }); + + describe('Delete Coupon', () => { + it('should delete an existing coupon', async () => { + const response = await request(app) + .delete(`/coupons/vendor/${vendor1Id}/checkout/delete`) + .send({ + code: couponCode, + }) + .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); + + expect(response.status).toBe(200); + expect(response.body.status).toBe('success'); + }, 10000); + + it('should return 404 for deleting a non-existent coupon', async () => { + const response = await request(app) + .delete(`/coupons/vendor/${vendor1Id}/checkout/delete`) + .send({ + code: invalidCouponCode, + }) + .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); + + expect(response.status).toBe(404); + expect(response.body.status).toBe('error'); + expect(response.body.message).toBe('Invalid coupon'); + }, 10000); + }); +}); diff --git a/src/__test__/getProduct.test.ts b/src/__test__/getProduct.test.ts index ffcb6c4..0b9d911 100644 --- a/src/__test__/getProduct.test.ts +++ b/src/__test__/getProduct.test.ts @@ -10,7 +10,7 @@ import { Category } from '../entities/Category'; const vendor1Id = uuid(); const product1Id = uuid(); -const Invalidproduct = "11278df2-d026-457a-9471-4749f038df68"; +const Invalidproduct = '11278df2-d026-457a-9471-4749f038df68'; const catId = uuid(); const jwtSecretKey = process.env.JWT_SECRET || ''; @@ -25,111 +25,107 @@ const getAccessToken = (id: string, email: string) => { ); }; const sampleVendor1: UserInterface = { - id: vendor1Id, - firstName: 'vendor1o', - lastName: 'user', - email: 'vendor10@example.com', - password: 'password', - userType: 'Vendor', - gender: 'Male', - phoneNumber: '126380996348', - photoUrl: 'https://example.com/photo.jpg', - role: 'VENDOR', - }; - - const sampleCat = { - id: catId, - name: 'accessories', - }; + id: vendor1Id, + firstName: 'vendor1o', + lastName: 'user', + email: 'vendor10@example.com', + password: 'password', + userType: 'Vendor', + gender: 'Male', + phoneNumber: '126380996348', + photoUrl: 'https://example.com/photo.jpg', + role: 'VENDOR', +}; + +const sampleCat = { + id: catId, + name: 'accessories', +}; const sampleProduct1 = { - id: product1Id, - name: 'test product single', - description: 'amazing product', - images: ['photo1.jpg', 'photo2.jpg', 'photo3.jpg'], - newPrice: 200, - quantity: 10, - vendor: sampleVendor1, - categories: [sampleCat], - }; + id: product1Id, + name: 'test product single', + description: 'amazing product', + images: ['photo1.jpg', 'photo2.jpg', 'photo3.jpg'], + newPrice: 200, + quantity: 10, + vendor: sampleVendor1, + categories: [sampleCat], +}; beforeAll(async () => { - const connection = await dbConnection(); - - const categoryRepository = connection?.getRepository(Category); - await categoryRepository?.save({ ...sampleCat }); - - const userRepository = connection?.getRepository(User); - await userRepository?.save({ ...sampleVendor1 }); - - - const productRepository = connection?.getRepository(Product); - await productRepository?.save({ ...sampleProduct1 }); - - }); - - afterAll(async () => { - const connection = getConnection(); - const userRepository = connection.getRepository(User); - const categoryRepository = connection.getRepository(Category); - - const productRepository = await connection.getRepository(Product).delete({}); - if (productRepository) { - await userRepository.delete({}); - await categoryRepository.delete({}); - } - await connection.close(); - server.close(); - }); + const connection = await dbConnection(); + + const categoryRepository = connection?.getRepository(Category); + await categoryRepository?.save({ ...sampleCat }); + + const userRepository = connection?.getRepository(User); + await userRepository?.save({ ...sampleVendor1 }); + + const productRepository = connection?.getRepository(Product); + await productRepository?.save({ ...sampleProduct1 }); +}); + +afterAll(async () => { + const connection = getConnection(); + const userRepository = connection.getRepository(User); + const categoryRepository = connection.getRepository(Category); + + const productRepository = await connection.getRepository(Product).delete({}); + if (productRepository) { + await userRepository.delete({}); + await categoryRepository.delete({}); + } + await connection.close(); + server.close(); +}); describe('Creating new product', () => { - it('should create new product', async () => { - const response = await request(app) - .post('/product') - .field('name', 'test product3') - .field('description', 'amazing product3') - .field('newPrice', 200) - .field('quantity', 10) - .field('expirationDate', '10-2-2023') - .field('categories', 'technology') - .field('categories', 'sample') - .attach('images', `${__dirname}/test-assets/photo1.png`) - .attach('images', `${__dirname}/test-assets/photo2.webp`) - .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); - - expect(response.status).toBe(201); - expect(response.body.data.product).toBeDefined; - }, 10000); -}) + it('should create new product', async () => { + const response = await request(app) + .post('/product') + .field('name', 'test product3') + .field('description', 'amazing product3') + .field('newPrice', 200) + .field('quantity', 10) + .field('expirationDate', '10-2-2023') + .field('categories', 'technology') + .field('categories', 'sample') + .attach('images', `${__dirname}/test-assets/photo1.png`) + .attach('images', `${__dirname}/test-assets/photo2.webp`) + .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); + + expect(response.status).toBe(201); + expect(response.body.data.product).toBeDefined; + }, 20000); +}); describe('Get single product', () => { - it('should get a single product', async () => { - const response = await request(app) - .get(`/product/${product1Id}`) - .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); - - expect(response.status).toBe(200); - expect(response.body.status).toBe('success'); - expect(response.body.product).toBeDefined; - expect(response.body.product.id).toBe(product1Id); - }, 10000); - - it('should return 400 for invalid product Id', async () => { - const response = await request(app) - .get(`/product/non-existing-id`) - .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); - - expect(response.status).toBe(400); - expect(response.body.status).toBe('error'); - expect(response.body.message).toBe('Invalid product ID'); - }, 10000); - it('should return 404 for product not found', async () => { - const response = await request(app) - .get(`/product/${Invalidproduct}`) - .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); - - expect(response.status).toBe(404); - expect(response.body.status).toBe('error'); - expect(response.body.message).toBe('Product not found'); - }, 10000); - }); - \ No newline at end of file + it('should get a single product', async () => { + const response = await request(app) + .get(`/product/${product1Id}`) + .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); + + expect(response.status).toBe(200); + expect(response.body.status).toBe('success'); + expect(response.body.product).toBeDefined; + expect(response.body.product.id).toBe(product1Id); + }, 10000); + + it('should return 400 for invalid product Id', async () => { + const response = await request(app) + .get(`/product/non-existing-id`) + .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); + + expect(response.status).toBe(400); + expect(response.body.status).toBe('error'); + expect(response.body.message).toBe('Invalid product ID'); + }, 10000); + it('should return 404 for product not found', async () => { + const response = await request(app) + .get(`/product/${Invalidproduct}`) + .set('Authorization', `Bearer ${getAccessToken(vendor1Id, sampleVendor1.email)}`); + + expect(response.status).toBe(404); + expect(response.body.message).toBe('Product not found'); + }, 10000); +}); diff --git a/src/__test__/userServices.test.ts b/src/__test__/userServices.test.ts index 5249a9a..b26e467 100644 --- a/src/__test__/userServices.test.ts +++ b/src/__test__/userServices.test.ts @@ -237,4 +237,4 @@ describe('start2FAProcess', () => { expect(res.status).toBe(404); expect(res.body).toEqual({ status: 'error', message: 'Incorrect email or password' }); }, 10000); -}); +}); \ No newline at end of file diff --git a/src/__test__/vendorProduct.test.ts b/src/__test__/vendorProduct.test.ts index dfa96c4..e990333 100644 --- a/src/__test__/vendorProduct.test.ts +++ b/src/__test__/vendorProduct.test.ts @@ -456,29 +456,27 @@ describe('Vendor product management tests', () => { describe('List all products service', () => { it('should return all products for a given category', async () => { - const response = await request(app) - .get('/product/all') + const response = await request(app).get('/product/all'); expect(response.status).toBe(200); expect(response.body.data.products).toBeDefined(); }); - + it('should return no products for a non-existent category', async () => { const response = await request(app) .get('/product/all') - .query({ page: 1, limit: 10, category: 'nonexistentcategory' }) - + .query({ page: 1, limit: 10, category: 'nonexistentcategory' }); + expect(response.status).toBe(200); expect(response.body.data.products).toBeUndefined(); }); - + it('should return an error for invalid input syntax', async () => { const response = await request(app) .get('/product/all') - .query({ page: 'invalid', limit: 'limit', category: 'technology' }) - + .query({ page: 'invalid', limit: 'limit', category: 'technology' }); + expect(response.status).toBe(400); }); }); - }); diff --git a/src/__test__/wishList.test.ts b/src/__test__/wishList.test.ts index 2b2f120..f95565b 100644 --- a/src/__test__/wishList.test.ts +++ b/src/__test__/wishList.test.ts @@ -17,222 +17,202 @@ const catId = uuid(); const vendor2Id = uuid(); const sampleBuyer1: UserInterface = { - id: buyer1Id, - firstName: 'buyer1', - lastName: 'user', - email: 'buyer1@example.com', - password: 'password', - userType: 'Buyer', - gender: 'Male', - phoneNumber: '126380996347', - photoUrl: 'https://example.com/photo.jpg', - role: 'BUYER', - }; - const sampleBuyer2: UserInterface = { - id: buyer2Id, - firstName: 'buyer2', - lastName: 'use', - email: 'buyer2@example.com', - password: 'passwo', - userType: 'Buyer', - gender: 'Male', - phoneNumber: '1638099347', - photoUrl: 'https://example.com/photo.jpg', - role: 'BUYER', - }; - const sampleVendor1: UserInterface = { - id: vendor2Id, - firstName: 'vendor1', - lastName: 'user', - email: 'vendor11@example.com', - password: 'password', - userType: 'Vendor', - gender: 'Male', - phoneNumber: '12638090347', - photoUrl: 'https://example.com/photo.jpg', - role: 'VENDOR', - }; + id: buyer1Id, + firstName: 'buyer1', + lastName: 'user', + email: 'buyer1@example.com', + password: 'password', + userType: 'Buyer', + gender: 'Male', + phoneNumber: '126380996347', + photoUrl: 'https://example.com/photo.jpg', + role: 'BUYER', +}; +const sampleBuyer2: UserInterface = { + id: buyer2Id, + firstName: 'buyer2', + lastName: 'use', + email: 'buyer2@example.com', + password: 'passwo', + userType: 'Buyer', + gender: 'Male', + phoneNumber: '1638099347', + photoUrl: 'https://example.com/photo.jpg', + role: 'BUYER', +}; +const sampleVendor1: UserInterface = { + id: vendor2Id, + firstName: 'vendor1', + lastName: 'user', + email: 'vendor11@example.com', + password: 'password', + userType: 'Vendor', + gender: 'Male', + phoneNumber: '12638090347', + photoUrl: 'https://example.com/photo.jpg', + role: 'VENDOR', +}; let productInWishList: number; beforeAll(async () => { - const connection = await dbConnection(); - const userRepository = connection?.getRepository(User); - await userRepository?.save({ ...sampleBuyer1 }); - await userRepository?.save({ ...sampleBuyer2 }); - await userRepository?.save({...sampleVendor1}) - }); - - afterAll(async () => { - const connection = getConnection(); - const userRepository = connection.getRepository(User); - const categoryRepository = connection.getRepository(Category); - const wishListRepository = connection.getRepository(wishList) - - // Delete all records - const productRepository = await connection.getRepository(Product).delete({}); - if (productRepository) { - await userRepository.delete({}); - await categoryRepository.delete({}); - } + const connection = await dbConnection(); + const userRepository = connection?.getRepository(User); + await userRepository?.save({ ...sampleBuyer1 }); + await userRepository?.save({ ...sampleBuyer2 }); + await userRepository?.save({ ...sampleVendor1 }); +}); + +afterAll(async () => { + const connection = getConnection(); + const userRepository = connection.getRepository(User); + const categoryRepository = connection.getRepository(Category); + const wishListRepository = connection.getRepository(wishList); + + // Delete all records + const productRepository = await connection.getRepository(Product).delete({}); + if (productRepository) { await userRepository.delete({}); - await wishListRepository.delete({}) - - // Close the connection to the test database - await connection.close(); - server.close(); - }); - const data1 = { - id: buyer1Id, - email: sampleBuyer1.email - }; - const data2 = { - id: buyer2Id, - email: sampleBuyer2.email - } - const vendorData = { - id: vendor2Id, - email: sampleVendor1.email + await categoryRepository.delete({}); } - + await userRepository.delete({}); + await wishListRepository.delete({}); + + // Close the connection to the test database + await connection.close(); + server.close(); +}); +const data1 = { + id: buyer1Id, + email: sampleBuyer1.email, +}; +const data2 = { + id: buyer2Id, + email: sampleBuyer2.email, +}; +const vendorData = { + id: vendor2Id, + email: sampleVendor1.email, +}; const jwtSecretKey = process.env.JWT_SECRET || ''; describe('Wish list management tests', () => { + describe('Add product to wish list', () => { + it('should return 404 when product is not found', async () => { + const token = jwt.sign(data1, jwtSecretKey); + const response = await request(app).post(`/wish-list/add/${uuid()}`).set('Authorization', `Bearer ${token}`); + expect(response.status).toBe(404); + expect(response.body).toEqual({ message: 'Product not found' }); + }); + + it('should add a new product to wish list', async () => { + const vendorToken = jwt.sign(vendorData, jwtSecretKey); + const prod1Response = await request(app) + .post('/product') + .field('name', 'test product12') + .field('description', 'amazing product3') + .field('newPrice', 2000) + .field('quantity', 10) + .field('categories', 'technology') + .field('categories', 'sample') + .attach('images', `${__dirname}/test-assets/photo1.png`) + .attach('images', `${__dirname}/test-assets/photo2.webp`) + .set('Authorization', `Bearer ${vendorToken}`); + + product1Id = prod1Response.body.data.product.id; + + const prod2Response = await request(app) + .post('/product') + .field('name', 'more product2') + .field('description', 'food product3') + .field('newPrice', 2000) + .field('quantity', 10) + .field('categories', 'technology') + .field('categories', 'sample') + .attach('images', `${__dirname}/test-assets/photo1.png`) + .attach('images', `${__dirname}/test-assets/photo2.webp`) + .set('Authorization', `Bearer ${vendorToken}`); + + product2Id = prod2Response.body.data.product.id; + + const token = jwt.sign(data1, jwtSecretKey); + const response1 = await request(app).post(`/wish-list/add/${product1Id}`).set('Authorization', `Bearer ${token}`); + expect(response1.status).toBe(201); + expect(response1.body.data.message).toBe('Product Added to wish list'); + productInWishList = response1.body.data.wishlistAdded.id; + + await request(app).post(`/wish-list/add/${product2Id}`).set('Authorization', `Bearer ${token}`); + }); + + it('should tell if there is the product is already in the wish list', async () => { + const token = jwt.sign(data1, jwtSecretKey); + const response = await request(app).post(`/wish-list/add/${product1Id}`).set('Authorization', `Bearer ${token}`); + expect(response.status).toBe(401); + expect(response.body.data.message).toBe('Product Already in the wish list'); + }); + it('should return 500 when the ID is not valid', async () => { + const token = jwt.sign(data1, jwtSecretKey); + const response = await request(app) + .post(`/wish-list/add/kjwxq-wbjk2-2bwqs-21`) + .set('Authorization', `Bearer ${token}`); + expect(response.status).toBe(500); + }); + }); + + describe('Get products in wishList', () => { + it('Returns 404 when buyer has no product in wish list', async () => { + const token = jwt.sign(data2, jwtSecretKey); + const response = await request(app).get('/wish-list').set('Authorization', `Bearer ${token}`); + expect(response.status).toBe(404); + expect(response.body.message).toBe('No products in wish list'); + }); + + it('Returns products in the wish list for a buyer ', async () => { + const token = jwt.sign(data1, jwtSecretKey); + const response = await request(app).get('/wish-list').set('Authorization', `Bearer ${token}`); + expect(response.status).toBe(200); + expect(response.body.message).toBe('Products retrieved'); + }); + }); - describe('Add product to wish list', () => { - it('should return 404 when product is not found', async () => { - const token = jwt.sign(data1, jwtSecretKey); - const response = await request(app) - .post( `/wish-list/add/${uuid()}`) - .set('Authorization', `Bearer ${token}`); - expect(response.status).toBe(404); - expect(response.body).toEqual({ message: 'Product not found' }); - }) - - it('should add a new product to wish list', async () =>{ - const vendorToken = jwt.sign(vendorData, jwtSecretKey); - const prod1Response = await request(app) - .post('/product') - .field('name', 'test product12') - .field('description', 'amazing product3') - .field('newPrice', 2000) - .field('quantity', 10) - .field('categories', 'technology') - .field('categories', 'sample') - .attach('images', `${__dirname}/test-assets/photo1.png`) - .attach('images', `${__dirname}/test-assets/photo2.webp`) - .set('Authorization', `Bearer ${vendorToken}`); - - product1Id = prod1Response.body.data.product.id; - - const prod2Response = await request(app) - .post('/product') - .field('name', 'more product2') - .field('description', 'food product3') - .field('newPrice', 2000) - .field('quantity', 10) - .field('categories', 'technology') - .field('categories', 'sample') - .attach('images', `${__dirname}/test-assets/photo1.png`) - .attach('images', `${__dirname}/test-assets/photo2.webp`) - .set('Authorization', `Bearer ${vendorToken}`); - - product2Id = prod2Response.body.data.product.id; - - const token = jwt.sign(data1, jwtSecretKey); - const response1 = await request(app) - .post( `/wish-list/add/${product1Id}`) - .set('Authorization', `Bearer ${token}`); - expect(response1.status).toBe(201); - expect(response1.body.data.message).toBe('Product Added to wish list'); - productInWishList = response1.body.data.wishlistAdded.id; - - await request(app) - .post( `/wish-list/add/${product2Id}`) - .set('Authorization', `Bearer ${token}`); - }) - - it('should tell if there is the product is already in the wish list', async () => { - const token = jwt.sign(data1, jwtSecretKey); - const response = await request(app) - .post( `/wish-list/add/${product1Id}`) - .set('Authorization', `Bearer ${token}`); - expect(response.status).toBe(401); - expect(response.body.data.message).toBe('Product Already in the wish list'); - }) - it('should return 500 when the ID is not valid', async() => { - const token = jwt.sign(data1, jwtSecretKey); - const response = await request(app) - .post( `/wish-list/add/kjwxq-wbjk2-2bwqs-21`) - .set('Authorization', `Bearer ${token}`); - expect(response.status).toBe(500); - }) - }) - - describe('Get products in wishList', () => { - it('Returns 404 when buyer has no product in wish list', async () => { - const token = jwt.sign(data2, jwtSecretKey); - const response = await request(app) - .get('/wish-list') - .set('Authorization', `Bearer ${token}`); - expect(response.status).toBe(404); - expect(response.body.message).toBe('No products in wish list'); - }) - - it('Returns products in the wish list for a buyer ', async () => { - const token = jwt.sign(data1, jwtSecretKey); - const response = await request(app) - .get('/wish-list') - .set('Authorization', `Bearer ${token}`); - expect(response.status).toBe(200); - expect(response.body.message).toBe('Products retrieved'); - }) - }) - - describe('Remove a product from wish lsit', () => { - it('should return 404 when product is not found in wish list', async () => { - const token = jwt.sign(data1, jwtSecretKey); - const response = await request(app) - .delete( `/wish-list/delete/${28}`) - .set('Authorization', `Bearer ${token}`); - expect(response.status).toBe(404); - expect(response.body.message).toBe('Product not found in wish list'); - }) - - it('should delete a product from wish list', async () => { - const token = jwt.sign(data1, jwtSecretKey); - const response = await request(app) - .delete( `/wish-list/delete/${productInWishList}`) - .set('Authorization', `Bearer ${token}`); - expect(response.status).toBe(200); - expect(response.body.message).toBe('Product removed from wish list'); - }) - it('should return 500 when the ID is not valid', async() => { - const token = jwt.sign(data1, jwtSecretKey); - const response = await request(app) - .delete( `/wish-list/delete/kjwxq-wbjk2-2bwqs-21`) - .set('Authorization', `Bearer ${token}`); - expect(response.status).toBe(500); - }) - }) - - describe('Clear all products in wish for a user', () => { - it('Returns 404 when buyer has no product in wish list', async () => { - const token = jwt.sign(data2, jwtSecretKey); - const response = await request(app) - .delete( '/wish-list/clearAll') - .set('Authorization', `Bearer ${token}`); - expect(response.status).toBe(404); - expect(response.body.message).toBe('No products in wish list'); - }) - - it('should delete all products for a nuyer in wish list', async () => { - const token = jwt.sign(data1, jwtSecretKey); - const response = await request(app) - .delete( '/wish-list/clearAll') - .set('Authorization', `Bearer ${token}`); - expect(response.status).toBe(200); - expect(response.body.message).toBe('All products removed successfully'); - }) - }) -}) \ No newline at end of file + describe('Remove a product from wish lsit', () => { + it('should return 404 when product is not found in wish list', async () => { + const token = jwt.sign(data1, jwtSecretKey); + const response = await request(app).delete(`/wish-list/delete/${28}`).set('Authorization', `Bearer ${token}`); + expect(response.status).toBe(404); + expect(response.body.message).toBe('Product not found in wish list'); + }); + + it('should delete a product from wish list', async () => { + const token = jwt.sign(data1, jwtSecretKey); + const response = await request(app) + .delete(`/wish-list/delete/${productInWishList}`) + .set('Authorization', `Bearer ${token}`); + expect(response.status).toBe(200); + expect(response.body.message).toBe('Product removed from wish list'); + }); + it('should return 500 when the ID is not valid', async () => { + const token = jwt.sign(data1, jwtSecretKey); + const response = await request(app) + .delete(`/wish-list/delete/kjwxq-wbjk2-2bwqs-21`) + .set('Authorization', `Bearer ${token}`); + expect(response.status).toBe(500); + }); + }); + + describe('Clear all products in wish for a user', () => { + it('Returns 404 when buyer has no product in wish list', async () => { + const token = jwt.sign(data2, jwtSecretKey); + const response = await request(app).delete('/wish-list/clearAll').set('Authorization', `Bearer ${token}`); + expect(response.status).toBe(404); + expect(response.body.message).toBe('No products in wish list'); + }); + + it('should delete all products for a nuyer in wish list', async () => { + const token = jwt.sign(data1, jwtSecretKey); + const response = await request(app).delete('/wish-list/clearAll').set('Authorization', `Bearer ${token}`); + expect(response.status).toBe(200); + expect(response.body.message).toBe('All products removed successfully'); + }); + }); +}); diff --git a/src/controllers/couponController.ts b/src/controllers/couponController.ts new file mode 100644 index 0000000..39eeb6e --- /dev/null +++ b/src/controllers/couponController.ts @@ -0,0 +1,26 @@ +import { Request, Response } from 'express'; +import { createCouponService } from '../services/couponServices/createCouponService'; +import { updateCouponService } from '../services/couponServices/updateService'; +import { deleteCouponService } from '../services/couponServices/deleteCoupon'; +import { accessAllCouponService } from '../services/couponServices/accessAllCoupon'; +import { readCouponService } from '../services/couponServices/readCoupon'; + +export const createCoupon = async (req: Request, res: Response) => { + await createCouponService(req, res); +}; + +export const updateCoupon = async (req: Request, res: Response) => { + await updateCouponService(req, res); +}; + +export const deleteCoupon = async (req: Request, res: Response) => { + await deleteCouponService(req, res); +}; + +export const accessAllCoupon = async (req: Request, res: Response) => { + await accessAllCouponService(req, res); +}; + +export const readCoupon = async (req: Request, res: Response) => { + await readCouponService(req, res); +}; diff --git a/src/docs/couponDocs.yml b/src/docs/couponDocs.yml new file mode 100644 index 0000000..f250230 --- /dev/null +++ b/src/docs/couponDocs.yml @@ -0,0 +1,182 @@ +/coupons/vendor/:id/access-coupons: + get: + tags: + - Vendor discount coupon management + summary: List all coupons + description: Return all coupons for the logged user + security: + - bearerAuth: [] + responses: + '200': + description: Return all coupons + '400': + description: Bad Request (syntax error, incorrect input format, etc..) + '401': + description: Unauthorized + '403': + description: Forbidden (Unauthorized action) + '500': + description: Internal server error + +/coupons/vendor/:id/checkout/:code: + get: + tags: + - Vendor discount coupon management + summary: Get a single coupon + description: Return a coupon based on the provided code + security: + - bearerAuth: [] + parameters: + - in: path + name: code + schema: + type: string + required: true + description: The code of the coupon + responses: + '200': + description: Return info for the coupon + '400': + description: Bad Request (syntax error, incorrect input format, etc..) + '401': + description: Unauthorized + '403': + description: Forbidden (Unauthorized action) + '404': + description: Coupon not found + '500': + description: Internal server error + +/coupons/coupons/vendor/:id: + post: + tags: + - Vendor discount coupon management + summary: Creates a new coupon + security: + - bearerAuth: [] + consumes: + - application/json + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + code: + type: string + discountType: + type: string + discountRate: + type: number + maxUsageLimit: + type: number + quantity: + type: number + product: + type: string + expirationDate: + type: string + format: date + required: + - code + - discountType + - maxUsageLimit + - product + responses: + '201': + description: Successfully added the coupon + '400': + description: Bad Request (syntax error, incorrect input format, etc..) + '401': + description: Unauthorized + '403': + description: Forbidden (Unauthorized action) + '404': + description: Coupon not found + '500': + description: Internal server error + +/coupons/coupons/vendor/:id/update-coupon/:code: + put: + tags: + - Vendor discount coupon management + summary: Update a coupon + security: + - bearerAuth: [] + parameters: + - in: path + name: code + schema: + type: string + required: true + description: The code of the coupon + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + code: + type: string + discountType: + type: string + discountRate: + type: number + maxUsageLimit: + type: number + quantity: + type: number + product: + type: string + expirationDate: + type: string + format: date + responses: + '200': + description: Successfully updated the coupon + '400': + description: Bad Request (syntax error, incorrect input format, etc..) + '401': + description: Unauthorized + '403': + description: Forbidden (Unauthorized action) + '404': + description: Coupon not found + '500': + description: Internal server error + +/coupons/vendor/:id/checkout/delete: + delete: + tags: + - Vendor discount coupon management + summary: Delete a coupon + security: + - bearerAuth: [] + parameters: + - in: path + name: id + schema: + type: string + required: true + description: The ID of the vendor + - in: query + name: code + schema: + type: string + required: true + description: The code of the coupon + responses: + '200': + description: Successfully deleted the coupon + '400': + description: Bad Request (syntax error, incorrect input format, etc..) + '401': + description: Unauthorized + '403': + description: Forbidden (Unauthorized action) + '404': + description: Coupon not found + '500': + description: Internal server error diff --git a/src/entities/Product.ts b/src/entities/Product.ts index 82603a4..b907e10 100644 --- a/src/entities/Product.ts +++ b/src/entities/Product.ts @@ -9,11 +9,14 @@ import { ManyToMany, OneToMany, JoinTable, + OneToOne, + JoinColumn, } from 'typeorm'; import { IsNotEmpty, IsString, IsBoolean, ArrayNotEmpty, IsArray, MaxLength } from 'class-validator'; import { User } from './User'; import { Category } from './Category'; import { Order } from './Order'; +import { Coupon } from './coupon'; @Entity() @Unique(['id']) @@ -32,6 +35,10 @@ export class Product { @OneToMany(() => Order, (order: any) => order.product) // Specify the inverse side of the relationship orders!: Order[]; + @OneToOne(() => Coupon, (coupons: any) => coupons.product) + @JoinColumn() + coupons?: Coupon; + @Column() @IsNotEmpty() @IsString() diff --git a/src/entities/coupon.ts b/src/entities/coupon.ts new file mode 100644 index 0000000..19e5a68 --- /dev/null +++ b/src/entities/coupon.ts @@ -0,0 +1,59 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + Unique, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + JoinColumn, +} from 'typeorm'; +import { IsDate, IsNotEmpty } from 'class-validator'; +import { User } from './User'; +import { Product } from './Product'; + +@Entity() +@Unique(['id']) +@Unique(['code']) // Ensure only 'code' is unique +export class Coupon { + @PrimaryGeneratedColumn('uuid') + @IsNotEmpty() + id!: string; + + @ManyToOne(() => User) + @IsNotEmpty() + @JoinColumn() + vendor!: User; + + @ManyToOne(() => Product, product => product.coupons) + @IsNotEmpty() + @JoinColumn() + product!: Product; + + @Column() + @IsNotEmpty() + code!: string; + + @Column() + @IsNotEmpty() + discountType!: string; + + @Column('float') + @IsNotEmpty() + discountRate!: number; + + @Column('timestamp', { nullable: false }) + @IsNotEmpty() + @IsDate() + expirationDate?: Date; + + @Column('int') + @IsNotEmpty() + maxUsageLimit!: number; + + @CreateDateColumn() + createdAt!: Date; + + @UpdateDateColumn() + updatedAt!: Date; +} diff --git a/src/helper/couponValidator.ts b/src/helper/couponValidator.ts new file mode 100644 index 0000000..9736aa8 --- /dev/null +++ b/src/helper/couponValidator.ts @@ -0,0 +1,58 @@ +import Joi from 'joi'; +import { Coupon } from '../entities/coupon'; + +export const validateCoupon = ( + coupon: Pick +): Joi.ValidationResult => { + const schema = Joi.object({ + code: Joi.string().min(5).required().messages({ + 'any.required': 'code is required.', + 'string.min': 'code must be at least 5 characters long.', + }), + discountRate: Joi.number().required().messages({ + 'any.required': 'discountRate is required.', + }), + expirationDate: Joi.date().required().messages({ + 'any.required': 'expirationDate is required.', + }), + maxUsageLimit: Joi.number().required().messages({ + 'any.required': 'maxUsageLimit is required.', + }), + discountType: Joi.string().required().messages({ + 'any.required': 'discountType is required.', + }), + product: Joi.string().required().messages({ + 'any.required': 'product is required.', + }), + }); + + return schema.validate(coupon); +}; + +export const validateCouponUpdate = ( + coupon: Partial> +): Joi.ValidationResult => { + const schema = Joi.object({ + code: Joi.string().min(5).messages({ + 'string.min': 'code must be at least 5 characters long.', + }), + discountRate: Joi.number().messages({ + 'number.base': 'discountRate must be a number.', + }), + expirationDate: Joi.date().messages({ + 'date.base': 'expirationDate must be a valid date.', + }), + maxUsageLimit: Joi.number().messages({ + 'number.base': 'maxUsageLimit must be a number.', + }), + discountType: Joi.string().messages({ + 'string.base': 'discountType must be a string.', + }), + }) + .min(1) + .messages({ + 'object.min': 'At least one field must be updated.', + }); + + return schema.validate(coupon); +}; diff --git a/src/routes/couponRoutes.ts b/src/routes/couponRoutes.ts new file mode 100644 index 0000000..a7a979c --- /dev/null +++ b/src/routes/couponRoutes.ts @@ -0,0 +1,14 @@ +import { RequestHandler, Router } from 'express'; +import { createCoupon, updateCoupon, accessAllCoupon, readCoupon, deleteCoupon } from '../controllers/couponController'; +import { hasRole } from '../middlewares/roleCheck'; +import { authMiddleware } from '../middlewares/verifyToken'; + +const router = Router(); + +router.post('/vendor/:id', authMiddleware as RequestHandler, hasRole('VENDOR'), createCoupon); +router.put('/vendor/:id/update-coupon/:code', authMiddleware as RequestHandler, hasRole('VENDOR'), updateCoupon); +router.get('/vendor/:id/checkout/:code', authMiddleware as RequestHandler, hasRole('VENDOR'), readCoupon); +router.get('/vendor/:id/access-coupons', authMiddleware as RequestHandler, hasRole('VENDOR'), accessAllCoupon); +router.delete('/vendor/:id/checkout/delete', authMiddleware as RequestHandler, hasRole('VENDOR'), deleteCoupon); + +export default router; diff --git a/src/routes/index.ts b/src/routes/index.ts index 3c6edec..cddc08a 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -3,6 +3,7 @@ import { responseSuccess } from '../utils/response.utils'; import userRoutes from './UserRoutes'; import productRoutes from './ProductRoutes'; import wishListRoutes from './wishListRoute'; +import couponRoute from './couponRoutes';; import cartRoutes from './CartRoutes'; const router = Router(); @@ -13,7 +14,8 @@ router.get('/', (req: Request, res: Response) => { router.use('/user', userRoutes); router.use('/product', productRoutes); -router.use('/wish-list', wishListRoutes); +router.use('/wish-list', wishListRoutes); router.use('/cart', cartRoutes); +router.use('/coupons', couponRoute); export default router; diff --git a/src/services/couponServices/accessAllCoupon.ts b/src/services/couponServices/accessAllCoupon.ts new file mode 100644 index 0000000..9266a44 --- /dev/null +++ b/src/services/couponServices/accessAllCoupon.ts @@ -0,0 +1,37 @@ +import { Request, Response } from 'express'; +import { responseSuccess, responseError, responseServerError } from '../../utils/response.utils'; +import { getRepository } from 'typeorm'; +import { Coupon } from '../../entities/coupon'; +import { User } from '../../entities/User'; + +export const accessAllCouponService = async (req: Request, res: Response) => { + try { + const { id } = req.params; + + // Retrieve the user by id + const userRepository = getRepository(User); + const user = await userRepository.findOne({ where: { id } }); + + if (!user) { + console.log('User not found with id:', id); + return responseError(res, 404, 'User not found'); + } + + // Retrieve all coupons for the user + const couponRepository = getRepository(Coupon); + const coupons = await couponRepository.find({ + where: { vendor: { id: user.id } }, + relations: ['product'], + }); + + if (!coupons.length) { + console.log('No coupons found for user with id:', id); + return responseError(res, 404, 'No coupons found'); + } + + return responseSuccess(res, 200, 'Coupons retrieved successfully', coupons); + } catch (error: any) { + console.log('Error retrieving all coupons:\n', error); + return responseServerError(res, error); + } +}; diff --git a/src/services/couponServices/createCouponService.ts b/src/services/couponServices/createCouponService.ts new file mode 100644 index 0000000..a824ddf --- /dev/null +++ b/src/services/couponServices/createCouponService.ts @@ -0,0 +1,55 @@ +import { Request, Response } from 'express'; +import { responseSuccess, responseError, responseServerError } from '../../utils/response.utils'; +import { getRepository } from 'typeorm'; +import { Coupon } from '../../entities/coupon'; +import { validateCoupon } from '../../helper/couponValidator'; +import { User } from '../../entities/User'; +import { Product } from '../../entities/Product'; + +export const createCouponService = async (req: Request, res: Response) => { + try { + const { error } = validateCoupon(req.body); + if (error) { + console.log('Validation Error creating coupon:\n', error); + return res.status(400).json({ status: 'error', error: error?.details[0].message }); + } + + const { code, discountRate, expirationDate, maxUsageLimit, discountType, product: productId } = req.body; + const { id: vendorId } = req.params; + + const userRepository = getRepository(User); + const user = await userRepository.findOne({ where: { id: vendorId } }); + if (!user) { + console.log('Error creating coupon: User not found', user); + return responseError(res, 404, 'User not found'); + } + + const productRepository = getRepository(Product); + const product = await productRepository.findOne({ where: { id: productId } }); + if (!product) { + console.log('Error creating coupon: Product not found', product); + return responseError(res, 403, 'Product not found'); + } + + const couponRepository = getRepository(Coupon); + const existingCoupon = await couponRepository.findOne({ where: { code } }); + if (existingCoupon) { + return responseError(res, 402, 'Coupon code already exists'); + } + + const newCoupon = new Coupon(); + newCoupon.code = code; + newCoupon.discountRate = discountRate; + newCoupon.expirationDate = expirationDate; + newCoupon.maxUsageLimit = maxUsageLimit; + newCoupon.discountType = discountType; + newCoupon.product = product; + newCoupon.vendor = user; + + await couponRepository.save(newCoupon); + responseSuccess(res, 201, 'Coupon created successfully'); + } catch (error: any) { + console.log('Error creating coupon:\n', error); + return responseServerError(res, error); + } +}; diff --git a/src/services/couponServices/deleteCoupon.ts b/src/services/couponServices/deleteCoupon.ts new file mode 100644 index 0000000..c984d9e --- /dev/null +++ b/src/services/couponServices/deleteCoupon.ts @@ -0,0 +1,23 @@ +import { Request, Response } from 'express'; +import { responseSuccess, responseError, responseServerError } from '../../utils/response.utils'; +import { getRepository } from 'typeorm'; +import { Coupon } from '../../entities/coupon'; + +export const deleteCouponService = async (req: Request, res: Response) => { + try { + const couponRepository = getRepository(Coupon); + const coupon = await couponRepository.findOne({ where: { code: req.body.code } }); + + if (!coupon) { + console.log('Invalid coupon.'); + return responseError(res, 404, 'Invalid coupon'); + } + + await couponRepository.remove(coupon); + + return responseSuccess(res, 200, 'Coupon deleted successfully'); + } catch (error: any) { + console.log('Error deleting coupon:\n', error); + return responseServerError(res, error); + } +}; diff --git a/src/services/couponServices/readCoupon.ts b/src/services/couponServices/readCoupon.ts new file mode 100644 index 0000000..47e12ea --- /dev/null +++ b/src/services/couponServices/readCoupon.ts @@ -0,0 +1,23 @@ +import { Request, Response } from 'express'; +import { responseSuccess, responseError, responseServerError } from '../../utils/response.utils'; +import { getRepository } from 'typeorm'; +import { Coupon } from '../../entities/coupon'; + +export const readCouponService = async (req: Request, res: Response) => { + try { + const { code } = req.params; + if (!code) return responseError(res, 400, 'coupon code is required'); + + const couponRepository = getRepository(Coupon); + const coupon = await couponRepository.findOne({ where: { code: code } }); + + if (!coupon) { + return responseError(res, 404, 'Invalid coupon'); + } + + return responseSuccess(res, 200, 'Coupon retrieved successfully', coupon); + } catch (error: any) { + console.log('Error retrieving coupon:\n', error); + return responseServerError(res, error); + } +}; diff --git a/src/services/couponServices/updateService.ts b/src/services/couponServices/updateService.ts new file mode 100644 index 0000000..26aeef6 --- /dev/null +++ b/src/services/couponServices/updateService.ts @@ -0,0 +1,59 @@ +import { Coupon } from '../../entities/coupon'; +import { Request, Response } from 'express'; +import { responseSuccess, responseError, responseServerError } from '../../utils/response.utils'; +import { getRepository } from 'typeorm'; +import { validateCouponUpdate } from '../../helper/couponValidator'; +import { Product } from '../../entities/Product'; + +export const updateCouponService = async (req: Request, res: Response) => { + try { + const { code } = req.params; + const { error } = validateCouponUpdate(req.body); + if (error) { + return res.status(400).json({ status: 'error', error: error?.details[0].message }); + } + + const couponRepository = getRepository(Coupon); + const coupon = await couponRepository.findOne({ where: { code } }); + if (coupon) { + if (req.body.code !== undefined) { + const existtCoupon = await couponRepository.findOne({ where: { code: req.body.code } }); + if (existtCoupon) return responseError(res, 400, 'Coupon code already exists'); + if (req.body.code === coupon.code) return responseError(res, 400, 'Coupon code already up to date'); + coupon.code = req.body.code; + } + if (req.body.discountRate !== undefined) { + coupon.discountRate = req.body.discountRate; + } + if (req.body.expirationDate !== undefined) { + coupon.expirationDate = req.body.expirationDate; + } + if (req.body.maxUsageLimit !== undefined) { + coupon.maxUsageLimit = req.body.maxUsageLimit; + } + if (req.body.discountType !== undefined) { + coupon.discountType = req.body.discountType; + } + if (req.body.product !== undefined) { + const { id } = req.body.product; + const productRepository = getRepository(Product); + const product = await productRepository.findOne({ where: { id } }); + if (!product) { + console.log('Error updating coupon: Product not found', product); + return responseError(res, 404, 'Product not found'); + } + + coupon.product = product; + } + + await couponRepository.save(coupon); + return responseSuccess(res, 200, 'Coupon updated successfully', coupon); + } else { + console.log('Error updating coupon: Coupon not found', coupon); + return responseError(res, 404, 'Coupon not found'); + } + } catch (error: any) { + console.log('Error while updating coupon:\n', error); + return responseServerError(res, error); + } +};