From d4a2c376f7ea6346220f7239528da69effb434e1 Mon Sep 17 00:00:00 2001 From: SamixYasuke Date: Sat, 10 Aug 2024 14:01:23 +0100 Subject: [PATCH] feat: get all products in organisation by super admin --- src/controllers/OrgController.ts | 144 ++++++++++++++++++++++++++++++- src/models/product.ts | 3 + src/routes/organisation.ts | 32 ++++--- src/routes/product.ts | 1 - src/services/org.services.ts | 15 +++- src/test/organisation.spec.ts | 80 +++++++++++++++++ 6 files changed, 259 insertions(+), 16 deletions(-) diff --git a/src/controllers/OrgController.ts b/src/controllers/OrgController.ts index df4e6595..49a1dc09 100644 --- a/src/controllers/OrgController.ts +++ b/src/controllers/OrgController.ts @@ -8,8 +8,9 @@ import { } from "../middleware"; import { OrgService } from "../services/org.services"; import log from "../utils/logger"; +import isSuperAdmin from "../utils/isSuperAdmin"; -export class OrgController { +class OrgController { private orgService: OrgService; constructor() { this.orgService = new OrgService(); @@ -1605,4 +1606,145 @@ export class OrgController { ); } } + + /** + * @swagger + * /api/v1/organizations/{org_id}/products: + * get: + * summary: Get all products for an organization + * tags: [Products] + * parameters: + * - in: path + * name: org_id + * required: true + * description: The ID of the organization + * schema: + * type: string + * example: "org-12345" + * responses: + * 200: + * description: Product retrieved successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * status_code: + * type: integer + * example: 200 + * message: + * type: string + * example: "Product retrieved successfully" + * data: + * type: object + * properties: + * products: + * type: array + * items: + * type: object + * properties: + * id: + * type: string + * example: "prod-12345" + * name: + * type: string + * example: "Product Name" + * description: + * type: string + * example: "Product Description" + * price: + * type: number + * example: 100.50 + * quantity: + * type: integer + * example: 10 + * category: + * type: string + * example: "Electronics" + * image: + * type: string + * example: "https://example.com/product-image.jpg" + * stock_status: + * type: string + * example: "In Stock" + * 401: + * description: User not authenticated + * content: + * application/json: + * schema: + * type: object + * properties: + * status_code: + * type: integer + * example: 401 + * message: + * type: string + * example: "User not authenticated" + * 403: + * description: User is not authorized to fetch all products + * content: + * application/json: + * schema: + * type: object + * properties: + * status_code: + * type: integer + * example: 403 + * message: + * type: string + * example: "User is not authorized to fetch all products" + * 500: + * description: Internal Server Error - An unexpected error has occurred + * content: + * application/json: + * schema: + * type: object + * properties: + * status_code: + * type: integer + * example: 500 + * message: + * type: string + * example: "An unexpected error has occurred" + */ + public getAllOrgProducts = async ( + req: Request, + res: Response, + next: NextFunction, + ) => { + try { + const { org_id } = req.params; + const userId = req.user?.id; + if (!userId) { + return res.status(401).json({ + status_code: 401, + success: false, + message: "User not authenticated", + }); + } + + const isAdmin = await isSuperAdmin(userId); + if (!isAdmin) { + return res.status(403).json({ + status_code: 403, + success: false, + message: "User is not authorized to fetch all products", + }); + } + + const products = await this.orgService.getAllOrgProducts(org_id); + return res.status(200).json({ + status_code: 200, + message: "Product retrieved successfully", + data: { products }, + }); + } catch (error) { + res.status(500).json({ + status_code: 500, + message: error.message || "An unexpected error has occurred", + }); + } + }; } + +export { OrgController }; diff --git a/src/models/product.ts b/src/models/product.ts index aadd5761..e386adcc 100644 --- a/src/models/product.ts +++ b/src/models/product.ts @@ -24,6 +24,9 @@ export class Product extends ExtendedBaseEntity { @Column() price: number; + @Column() + cost_price: number; + @Column({ default: 1 }) quantity: number; diff --git a/src/routes/organisation.ts b/src/routes/organisation.ts index 5e03e9b7..ffc914d2 100644 --- a/src/routes/organisation.ts +++ b/src/routes/organisation.ts @@ -14,40 +14,40 @@ const orgRouter = Router(); const orgController = new OrgController(); orgRouter.get( - "/organizations/invites", + "/organisations/invites", authMiddleware, checkPermissions([UserRole.SUPER_ADMIN, UserRole.ADMIN]), orgController.getAllInvite.bind(orgController), ); orgRouter.get( - "/organizations/:org_id", + "/organisations/:org_id", authMiddleware, validateOrgId, orgController.getSingleOrg.bind(orgController), ); orgRouter.delete( - "/organizations/:org_id/user/:user_id", + "/organisations/:org_id/user/:user_id", authMiddleware, validateOrgId, orgController.removeUser.bind(orgController), ); orgRouter.post( - "/organizations", + "/organisations", authMiddleware, organizationValidation, orgController.createOrganisation.bind(orgController), ); orgRouter.get( - "/organizations/:org_id/invite", + "/organisations/:org_id/invite", authMiddleware, checkPermissions([UserRole.ADMIN, UserRole.SUPER_ADMIN]), orgController.generateGenericInviteLink.bind(orgController), ); orgRouter.post( - "organizations/:org_id/roles", + "organisations/:org_id/roles", authMiddleware, validateOrgRole, checkPermissions([UserRole.SUPER_ADMIN, UserRole.ADMIN]), @@ -55,19 +55,19 @@ orgRouter.post( ); orgRouter.post( - "/organizations/:org_id/send-invite", + "/organisations/:org_id/send-invite", authMiddleware, checkPermissions([UserRole.SUPER_ADMIN, UserRole.ADMIN]), orgController.generateAndSendInviteLinks.bind(orgController), ); orgRouter.post( - "/organizations/accept-invite", + "/organisations/accept-invite", authMiddleware, orgController.addUserToOrganizationWithInvite.bind(orgController), ); orgRouter.get( - "/users/:id/organizations", + "/users/:id/organisations", authMiddleware, orgController.getOrganizations.bind(orgController), ); @@ -79,7 +79,7 @@ orgRouter.get( ); orgRouter.put( - "/organizations/:organization_id", + "/organisations/:organization_id", authMiddleware, validateUpdateOrg, checkPermissions([UserRole.SUPER_ADMIN, UserRole.USER]), @@ -87,22 +87,28 @@ orgRouter.put( ); orgRouter.get( - "/organizations/:org_id/roles/:role_id", + "/organisations/:org_id/roles/:role_id", authMiddleware, orgController.getSingleRole.bind(orgController), ); orgRouter.get( - "/organizations/:org_id/roles", + "/organisations/:org_id/roles", authMiddleware, orgController.getAllOrganizationRoles.bind(orgController), ); orgRouter.put( - "/organizations/:org_id/roles/:role_id/permissions", + "/organisations/:org_id/roles/:role_id/permissions", authMiddleware, checkPermissions([UserRole.ADMIN]), orgController.updateOrganizationRolePermissions.bind(orgController), ); +orgRouter.get( + "/organisations/:org_id/products", + authMiddleware, + orgController.getAllOrgProducts, +); + export { orgRouter }; diff --git a/src/routes/product.ts b/src/routes/product.ts index a0a03110..2693791d 100644 --- a/src/routes/product.ts +++ b/src/routes/product.ts @@ -8,7 +8,6 @@ import { adminOnly } from "../middleware"; const productRouter = Router(); const productController = new ProductController(); -// route productRouter.post( "/organizations/:org_id/products", validateProductDetails, diff --git a/src/services/org.services.ts b/src/services/org.services.ts index f708bb06..e14d36d7 100644 --- a/src/services/org.services.ts +++ b/src/services/org.services.ts @@ -11,7 +11,7 @@ import { } from "../middleware"; import { Organization, Invitation, UserOrganization } from "../models"; import { OrganizationRole } from "../models/organization-role.entity"; -import { User } from "../models/user"; +import { User, Product } from "../models"; import { ICreateOrganisation, ICreateOrgRole, IOrgService } from "../types"; import log from "../utils/logger"; @@ -528,4 +528,17 @@ export class OrgService implements IOrgService { throw error; } } + + public getAllOrgProducts = async (org_id: string) => { + try { + const productRepository = AppDataSource.getRepository(Product); + const products = await productRepository.find({ + where: { org: { id: org_id } }, + order: { created_at: "DESC" }, + }); + return products; + } catch (error) { + throw new Error("Unable to retrieve products. Please try again later."); + } + }; } diff --git a/src/test/organisation.spec.ts b/src/test/organisation.spec.ts index 2a95145f..2dc880c1 100644 --- a/src/test/organisation.spec.ts +++ b/src/test/organisation.spec.ts @@ -12,6 +12,7 @@ import { import { validateOrgId } from "../middleware/organizationValidation"; import { Organization, OrganizationRole, User } from "../models"; import { OrgService } from "../services"; +import isSuperAdmin from "../utils/isSuperAdmin"; jest.mock("../data-source", () => ({ __esModule: true, @@ -29,6 +30,8 @@ jest.mock("passport-google-oauth2", () => ({ Strategy: jest.fn(), })); +jest.mock("../utils/isSuperAdmin"); + describe("Organization Controller and Middleware", () => { let organizationService: OrgService; let orgController: OrgController; @@ -351,4 +354,81 @@ describe("Update User Organization", () => { expect(mockRepository.update).not.toHaveBeenCalled(); }); + + describe("OrganizationController - getAllOrgProducts", () => { + let orgController: OrgController; + let mockRequest: Partial; + let mockResponse: Partial; + let mockNext: NextFunction; + let orgServiceMock: jest.Mocked; + + beforeEach(() => { + jest.clearAllMocks(); + + orgServiceMock = { + getAllOrgProducts: jest.fn(), + } as any; + + orgController = new OrgController(); + mockRequest = { + params: { org_id: "mock-org-id" }, + user: { id: "mock-user-id" }, + }; + mockResponse = { status: jest.fn().mockReturnThis(), json: jest.fn() }; + mockNext = jest.fn(); + }); + + it("should return 401 if user is not authenticated", async () => { + mockRequest.user = null; // Simulate unauthenticated user + + await orgController.getAllOrgProducts( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); + + expect(mockResponse.status).toHaveBeenCalledWith(401); + expect(mockResponse.json).toHaveBeenCalledWith({ + status_code: 401, + success: false, + message: "User not authenticated", + }); + }); + + it("should return 403 if user is not authorized", async () => { + jest.mocked(isSuperAdmin).mockResolvedValue(false); // Simulate user is not a super admin + + await orgController.getAllOrgProducts( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); + + expect(mockResponse.status).toHaveBeenCalledWith(403); + expect(mockResponse.json).toHaveBeenCalledWith({ + status_code: 403, + success: false, + message: "User is not authorized to fetch all products", + }); + }); + + it("should return 500 if an error occurs", async () => { + jest.mocked(isSuperAdmin).mockResolvedValue(true); + orgServiceMock.getAllOrgProducts.mockRejectedValue( + new Error("Database error"), + ); + + await orgController.getAllOrgProducts( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); + + expect(mockResponse.status).toHaveBeenCalledWith(500); + expect(mockResponse.json).toHaveBeenCalledWith({ + status_code: 500, + message: "Unable to retrieve products. Please try again later.", + }); + }); + }); });