diff --git a/README.md b/README.md index cfe8f0b..ccdf2a7 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,19 @@ npm run db:seed --- +## 🔌 Supabase Integration + +This project uses Supabase for external data access and future integrations. + +Update your `.env` file with: + +```bash +SUPABASE_URL=... +SUPABASE_ANON_KEY=... +``` + +--- + ## 📁 Module Overview ### Core Modules diff --git a/src/modules/auth/__tests__/controllers/AuthController.int.test.ts b/src/modules/auth/__tests__/controllers/AuthController.int.test.ts index 39b48e8..86c3b89 100644 --- a/src/modules/auth/__tests__/controllers/AuthController.int.test.ts +++ b/src/modules/auth/__tests__/controllers/AuthController.int.test.ts @@ -1,10 +1,19 @@ import { Request, Response } from "express"; -// Integration test for AuthController -import AuthController from "../../presentation/controllers/Auth.controller"; +import { AuthModuleFactory } from "../../factories/auth.factory"; describe("AuthController Integration", () => { - it("should have a register method", () => { - expect(typeof AuthController.register).toBe("function"); + let authUseCases: ReturnType; + + beforeEach(() => { + authUseCases = AuthModuleFactory.createAuthController(); + }); + + it("should create auth use cases with all required dependencies", () => { + expect(authUseCases.sendVerificationEmailUseCase).toBeDefined(); + expect(authUseCases.resendVerificationEmailUseCase).toBeDefined(); + expect(authUseCases.verifyEmailUseCase).toBeDefined(); + expect(authUseCases.validateWalletFormatUseCase).toBeDefined(); + expect(authUseCases.verifyWalletUseCase).toBeDefined(); }); test("should return error if required fields are missing on register", async () => { @@ -13,7 +22,11 @@ describe("AuthController Integration", () => { status: jest.fn().mockReturnThis(), json: jest.fn(), } as Partial; - await AuthController.register(req as Request, res as Response); + + // Import the controller functions directly since they use the factory + const { register } = await import("../../presentation/controllers/Auth.controller"); + await register(req as Request, res as Response); + expect(res.status).toHaveBeenCalledWith(400); expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ message: expect.any(String) }) diff --git a/src/modules/auth/__tests__/factories/auth.factory.test.ts b/src/modules/auth/__tests__/factories/auth.factory.test.ts new file mode 100644 index 0000000..fbfa685 --- /dev/null +++ b/src/modules/auth/__tests__/factories/auth.factory.test.ts @@ -0,0 +1,85 @@ +import { AuthModuleFactory } from "../../factories/auth.factory"; +import { PrismaUserRepository } from "../../../user/repositories/PrismaUserRepository"; +import { SendVerificationEmailUseCase } from "../../use-cases/send-verification-email.usecase"; +import { ResendVerificationEmailUseCase } from "../../use-cases/resend-verification-email.usecase"; +import { VerifyEmailUseCase } from "../../use-cases/verify-email.usecase"; +import { ValidateWalletFormatUseCase } from "../../use-cases/wallet-format-validation.usecase"; +import { VerifyWalletUseCase } from "../../use-cases/verify-wallet.usecase"; + +// Mock the dependencies +jest.mock("../../../user/repositories/PrismaUserRepository"); +jest.mock("../../use-cases/send-verification-email.usecase"); +jest.mock("../../use-cases/resend-verification-email.usecase"); +jest.mock("../../use-cases/verify-email.usecase"); +jest.mock("../../use-cases/wallet-format-validation.usecase"); +jest.mock("../../use-cases/verify-wallet.usecase"); + +describe("AuthModuleFactory", () => { + let mockUserRepository: jest.Mocked; + let mockSendVerificationEmailUseCase: jest.Mocked; + let mockResendVerificationEmailUseCase: jest.Mocked; + let mockVerifyEmailUseCase: jest.Mocked; + let mockValidateWalletFormatUseCase: jest.Mocked; + let mockVerifyWalletUseCase: jest.Mocked; + + beforeEach(() => { + jest.clearAllMocks(); + + // Create mock instances + mockUserRepository = new PrismaUserRepository() as jest.Mocked; + mockSendVerificationEmailUseCase = new SendVerificationEmailUseCase(mockUserRepository) as jest.Mocked; + mockResendVerificationEmailUseCase = new ResendVerificationEmailUseCase(mockUserRepository) as jest.Mocked; + mockVerifyEmailUseCase = new VerifyEmailUseCase(mockUserRepository) as jest.Mocked; + mockValidateWalletFormatUseCase = new ValidateWalletFormatUseCase() as jest.Mocked; + mockVerifyWalletUseCase = new VerifyWalletUseCase() as jest.Mocked; + + // Mock the constructors + (PrismaUserRepository as jest.Mock).mockImplementation(() => mockUserRepository); + (SendVerificationEmailUseCase as jest.Mock).mockImplementation(() => mockSendVerificationEmailUseCase); + (ResendVerificationEmailUseCase as jest.Mock).mockImplementation(() => mockResendVerificationEmailUseCase); + (VerifyEmailUseCase as jest.Mock).mockImplementation(() => mockVerifyEmailUseCase); + (ValidateWalletFormatUseCase as jest.Mock).mockImplementation(() => mockValidateWalletFormatUseCase); + (VerifyWalletUseCase as jest.Mock).mockImplementation(() => mockVerifyWalletUseCase); + }); + + describe("createAuthController", () => { + it("should create all required use cases with proper dependencies", () => { + const authUseCases = AuthModuleFactory.createAuthController(); + + // Verify all use cases are created + expect(authUseCases.sendVerificationEmailUseCase).toBeDefined(); + expect(authUseCases.resendVerificationEmailUseCase).toBeDefined(); + expect(authUseCases.verifyEmailUseCase).toBeDefined(); + expect(authUseCases.validateWalletFormatUseCase).toBeDefined(); + expect(authUseCases.verifyWalletUseCase).toBeDefined(); + + // Verify constructors were called with correct dependencies + expect(PrismaUserRepository).toHaveBeenCalledTimes(1); + expect(SendVerificationEmailUseCase).toHaveBeenCalledWith(mockUserRepository); + expect(ResendVerificationEmailUseCase).toHaveBeenCalledWith(mockUserRepository); + expect(VerifyEmailUseCase).toHaveBeenCalledWith(mockUserRepository); + expect(ValidateWalletFormatUseCase).toHaveBeenCalledTimes(1); + expect(VerifyWalletUseCase).toHaveBeenCalledTimes(1); + }); + + it("should return different instances on each call", () => { + const firstInstance = AuthModuleFactory.createAuthController(); + const secondInstance = AuthModuleFactory.createAuthController(); + + expect(firstInstance).not.toBe(secondInstance); + expect(firstInstance.sendVerificationEmailUseCase).not.toBe(secondInstance.sendVerificationEmailUseCase); + }); + + it("should create use cases with the same user repository instance", () => { + const authUseCases = AuthModuleFactory.createAuthController(); + + // All use cases that depend on user repository should use the same instance + expect(authUseCases.sendVerificationEmailUseCase).toBeDefined(); + expect(authUseCases.resendVerificationEmailUseCase).toBeDefined(); + expect(authUseCases.verifyEmailUseCase).toBeDefined(); + + // Verify the user repository was created only once + expect(PrismaUserRepository).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/src/modules/auth/factories/auth.factory.ts b/src/modules/auth/factories/auth.factory.ts new file mode 100644 index 0000000..80df469 --- /dev/null +++ b/src/modules/auth/factories/auth.factory.ts @@ -0,0 +1,25 @@ +import { PrismaUserRepository } from "../../user/repositories/PrismaUserRepository"; +import { SendVerificationEmailUseCase } from "../use-cases/send-verification-email.usecase"; +import { ResendVerificationEmailUseCase } from "../use-cases/resend-verification-email.usecase"; +import { VerifyEmailUseCase } from "../use-cases/verify-email.usecase"; +import { ValidateWalletFormatUseCase } from "../use-cases/wallet-format-validation.usecase"; +import { VerifyWalletUseCase } from "../use-cases/verify-wallet.usecase"; + +export class AuthModuleFactory { + static createAuthController() { + const userRepository = new PrismaUserRepository(); + const sendVerificationEmailUseCase = new SendVerificationEmailUseCase(userRepository); + const resendVerificationEmailUseCase = new ResendVerificationEmailUseCase(userRepository); + const verifyEmailUseCase = new VerifyEmailUseCase(userRepository); + const validateWalletFormatUseCase = new ValidateWalletFormatUseCase(); + const verifyWalletUseCase = new VerifyWalletUseCase(); + + return { + sendVerificationEmailUseCase, + resendVerificationEmailUseCase, + verifyEmailUseCase, + validateWalletFormatUseCase, + verifyWalletUseCase, + }; + } +} diff --git a/src/modules/auth/presentation/controllers/Auth.controller.ts b/src/modules/auth/presentation/controllers/Auth.controller.ts index 1ba7a6d..e083386 100644 --- a/src/modules/auth/presentation/controllers/Auth.controller.ts +++ b/src/modules/auth/presentation/controllers/Auth.controller.ts @@ -14,23 +14,15 @@ import { } from "../../dto/wallet-validation.dto"; // Use cases -import { PrismaUserRepository } from "../../../user/repositories/PrismaUserRepository"; -import { SendVerificationEmailUseCase } from "../../use-cases/send-verification-email.usecase"; -import { ResendVerificationEmailUseCase } from "../../use-cases/resend-verification-email.usecase"; -import { VerifyEmailUseCase } from "../../use-cases/verify-email.usecase"; -import { ValidateWalletFormatUseCase } from "../../use-cases/wallet-format-validation.usecase"; -import { VerifyWalletUseCase } from "../../use-cases/verify-wallet.usecase"; - -const userRepository = new PrismaUserRepository(); -const sendVerificationEmailUseCase = new SendVerificationEmailUseCase( - userRepository -); -const resendVerificationEmailUseCase = new ResendVerificationEmailUseCase( - userRepository -); -const verifyEmailUseCase = new VerifyEmailUseCase(userRepository); -const validateWalletFormatUseCase = new ValidateWalletFormatUseCase(); -const verifyWalletUseCase = new VerifyWalletUseCase(); +import { AuthModuleFactory } from "../../factories/auth.factory"; + +const { + sendVerificationEmailUseCase, + resendVerificationEmailUseCase, + verifyEmailUseCase, + validateWalletFormatUseCase, + verifyWalletUseCase, +} = AuthModuleFactory.createAuthController(); // DTO validator async function validateOr400( diff --git a/src/modules/certificate/__tests__/factories/certificate.factory.test.ts b/src/modules/certificate/__tests__/factories/certificate.factory.test.ts new file mode 100644 index 0000000..e674e26 --- /dev/null +++ b/src/modules/certificate/__tests__/factories/certificate.factory.test.ts @@ -0,0 +1,43 @@ +import { CertificateModuleFactory } from "../../factories/certificate.factory"; +import { CertificateService } from "../../application/services/CertificateService"; +import { ICertificateService } from "../../domain/interfaces/ICertificateService"; + +// Mock the CertificateService +jest.mock("../../application/services/CertificateService"); + +describe("CertificateModuleFactory", () => { + let mockCertificateService: jest.Mocked; + + beforeEach(() => { + jest.clearAllMocks(); + mockCertificateService = new CertificateService() as jest.Mocked; + (CertificateService as jest.Mock).mockImplementation(() => mockCertificateService); + }); + + describe("createCertificateService", () => { + it("should create a certificate service instance", () => { + const certificateService = CertificateModuleFactory.createCertificateService(); + + expect(certificateService).toBeDefined(); + expect(CertificateService).toHaveBeenCalledTimes(1); + }); + + it("should return an instance that implements ICertificateService", () => { + const certificateService = CertificateModuleFactory.createCertificateService(); + + expect(certificateService).toBeInstanceOf(CertificateService); + expect(typeof certificateService.createCertificate).toBe("function"); + expect(typeof certificateService.getCertificateUrl).toBe("function"); + expect(typeof certificateService.getCertificateBuffer).toBe("function"); + expect(typeof certificateService.logDownload).toBe("function"); + }); + + it("should return different instances on each call", () => { + const firstInstance = CertificateModuleFactory.createCertificateService(); + const secondInstance = CertificateModuleFactory.createCertificateService(); + + expect(firstInstance).not.toBe(secondInstance); + expect(CertificateService).toHaveBeenCalledTimes(2); + }); + }); +}); diff --git a/src/modules/certificate/factories/certificate.factory.ts b/src/modules/certificate/factories/certificate.factory.ts new file mode 100644 index 0000000..c0905e6 --- /dev/null +++ b/src/modules/certificate/factories/certificate.factory.ts @@ -0,0 +1,8 @@ +import { CertificateService } from "../application/services/CertificateService"; +import { ICertificateService } from "../domain/interfaces/ICertificateService"; + +export class CertificateModuleFactory { + static createCertificateService(): ICertificateService { + return new CertificateService(); + } +} diff --git a/src/modules/certificate/presentation/controllers/certificate.controller.ts b/src/modules/certificate/presentation/controllers/certificate.controller.ts index f944ba4..ed49837 100644 --- a/src/modules/certificate/presentation/controllers/certificate.controller.ts +++ b/src/modules/certificate/presentation/controllers/certificate.controller.ts @@ -1,10 +1,10 @@ -import { container } from "../../../../shared/infrastructure/container"; import { prisma } from "../../../../config/prisma"; import { ICertificateService } from "../../domain/interfaces/ICertificateService"; +import { CertificateModuleFactory } from "../../factories/certificate.factory"; import { AuthenticatedRequest } from "../../../../types/auth.types"; import { Response } from "express"; -const certificateService: ICertificateService = container.certificateService; +const certificateService: ICertificateService = CertificateModuleFactory.createCertificateService(); export const downloadCertificate = async ( req: AuthenticatedRequest, diff --git a/src/modules/messaging/__tests__/factories/messaging.factory.test.ts b/src/modules/messaging/__tests__/factories/messaging.factory.test.ts new file mode 100644 index 0000000..876f41e --- /dev/null +++ b/src/modules/messaging/__tests__/factories/messaging.factory.test.ts @@ -0,0 +1,66 @@ +import { MessagingModuleFactory } from "../../factories/messaging.factory"; +import { MessagingService } from "../../application/services/MessagingService"; +import { MessagingController } from "../../controllers/MessagingController"; +import { MessagePrismaRepository } from "../../repositories/implementations/message-prisma.repository"; +import { PrismaClient } from "@prisma/client"; + +// Mock the dependencies +jest.mock("../../application/services/MessagingService"); +jest.mock("../../controllers/MessagingController"); +jest.mock("../../repositories/implementations/message-prisma.repository"); +jest.mock("@prisma/client"); + +describe("MessagingModuleFactory", () => { + let mockPrisma: jest.Mocked; + let mockMessageRepository: jest.Mocked; + let mockMessagingService: jest.Mocked; + let mockMessagingController: jest.Mocked; + + beforeEach(() => { + jest.clearAllMocks(); + + // Create mock instances + mockPrisma = new PrismaClient() as jest.Mocked; + mockMessageRepository = new MessagePrismaRepository(mockPrisma) as jest.Mocked; + mockMessagingService = new MessagingService(mockMessageRepository, mockPrisma) as jest.Mocked; + mockMessagingController = new MessagingController(mockMessagingService) as jest.Mocked; + + // Mock the constructors + (PrismaClient as jest.Mock).mockImplementation(() => mockPrisma); + (MessagePrismaRepository as jest.Mock).mockImplementation(() => mockMessageRepository); + (MessagingService as jest.Mock).mockImplementation(() => mockMessagingService); + (MessagingController as jest.Mock).mockImplementation(() => mockMessagingController); + }); + + describe("createMessagingController", () => { + it("should create a messaging controller with all required dependencies", () => { + const messagingController = MessagingModuleFactory.createMessagingController(); + + expect(messagingController).toBeDefined(); + expect(messagingController).toBeInstanceOf(MessagingController); + + // Verify constructors were called with correct dependencies + expect(PrismaClient).toHaveBeenCalledTimes(1); + expect(MessagePrismaRepository).toHaveBeenCalledWith(mockPrisma); + expect(MessagingService).toHaveBeenCalledWith(mockMessageRepository, mockPrisma); + expect(MessagingController).toHaveBeenCalledWith(mockMessagingService); + }); + + it("should return different instances on each call", () => { + const firstInstance = MessagingModuleFactory.createMessagingController(); + const secondInstance = MessagingModuleFactory.createMessagingController(); + + expect(firstInstance).not.toBe(secondInstance); + expect(MessagingController).toHaveBeenCalledTimes(2); + }); + + it("should create dependencies with proper wiring", () => { + const messagingController = MessagingModuleFactory.createMessagingController(); + + // Verify the controller has the expected methods + expect(typeof messagingController.sendMessage).toBe("function"); + expect(typeof messagingController.getConversation).toBe("function"); + expect(typeof messagingController.markAsRead).toBe("function"); + }); + }); +}); diff --git a/src/modules/messaging/factories/messaging.factory.ts b/src/modules/messaging/factories/messaging.factory.ts new file mode 100644 index 0000000..6d60f43 --- /dev/null +++ b/src/modules/messaging/factories/messaging.factory.ts @@ -0,0 +1,13 @@ +import { MessagingService } from "../application/services/MessagingService"; +import { MessagingController } from "../controllers/MessagingController"; +import { MessagePrismaRepository } from "../repositories/implementations/message-prisma.repository"; +import { PrismaClient } from "@prisma/client"; + +export class MessagingModuleFactory { + static createMessagingController(): MessagingController { + const prisma = new PrismaClient(); + const messageRepository = new MessagePrismaRepository(prisma); + const messagingService = new MessagingService(messageRepository, prisma); + return new MessagingController(messagingService); + } +} diff --git a/src/modules/messaging/routes/messaging.routes.ts b/src/modules/messaging/routes/messaging.routes.ts index eea7f32..8a063ae 100644 --- a/src/modules/messaging/routes/messaging.routes.ts +++ b/src/modules/messaging/routes/messaging.routes.ts @@ -1,16 +1,10 @@ import { Router } from "express"; -import { MessagingController } from "../controllers/MessagingController"; -import { MessagingService } from "../application/services/MessagingService"; -import { MessagePrismaRepository } from "../repositories/implementations/message-prisma.repository"; +import { MessagingModuleFactory } from "../factories/messaging.factory"; import { authMiddleware } from "../../../middleware/authMiddleware"; -import { PrismaClient } from "@prisma/client"; const router = Router(); -const prisma = new PrismaClient(); -const messageRepository = new MessagePrismaRepository(prisma); -const messagingService = new MessagingService(messageRepository, prisma); -const messagingController = new MessagingController(messagingService); +const messagingController = MessagingModuleFactory.createMessagingController(); router.use(authMiddleware); diff --git a/src/modules/wallet/factories/wallet.factory.ts b/src/modules/wallet/factories/wallet.factory.ts new file mode 100644 index 0000000..7be944e --- /dev/null +++ b/src/modules/wallet/factories/wallet.factory.ts @@ -0,0 +1,19 @@ +import { WalletService } from "../application/services/WalletService"; +import { HorizonWalletRepository } from "../repositories/HorizonWalletRepository"; +import { VerifyWalletUseCase } from "../use-cases/VerifyWalletUseCase"; +import { ValidateWalletFormatUseCase } from "../use-cases/ValidateWalletFormatUseCase"; + +export class WalletModuleFactory { + static createWalletService(): WalletService { + const walletRepository = new HorizonWalletRepository(); + const verifyWalletUseCase = new VerifyWalletUseCase(walletRepository); + const validateWalletFormatUseCase = new ValidateWalletFormatUseCase(); + + const walletService = new WalletService(); + (walletService as any).walletRepository = walletRepository; + (walletService as any).verifyWalletUseCase = verifyWalletUseCase; + (walletService as any).validateWalletFormatUseCase = validateWalletFormatUseCase; + + return walletService; + } +} diff --git a/src/shared/infrastructure/container.ts b/src/shared/infrastructure/container.ts deleted file mode 100644 index f31aa07..0000000 --- a/src/shared/infrastructure/container.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { CertificateService } from "../../modules/certificate/application/services/CertificateService"; -import { ICertificateService } from "../../modules/certificate/domain/interfaces/ICertificateService"; - -export const container = { - certificateService: new CertificateService() as ICertificateService, -};