diff --git a/src/__test__/category.test.ts b/src/__test__/category.test.ts index 99f6daa..9c8cc14 100644 --- a/src/__test__/category.test.ts +++ b/src/__test__/category.test.ts @@ -1,6 +1,6 @@ import request from 'supertest'; import app from '../app'; -import { afterAllHook, beforeAllHook, getVendorToken } from './testSetup'; +import { afterAllHook, beforeAllHook, getAdminToken, getVendorToken } from './testSetup'; beforeAll(beforeAllHook); afterAll(afterAllHook); @@ -8,9 +8,11 @@ afterAll(afterAllHook); describe('Category Creation Tests', () => { beforeAll(async () => { token = await getVendorToken(); + adminToken = await getAdminToken() }); let token: string; let categoryId: number; + let adminToken: string; it('should create a new category with valid data', async () => { const categoryData = { @@ -203,4 +205,13 @@ describe('Category Creation Tests', () => { expect(response.status).toBe(404); expect(response.body.message).toBe('Category Not Found'); }); + + it('should return an array of category metrics', async () => { + const response = await request(app) + .get('/api/v1/category/get_metrics') + .set('Authorization', `Bearer ${adminToken}`) + + expect(response.status).toBe(200) + expect(response.body.data).toBeDefined() + }) }); diff --git a/src/__test__/userController.test.ts b/src/__test__/userController.test.ts index 875bd36..88144c3 100644 --- a/src/__test__/userController.test.ts +++ b/src/__test__/userController.test.ts @@ -4,6 +4,7 @@ import { afterAllHook, beforeAllHook } from './testSetup'; import jwt from 'jsonwebtoken'; import dbConnection from '../database'; import UserModel from '../database/models/userModel'; +import { getAdminToken } from './testSetup'; const userRepository = dbConnection.getRepository(UserModel); beforeAll(beforeAllHook); @@ -495,3 +496,18 @@ if (user) { } }); }); + +describe('User metrics tests', () => { + let adminToken:string; + beforeAll(async() => { + adminToken = await getAdminToken() + }) + it('should get user metrics successfully', async () => { + const response = await request(app).get('/api/v1/user/get_metrics') + .set('Authorization', `Bearer ${adminToken}`) + + expect(response.status).toBe(200) + expect(response.body.buyerData).toBeDefined() + expect(response.body.vendorData).toBeDefined() + }) +}) diff --git a/src/controller/categoryController.ts b/src/controller/categoryController.ts index 6610ef2..eea7bbd 100644 --- a/src/controller/categoryController.ts +++ b/src/controller/categoryController.ts @@ -3,8 +3,10 @@ import dbConnection from '../database'; import Category from '../database/models/categoryEntity'; import { check, validationResult } from 'express-validator'; import errorHandler from '../middlewares/errorHandler'; +import { Order } from '../database/models/orderEntity'; const categoryRepository = dbConnection.getRepository(Category); +const orderRepository = dbConnection.getRepository(Order) interface categoryRequestBody { name: string; @@ -130,3 +132,68 @@ export const deleteCategory = errorHandler( res.status(200).json({ message: 'Category deleted successfully' }); } ); + +export const getCategoryMetrics = errorHandler( + async (req: Request, res: Response) => { + const orders = await orderRepository.find({ + where:{ + paid: true + }, + select:{ + id:true, + totalAmount:true, + paid:true, + orderDetails:{ + id:true, + price:true, + quantity:true, + product:{ + id:true, + name:true, + category:{ + id:true, + name:true + } + }, + } + }, + relations:['orderDetails','orderDetails.product','orderDetails.product.category'] + }) + + const categories = await categoryRepository.find({ + select:{ + products:{ + id:true + } + }, + relations: ['products'] + }) + + const counter:{[key:string]:number} = {}; + for(const order of orders){ + for(const orderDetail of order.orderDetails){ + if(orderDetail.product.category.name in order){ + counter[orderDetail.product.category.name] += orderDetail.price + }else{ + counter[orderDetail.product.category.name] = orderDetail.price + } + } + } + + const data = [] + + for(const category of categories){ + if(category.name in counter){ + data.push({ + categoryName: category.name, + totalProducts: category.products.length, + totalSales: counter[category.name] + }) + } + } + + data.sort((a, b) => b.totalSales - a.totalSales) + + return res.status(200).json({data:data.slice(0,4)}) + } +); \ No newline at end of file diff --git a/src/controller/userController.ts b/src/controller/userController.ts index bcfe5eb..54a5dae 100644 --- a/src/controller/userController.ts +++ b/src/controller/userController.ts @@ -413,3 +413,21 @@ export const removeProfileImg = errorHandler( }); } ); + +export const getUserMetrics = errorHandler(async (req: Request, res: Response) => { + const users = await userRepository.find({relations:['userType']}); + + const buyerData: number[] = Array(12).fill(0); + const vendorData: number[] = Array(12).fill(0); + + for(const user of users){ + const monthIndex = user.createdAt.getMonth() + if(user.userType.name === 'Buyer'){ + buyerData[monthIndex] += 1 + }else if(user.userType.name === 'Vendor'){ + vendorData[monthIndex] += 1 + } + } + + return res.status(200).json({buyerData, vendorData}) +}) diff --git a/src/database/models/userModel.ts b/src/database/models/userModel.ts index ca1bc13..7a72493 100644 --- a/src/database/models/userModel.ts +++ b/src/database/models/userModel.ts @@ -4,6 +4,8 @@ import { Column, ManyToOne, OneToMany, + CreateDateColumn, + UpdateDateColumn, } from 'typeorm'; import { Role } from './roleEntity'; import { Order } from './orderEntity'; @@ -54,6 +56,12 @@ export default class UserModel { @Column({ nullable: true }) twoFactorCode: number; + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + constructor(user: Partial) { Object.assign(this, user); } diff --git a/src/docs/categoryDocs.ts b/src/docs/categoryDocs.ts index 5891db1..b971774 100644 --- a/src/docs/categoryDocs.ts +++ b/src/docs/categoryDocs.ts @@ -205,3 +205,22 @@ * '500': * description: Internal Server Error */ + + +/** + * @swagger + * /api/v1/category/get_metrics: + * get: + * summary: Get category metrics + * tags: [User] + * security: + * - bearerAuth: [] + * responses: + * '200': + * description: Successful + * '401': + * description: Unauthorized + * '500': + * description: Internal Server Error + */ + diff --git a/src/docs/userRegisterDocs.ts b/src/docs/userRegisterDocs.ts index 2f1d248..63720bb 100644 --- a/src/docs/userRegisterDocs.ts +++ b/src/docs/userRegisterDocs.ts @@ -399,3 +399,20 @@ * '500': * description: Internal Server Error */ + +/** + * @swagger + * /api/v1/user/get_metrics: + * get: + * summary: Get buyer and vendor metrics + * tags: [User] + * security: + * - bearerAuth: [] + * responses: + * '200': + * description: Successful + * '401': + * description: Unauthorized + * '500': + * description: Internal Server Error + */ diff --git a/src/emails/index.ts b/src/emails/index.ts index afeeca8..3a710c0 100644 --- a/src/emails/index.ts +++ b/src/emails/index.ts @@ -57,4 +57,4 @@ async function sendEmail(emailType: EmailType, recipient: string, data: Data) { } } -export default sendEmail; +export default sendEmail; \ No newline at end of file diff --git a/src/routes/categoryRoutes.ts b/src/routes/categoryRoutes.ts index f28acf6..44dd633 100644 --- a/src/routes/categoryRoutes.ts +++ b/src/routes/categoryRoutes.ts @@ -4,12 +4,16 @@ import { deleteCategory, getAllCategories, getCategory, + getCategoryMetrics, updateCategory, } from '../controller/categoryController'; import { IsLoggedIn } from '../middlewares/isLoggedIn'; +import { checkRole } from '../middlewares/authorize'; const categoryRouter = Router(); +categoryRouter.route('/get_metrics').get(IsLoggedIn, checkRole(['Admin']), getCategoryMetrics) + categoryRouter .route('/') .post(IsLoggedIn, createCategory) @@ -20,4 +24,5 @@ categoryRouter .put(IsLoggedIn, updateCategory) .delete(IsLoggedIn, deleteCategory); -export default categoryRouter; + +export default categoryRouter; \ No newline at end of file diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index 96120fc..74a0d71 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -11,7 +11,8 @@ import { updateProfile, deleteUser, changeProfileImg, - removeProfileImg + removeProfileImg, + getUserMetrics } from '../controller/userController'; import { @@ -56,4 +57,5 @@ userRouter.post('/subscribe', subscribe); userRouter.get('/subscribe/delete/:id', removeSubscriber); userRouter.get('/subscribe/getAll', getAllSubscriber); userRouter.route('/profileImg').patch(IsLoggedIn, upload.fields([{name:'image'}]), changeProfileImg).delete(IsLoggedIn, removeProfileImg) +userRouter.get('/get_metrics', IsLoggedIn, checkRole(['Admin']), getUserMetrics) export default userRouter;