diff --git a/src/__test__/userServices.test.ts b/src/__test__/userServices.test.ts index a58dff7..e8fc8a9 100644 --- a/src/__test__/userServices.test.ts +++ b/src/__test__/userServices.test.ts @@ -7,7 +7,7 @@ import { cleanDatabase } from './test-assets/DatabaseCleanup'; import { v4 as uuid } from 'uuid'; import { dbConnection } from '../startups/dbConnection'; -import bcrypt from 'bcrypt' +import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; const userId = uuid(); @@ -132,66 +132,78 @@ describe('User service Test', () => { }); it('should Login a user, with valid credentials', async () => { - const res = await request(app) - .post('/user/login') - .send({ - email: 'user@example.com', - password: 'password', - }); + const res = await request(app).post('/user/login').send({ + email: 'user@example.com', + password: 'password', + }); // Assert expect(res.status).toBe(200); expect(res.body.data.token).toBeDefined(); }); it('should send OTP, if 2FA is enabled', async () => { - const res = await request(app) - .post('/user/login') - .send({ - email: 'user2@example.com', - password: 'password', - }); + const res = await request(app).post('/user/login').send({ + email: 'user2@example.com', + password: 'password', + }); // Assert expect(res.status).toBe(200); expect(res.body.data.message).toBe('Please provide the OTP sent to your email or phone'); }); it('should not Login a user, with invalid credentials', async () => { - const res = await request(app) - .post('/user/login') - .send({ - email: 'user@example.com', - password: 'passwo', - }); + const res = await request(app).post('/user/login').send({ + email: 'user@example.com', + password: 'passwo', + }); // Assert expect(res.status).toBe(401); }); it('should not login User if user email is not verified', async () => { - const res = await request(app) - .post('/user/login') - .send({ - email: 'john.doe1@example.com', - password: 'password', - }); + const res = await request(app).post('/user/login').send({ + email: 'john.doe1@example.com', + password: 'password', + }); expect(res.status).toBe(400); - expect(res.body.message).toBe("Please verify your account"); + expect(res.body.message).toBe('Please verify your account'); }); it('should not login User if user is currently suspended', async () => { - const res = await request(app) - .post('/user/login') - .send({ - email: sampleUser1.email, - password: sampleUser1.password, - }); + const res = await request(app).post('/user/login').send({ + email: sampleUser1.email, + password: sampleUser1.password, + }); expect(res.status).toBe(400); - expect(res.body.message).toBe("Your account has been suspended"); + expect(res.body.message).toBe('Your account has been suspended'); }); }); describe('User Profile update', () => { + it('should get user information', async () => { + const res = await request(app) + .get('/user/profile') + .set('Authorization', `Bearer ${getAccessToken(userId, sampleUser.email)}`); + + expect(res.status).toBe(200); + expect(res.body.status).toBe('success'); + expect(res.body.data.code).toBe(200); + expect(res.body.data.message).toBe('profile fetched successfully'); + }); + + it('should get user information', async () => { + const res = await request(app) + .put('/user/profile') + .send({ images: [] }) + .set('Authorization', `Bearer ${getAccessToken(userId, sampleUser.email)}`); + + expect(res.status).toBe(400); + expect(res.body.status).toBe('error'); + expect(res.body.message).toBe("Cannot read properties of undefined (reading 'length')"); + }); + it('should update user profile', async () => { const res = await request(app) .put('/user/update') @@ -200,7 +212,6 @@ describe('User service Test', () => { lastName: 'Doe', gender: 'Male', phoneNumber: '0789412421', - photoUrl: 'photo.jpg', }) .set('Authorization', `Bearer ${getAccessToken(userId, sampleUser.email)}`); @@ -220,7 +231,7 @@ describe('User service Test', () => { .put('/user/update') .send({ firstName: 'firstName updated', - lastName: 'lastName updated' + lastName: 'lastName updated', }) .set('Authorization', `Bearer ${getAccessToken(userId, sampleUser.email)}`); @@ -230,45 +241,40 @@ describe('User service Test', () => { describe('User Reset Password', () => { it('should return response error, if no email and userID provided', async () => { - const respond = await request(app) - .post('/user/password/reset'); + const respond = await request(app).post('/user/password/reset'); expect(respond.status).toBe(400); }); it('should not reset password, for no existing Users', async () => { - const respond = await request(app) - .post('/user/password/reset') - .query({ - email: 'example@gmail.com', - userid: uuid() - }); + const respond = await request(app).post('/user/password/reset').query({ + email: 'example@gmail.com', + userid: uuid(), + }); expect(respond.status).toBe(404); }); it('should not reset password, if no new password sent', async () => { - const respond = await request(app) - .post('/user/password/reset') - .query({ - email: sampleUser.email, - userid: sampleUser.id - }); + const respond = await request(app).post('/user/password/reset').query({ + email: sampleUser.email, + userid: sampleUser.id, + }); expect(respond.status).toBe(400); expect(respond.body.message).toBe('Please provide all required fields'); }); - it('should not reset password, if new password doesn\'t match password in confirmPassword field', async () => { + it("should not reset password, if new password doesn't match password in confirmPassword field", async () => { const respond = await request(app) .post('/user/password/reset') .query({ email: sampleUser.email, - userid: sampleUser.id + userid: sampleUser.id, }) .send({ newPassword: 'new-password', - confirmPassword: 'password' + confirmPassword: 'password', }); expect(respond.status).toBe(400); @@ -276,12 +282,10 @@ describe('User service Test', () => { }); it('should not reset password, for incorrect user id syntax (invalid uuid)', async () => { - const respond = await request(app) - .post('/user/password/reset') - .query({ - email: sampleUser.email, - userid: 'invalid-=uuid' - }); + const respond = await request(app).post('/user/password/reset').query({ + email: sampleUser.email, + userid: 'invalid-=uuid', + }); expect(respond.status).toBe(500); }); @@ -290,11 +294,11 @@ describe('User service Test', () => { .post('/user/password/reset') .query({ email: sampleUser.email, - userid: sampleUser.id + userid: sampleUser.id, }) .send({ newPassword: 'new-password', - confirmPassword: 'new-password' + confirmPassword: 'new-password', }); expect(respond.status).toBe(200); @@ -304,22 +308,19 @@ describe('User service Test', () => { describe('User password reset link', () => { it('should send link to reset password', async () => { - const response = await request(app) - .post('/user/password/reset/link') - .query({ email: sampleUser.email }); + const response = await request(app).post('/user/password/reset/link').query({ email: sampleUser.email }); expect(response.status).toBe(200); }); it('should not send link to reset password, if no email provided', async () => { - const response = await request(app) - .post('/user/password/reset/link'); + const response = await request(app).post('/user/password/reset/link'); expect(response.status).toBe(400); expect(response.body.message).toBe('Missing required field'); }); - it('should not send link to reset password, if email doesn\'t exist in DB', async () => { + it("should not send link to reset password, if email doesn't exist in DB", async () => { const response = await request(app) .post('/user/password/reset/link') .query({ email: 'nonexistingemail@gmail.com' }); @@ -330,7 +331,6 @@ describe('User service Test', () => { }); describe('Start@FAProcess', () => { - it('should return 400 if not sent email in body on enabling 2fa', async () => { const data = {}; @@ -359,7 +359,10 @@ describe('User service Test', () => { const res = await request(app).post('/user/enable-2fa').send(data); expect(res.status).toBe(200); - expect(res.body).toEqual({ status: 'success', message: 'Two factor authentication enabled successfully' }); + expect(res.body.status).toBe('success'); + expect(res.body.data.code).toBe(200); + expect(res.body.data.message).toBe('Two factor authentication enabled successfully'); + expect(res.body.data.profile.twoFactorEnabled).toBe(true); }); it('should return 400 if not sent email in body on disabling 2fa', async () => { @@ -390,7 +393,10 @@ describe('User service Test', () => { const res = await request(app).post('/user/disable-2fa').send(data); expect(res.status).toBe(200); - expect(res.body).toEqual({ status: 'success', message: 'Two factor authentication disabled successfully' }); + expect(res.body.status).toBe('success'); + expect(res.body.data.code).toBe(200); + expect(res.body.data.message).toBe('Two factor authentication disabled successfully'); + expect(res.body.data.profile.twoFactorEnabled).toBe(false); }); it('should return 400 if not sent email and otp in body on verifying OTP', async () => { @@ -454,12 +460,10 @@ describe('User service Test', () => { }); it('should login user, if OTP provided is valid', async () => { - const res = await request(app) - .post('/user/verify-otp') - .send({ - email: sampleUser3.email, - otp: '123456', - }); + const res = await request(app).post('/user/verify-otp').send({ + email: sampleUser3.email, + otp: '123456', + }); expect(res.status).toBe(200); expect(res.body.data.token).toBeDefined(); diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts index 35c3d5a..eefdf0d 100644 --- a/src/controllers/authController.ts +++ b/src/controllers/authController.ts @@ -16,6 +16,8 @@ import { deactivateUserService } from '../services/updateUserStatus/deactivateUs import { userProfileUpdateServices } from '../services/userServices/userProfileUpdateServices'; import getAllUsers from '../services/userServices/getAllUsers'; import getUserById from '../services/userServices/getUserById'; +import getUserProfile from '../services/userServices/getUserProfile'; +import userUpdateProfilePicture from '../services/userServices/userUpdateProfileImage'; export const userRegistration = async (req: Request, res: Response) => { await userRegistrationService(req, res); @@ -76,4 +78,12 @@ export const getAllUsersController = async (req: Request, res: Response) => { export const getUserByIdController = async (req: Request, res: Response) => { await getUserById(req, res); -}; \ No newline at end of file +}; + +export const getUserProfileController = async (req: Request, res: Response) => { + await getUserProfile(req, res); +}; + +export const userUpdateProfilePictureController = async (req: Request, res: Response) => { + await userUpdateProfilePicture(req, res); +}; diff --git a/src/controllers/productController.ts b/src/controllers/productController.ts index 662a8a0..a5a18e3 100644 --- a/src/controllers/productController.ts +++ b/src/controllers/productController.ts @@ -10,9 +10,10 @@ import { productStatusServices, viewSingleProduct, searchProductService, - listAllProductsService, + listAllProductsService, confirmPayment, - getAllCategories + getAllCategories, + transaction, } from '../services'; export const readProduct = async (req: Request, res: Response) => { @@ -53,12 +54,14 @@ export const singleProduct = async (req: Request, res: Response) => { await viewSingleProduct(req, res); }; export const searchProduct = async (req: Request, res: Response) => { - await searchProductService (req, res); - + await searchProductService(req, res); }; export const Payment = async (req: Request, res: Response) => { await confirmPayment(req, res); }; export const getAllCategory = async (req: Request, res: Response) => { await getAllCategories(req, res); -}; \ No newline at end of file +}; +export const getAllTransaction = async (req: Request, res: Response) => { + await transaction(req, res); +}; diff --git a/src/routes/ProductRoutes.ts b/src/routes/ProductRoutes.ts index 902a1da..fa0ba4e 100644 --- a/src/routes/ProductRoutes.ts +++ b/src/routes/ProductRoutes.ts @@ -1,6 +1,6 @@ import { RequestHandler, Router } from 'express'; -import { productStatus, searchProduct, } from '../controllers/index'; +import { productStatus, searchProduct } from '../controllers/index'; import { hasRole } from '../middlewares/roleCheck'; import upload from '../middlewares/multer'; import { authMiddleware } from '../middlewares/verifyToken'; @@ -16,19 +16,23 @@ import { listAllProducts, singleProduct, createOrder, - getOrders, getOrder, + getOrders, + getOrder, updateOrder, - getOrdersHistory, Payment, + getOrdersHistory, + Payment, getSingleVendorOrder, getVendorOrders, updateVendorOrder, getBuyerVendorOrders, getSingleBuyerVendorOrder, updateBuyerVendorOrder, - getAllCategory + getAllCategory, + getAllTransaction, } from '../controllers'; const router = Router(); +router.get('/transaction', authMiddleware as RequestHandler, hasRole('ADMIN'), getAllTransaction); router.get('/search', searchProduct); router.get('/all', listAllProducts); router.get('/recommended', authMiddleware as RequestHandler, hasRole('BUYER'), getRecommendedProducts); @@ -55,10 +59,12 @@ router.get('/vendor/orders/:id', authMiddleware as RequestHandler, hasRole('VEND router.put('/vendor/orders/:id', authMiddleware as RequestHandler, hasRole('VENDOR'), updateVendorOrder); // Admin order management + router.get('/admin/orders', authMiddleware as RequestHandler, hasRole('ADMIN'), getBuyerVendorOrders); router.get('/admin/orders/:id', authMiddleware as RequestHandler, hasRole('ADMIN'), getSingleBuyerVendorOrder); router.put('/admin/orders/:id', authMiddleware as RequestHandler, hasRole('ADMIN'), updateBuyerVendorOrder); router.post('/payment/:id', authMiddleware as RequestHandler, hasRole('BUYER'), Payment); +//all transaction export default router; diff --git a/src/routes/UserRoutes.ts b/src/routes/UserRoutes.ts index 2a446ff..6a0cdd9 100644 --- a/src/routes/UserRoutes.ts +++ b/src/routes/UserRoutes.ts @@ -15,10 +15,13 @@ import { logout, getAllUsersController, getUserByIdController, + getUserProfileController, + userUpdateProfilePictureController, } from '../controllers'; import { activateUser, disactivateUser, userProfileUpdate } from '../controllers/index'; import { hasRole } from '../middlewares/roleCheck'; +import upload from '../middlewares/multer'; import passport from 'passport'; import '../utils/auth'; import { start2FAProcess } from '../services/userServices/userStartTwoFactorAuthProcess'; @@ -43,6 +46,8 @@ router.post('/deactivate', authMiddleware as RequestHandler, hasRole('ADMIN'), d router.post('/password/reset', userPasswordReset); router.post('/password/reset/link', sendPasswordResetLink); router.put('/update', authMiddleware as RequestHandler, userProfileUpdate); +router.get('/profile', authMiddleware as RequestHandler, getUserProfileController); +router.put('/profile', authMiddleware as RequestHandler, upload.array('images', 1), userUpdateProfilePictureController); router.get('/google-auth', passport.authenticate('google', { scope: ['profile', 'email'] })); router.get( diff --git a/src/services/index.ts b/src/services/index.ts index 80d463b..bd8a4ac 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -22,6 +22,7 @@ export * from './productServices/productStatus'; export * from './productServices/viewSingleProduct'; export * from './productServices/searchProduct'; export * from './productServices/payment'; +export * from './productServices/Transctions'; export * from './productServices/getCategories'; // Buyer wishlist services @@ -49,5 +50,5 @@ export * from './notificationServices/getNotifications'; export * from './notificationServices/deleteNotification'; export * from './notificationServices/updateNotification'; -// chatbot -export * from './chatbotServices/chatBot'; \ No newline at end of file +// chatbot +export * from './chatbotServices/chatBot'; diff --git a/src/services/productServices/Transctions.ts b/src/services/productServices/Transctions.ts new file mode 100644 index 0000000..00cc179 --- /dev/null +++ b/src/services/productServices/Transctions.ts @@ -0,0 +1,39 @@ +import { Request, Response } from 'express'; +import Stripe from 'stripe'; + +const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, { + apiVersion: '2024-04-10', +}); + +export const transaction = async (req: Request, res: Response) => { + try { + const payments = await stripe.paymentIntents.list(); + const paymentData = payments.data; + + // Calculate statistics + const totalPayments = paymentData.length; + const totalAmount = paymentData.reduce((sum, payment) => sum + payment.amount, 0); + const totalCapturedAmount = paymentData.reduce((sum, payment) => sum + payment.amount_received, 0); + const successfulPayments = paymentData.filter(payment => payment.status === 'succeeded').length; + const pendingPayments = paymentData.filter(payment => payment.status !== 'succeeded').length; + const averagePaymentAmount = totalPayments > 0 ? totalAmount / totalPayments : 0; + + // Prepare the response data + const responseData = { + payments: paymentData, + statistics: { + totalPayments, + totalAmount, + totalCapturedAmount, + successfulPayments, + pendingPayments, + averagePaymentAmount, + }, + }; + + res.json(responseData); + } catch (error) { + console.log(error); + res.status(500).json({ error: 'Something went wrong' }); + } +}; diff --git a/src/services/userServices/getUserProfile.ts b/src/services/userServices/getUserProfile.ts new file mode 100644 index 0000000..d1b1db1 --- /dev/null +++ b/src/services/userServices/getUserProfile.ts @@ -0,0 +1,18 @@ +import { Request, Response } from 'express'; +import { responseError, responseSuccess } from '../../utils/response.utils'; +import { UserInterface } from '../../entities/User'; + +const getUserProfile = async (req: Request, res: Response) => { + try { + const user = req.user; + // eslint-disable-next-line no-unused-vars + const { password, twoFactorCode, twoFactorCodeExpiresAt, ...safeUser } = user as UserInterface; + responseSuccess(res, 200, 'profile fetched successfully', { profile: safeUser }); + return; + } catch (error) { + responseError(res, 400, (error as Error).message); + return; + } +}; + +export default getUserProfile; diff --git a/src/services/userServices/userDisableTwoFactorAuth.ts b/src/services/userServices/userDisableTwoFactorAuth.ts index b4fc6e9..9644cde 100644 --- a/src/services/userServices/userDisableTwoFactorAuth.ts +++ b/src/services/userServices/userDisableTwoFactorAuth.ts @@ -1,7 +1,8 @@ import { Request, Response } from 'express'; -import { User } from '../../entities/User'; +import { User, UserInterface } from '../../entities/User'; import { getRepository } from 'typeorm'; import { sendNotification } from '../../utils/sendNotification'; +import { responseError, responseSuccess } from '../../utils/response.utils'; export const userDisableTwoFactorAuth = async (req: Request, res: Response) => { try { @@ -22,14 +23,23 @@ export const userDisableTwoFactorAuth = async (req: Request, res: Response) => { await userRepository.save(user); await sendNotification({ - content: "You disabled Two factor authentication on you account", + content: 'You disabled Two factor authentication on you account', type: 'user', - user: user - }) - return res.status(200).json({ status: 'success', message: 'Two factor authentication disabled successfully' }); + user: user, + }); + + const newUser = await userRepository.findOne({ + where: { + email: email, + }, + }); + + // eslint-disable-next-line no-unused-vars + const { password, twoFactorCode, twoFactorCodeExpiresAt, ...safeUser } = newUser as UserInterface; + return responseSuccess(res, 200, 'Two factor authentication disabled successfully', { + profile: safeUser, + }); } catch (error) { - if (error instanceof Error) { - return res.status(500).json({ status: 'error', message: error.message }); - } + responseError(res, 400, (error as Error).message); } }; diff --git a/src/services/userServices/userEnableTwoFactorAuth.ts b/src/services/userServices/userEnableTwoFactorAuth.ts index c5d3cbf..907848e 100644 --- a/src/services/userServices/userEnableTwoFactorAuth.ts +++ b/src/services/userServices/userEnableTwoFactorAuth.ts @@ -1,7 +1,8 @@ import { Request, Response } from 'express'; -import { User } from '../../entities/User'; +import { User, UserInterface } from '../../entities/User'; import { getRepository } from 'typeorm'; import { sendNotification } from '../../utils/sendNotification'; +import { responseError, responseSuccess } from '../../utils/response.utils'; export const userEnableTwoFactorAuth = async (req: Request, res: Response) => { try { @@ -22,14 +23,22 @@ export const userEnableTwoFactorAuth = async (req: Request, res: Response) => { await userRepository.save(user); await sendNotification({ - content: "You enabled Two factor authentication on you account", + content: 'You enabled Two factor authentication on you account', type: 'user', - user: user - }) - return res.status(200).json({ status: 'success', message: 'Two factor authentication enabled successfully' }); + user: user, + }); + + const newUser = await userRepository.findOne({ + where: { + email: email, + }, + }); + // eslint-disable-next-line no-unused-vars + const { password, twoFactorCode, twoFactorCodeExpiresAt, ...safeUser } = newUser as UserInterface; + return responseSuccess(res, 200, 'Two factor authentication enabled successfully', { + profile: safeUser, + }); } catch (error) { - if (error instanceof Error) { - return res.status(500).json({ status: 'error', message: error.message }); - } + responseError(res, 400, (error as Error).message); } }; diff --git a/src/services/userServices/userProfileUpdateServices.ts b/src/services/userServices/userProfileUpdateServices.ts index f65fc81..7b0e173 100644 --- a/src/services/userServices/userProfileUpdateServices.ts +++ b/src/services/userServices/userProfileUpdateServices.ts @@ -5,25 +5,21 @@ import { getRepository } from 'typeorm'; export const userProfileUpdateServices = async (req: Request, res: Response) => { try { - if (Object.keys(req.body).length === 0) { return responseError(res, 400, 'body required'); } - const { firstName, lastName, gender, phoneNumber, photoUrl} = req.body; + const { firstName, lastName, gender, phoneNumber } = req.body; // Validate user input - if ( - !firstName || !lastName || !gender || - !phoneNumber || !photoUrl - ) { + if (!firstName || !lastName || !gender || !phoneNumber) { return responseError(res, 400, 'Fill all the field'); } const userRepository = getRepository(User); const existingUser = await userRepository.findOne({ where: { - id: req.user?.id + id: req.user?.id, }, }); @@ -35,10 +31,19 @@ export const userProfileUpdateServices = async (req: Request, res: Response) => existingUser.lastName = lastName; existingUser.gender = gender; existingUser.phoneNumber = phoneNumber; - existingUser.photoUrl = photoUrl; - await userRepository.save(existingUser); - return responseSuccess(res, 200, 'User Profile has successfully been updated'); + + const newUser = await userRepository.findOne({ + where: { + id: req.user?.id, + }, + }); + + // eslint-disable-next-line no-unused-vars + const { password, twoFactorCode, twoFactorCodeExpiresAt, ...safeUser } = newUser as UserInterface; + return responseSuccess(res, 200, 'User Profile has successfully been updated', { + profile: safeUser, + }); } catch (error) { responseError(res, 400, (error as Error).message); } diff --git a/src/services/userServices/userUpdateProfileImage.ts b/src/services/userServices/userUpdateProfileImage.ts new file mode 100644 index 0000000..3a2279c --- /dev/null +++ b/src/services/userServices/userUpdateProfileImage.ts @@ -0,0 +1,58 @@ +import { Request, Response } from 'express'; +import { responseError, responseSuccess } from '../../utils/response.utils'; +import { User, UserInterface } from '../../entities/User'; +import { getRepository } from 'typeorm'; +import cloudinary from '../../utils/cloudinary'; + +declare module 'express' { + interface Request { + files?: any; + } +} + +const userUpdateProfilePicture = async (req: Request, res: Response) => { + try { + const user = req.user; + + const userRepository = getRepository(User); + const userToUpdate = await userRepository.findOne({ + where: { + email: user?.email, + }, + }); + + if (!userToUpdate) { + return responseError(res, 404, 'User not found'); + } + + const files: any = req.files; + + if (files.length < 1) { + return responseError(res, 400, 'Please upload an image'); + } + + for (const file of files) { + const image = file.path; + const link = await cloudinary.uploader.upload(image); + userToUpdate.photoUrl = link.secure_url; + } + + await userRepository.save(userToUpdate); + + const newUser = await userRepository.findOne({ + where: { + email: user?.email, + }, + }); + + // eslint-disable-next-line no-unused-vars + const { password, twoFactorCode, twoFactorCodeExpiresAt, ...safeUser } = newUser as UserInterface; + responseSuccess(res, 200, 'profile picture updated successfully', { profile: safeUser }); + return; + } catch (error) { + responseError(res, 400, (error as Error).message); + return; + } +}; + +export default userUpdateProfilePicture;