├── api-gateway ├── .gitignore ├── .env.example ├── src │ ├── utils │ │ ├── http-methods.js │ │ ├── app-roles.js │ │ └── env-config.js │ ├── routes │ │ ├── auth.routes.js │ │ ├── carts.routes.js │ │ ├── products.routes.js │ │ └── orders.routes.js │ └── middlewares │ │ ├── check-authorization.js │ │ ├── check-allowed-routes.js │ │ └── jwt-auth.js ├── docker-compose.yml ├── package.json └── Dockerfile ├── carts ├── .prettierrc ├── carts-service.png ├── tsconfig.build.json ├── .env.example ├── nest-cli.json ├── src │ ├── core │ │ ├── products │ │ │ ├── services │ │ │ │ ├── product-service.interface.ts │ │ │ │ └── implementations │ │ │ │ │ └── product.service.ts │ │ │ ├── dtos │ │ │ │ └── product.dto.ts │ │ │ └── products.module.ts │ │ └── carts │ │ │ ├── services │ │ │ ├── cart-service.interface.ts │ │ │ └── implementations │ │ │ │ └── cart.service.ts │ │ │ ├── usecases │ │ │ ├── delete-cart-by-id.usecase.ts │ │ │ ├── find-user-cart.usecase.ts │ │ │ ├── find-cart-by-user-id.usecase.ts │ │ │ └── clear-cart.usecase.ts │ │ │ ├── factories │ │ │ ├── cart.factory.ts │ │ │ └── cart-product.factory.ts │ │ │ ├── dtos │ │ │ ├── cart-product.dto.ts │ │ │ └── cart.dto.ts │ │ │ ├── mappers │ │ │ ├── cart-product.mapper.ts │ │ │ └── cart.mapper.ts │ │ │ ├── entities │ │ │ ├── cart-product.entity.ts │ │ │ └── cart.entity.ts │ │ │ └── carts.module.ts │ ├── infra │ │ ├── env-config │ │ │ ├── services │ │ │ │ ├── env-config-service.interface.ts │ │ │ │ └── implementations │ │ │ │ │ └── env-config.service.ts │ │ │ └── env-config.module.ts │ │ └── database │ │ │ └── database.module.ts │ ├── app.module.ts │ ├── main.ts │ └── common │ │ └── filters │ │ └── axios-exception.filter.ts ├── jest.config.ts ├── jest.unit.config.ts ├── jest.e2e.config.ts ├── jest.int.config.ts ├── Dockerfile ├── docker-compose.yml ├── tsconfig.json ├── docker-compose.dev.yml ├── .gitignore └── .eslintrc.js ├── orders ├── .prettierrc ├── payment-flow.png ├── orders-service.png ├── tsconfig.build.json ├── src │ ├── core │ │ ├── orders │ │ │ ├── enums │ │ │ │ ├── payment-method.enum.ts │ │ │ │ └── order-status.enum.ts │ │ │ ├── mappers │ │ │ │ ├── payment.mapper.ts │ │ │ │ ├── order.mapper.ts │ │ │ │ ├── cart-product.mapper.ts │ │ │ │ └── order-cart.mapper.ts │ │ │ ├── usecases │ │ │ │ ├── find-order-by-id.usecase.ts │ │ │ │ ├── find-all-orders.usecase.ts │ │ │ │ ├── find-all-user-orders.usecase.ts │ │ │ │ ├── cancel-order.usecase.ts │ │ │ │ └── update-order-after-unsuccessful-payment.usecase.ts │ │ │ ├── dtos │ │ │ │ ├── payment.dto.ts │ │ │ │ ├── order.dto.ts │ │ │ │ └── pay-order-with-credit-card.dto.ts │ │ │ ├── entities │ │ │ │ ├── cart-product.entity.ts │ │ │ │ ├── order-cart.entity.ts │ │ │ │ ├── payment.entity.ts │ │ │ │ └── order.entity.ts │ │ │ ├── services │ │ │ │ └── order-service.interface.ts │ │ │ ├── factories │ │ │ │ ├── order.factory.ts │ │ │ │ ├── order-cart.factory.ts │ │ │ │ └── payment.factory.ts │ │ │ └── orders.module.ts │ │ ├── users │ │ │ ├── services │ │ │ │ ├── user-service.interface.ts │ │ │ │ └── implementations │ │ │ │ │ └── user.service.ts │ │ │ ├── dtos │ │ │ │ └── user.dto.ts │ │ │ └── users.module.ts │ │ └── carts │ │ │ ├── services │ │ │ ├── cart-service.interface.ts │ │ │ └── implementations │ │ │ │ └── cart.service.ts │ │ │ ├── dtos │ │ │ ├── cart-product.dto.ts │ │ │ └── cart.dto.ts │ │ │ └── carts.module.ts │ ├── common │ │ ├── @types │ │ │ └── pagination.ts │ │ └── filters │ │ │ └── axios-exception.filter.ts │ ├── infra │ │ ├── env-config │ │ │ ├── services │ │ │ │ ├── env-config-service.interface.ts │ │ │ │ └── implementations │ │ │ │ │ └── env-config.service.ts │ │ │ └── env-config.module.ts │ │ ├── rabbitmq │ │ │ ├── services │ │ │ │ ├── rabbitmq-service.interface.ts │ │ │ │ ├── implementations │ │ │ │ │ ├── payments-queue.service.ts │ │ │ │ │ └── order-status-event-queue.service.ts │ │ │ │ ├── payments-queue-service.interface.ts │ │ │ │ └── order-status-event-queue-service.interface.ts │ │ │ └── rabbitmq.module.ts │ │ └── database │ │ │ └── database.module.ts │ ├── app.module.ts │ └── main.ts ├── .env.example ├── nest-cli.json ├── jest.config.ts ├── jest.e2e.config.ts ├── jest.int.config.ts ├── jest.unit.config.ts ├── Dockerfile ├── docker-compose.yml ├── tsconfig.json ├── docker-compose.dev.yml ├── .gitignore └── .eslintrc.js ├── payments ├── .prettierrc ├── payments-service.png ├── .env.example ├── tsconfig.build.json ├── docker-compose.yml ├── nest-cli.json ├── src │ ├── core │ │ ├── products │ │ │ ├── services │ │ │ │ ├── product-service.interface.ts │ │ │ │ └── implementations │ │ │ │ │ └── product.service.ts │ │ │ └── products.module.ts │ │ └── payments │ │ │ ├── services │ │ │ └── payment-service.interface.ts │ │ │ └── payments.module.ts │ ├── infra │ │ ├── env-config │ │ │ ├── services │ │ │ │ ├── env-config-service.interface.ts │ │ │ │ └── implementations │ │ │ │ │ └── env-config.service.ts │ │ │ └── env-config.module.ts │ │ └── rabbitmq │ │ │ ├── services │ │ │ ├── rabbitmq-service.interface.ts │ │ │ ├── implementations │ │ │ │ ├── payments-queue.service.ts │ │ │ │ └── order-status-event-queue.service.ts │ │ │ ├── payments-queue-service.interface.ts │ │ │ └── order-status-event-queue-service.interface.ts │ │ │ └── rabbitmq.module.ts │ ├── app.module.ts │ └── main.ts ├── Dockerfile ├── jest.config.ts ├── jest.unit.config.ts ├── jest.e2e.config.ts ├── jest.int.config.ts ├── tsconfig.json ├── .gitignore └── .eslintrc.js ├── products ├── .prettierrc ├── products-service.png ├── tsconfig.build.json ├── .env.example ├── nest-cli.json ├── src │ ├── core │ │ └── products │ │ │ ├── dtos │ │ │ ├── cart-product.dto.ts │ │ │ ├── create-product.dto.ts │ │ │ ├── update-product.dto.ts │ │ │ ├── check-products-stock-availability.dto.ts │ │ │ └── product.dto.ts │ │ │ ├── usecases │ │ │ ├── delete-product.usecase.ts │ │ │ ├── find-product-by-id.usecase.ts │ │ │ ├── find-all-products.usecase.ts │ │ │ ├── create-product.usecase.ts │ │ │ ├── update-product.usecase.ts │ │ │ └── check-products-stock-availability.usecase.ts │ │ │ ├── entities │ │ │ └── product.entity.ts │ │ │ ├── mappers │ │ │ └── product.mapper.ts │ │ │ ├── factories │ │ │ └── product.factory.ts │ │ │ ├── services │ │ │ └── product-service.interface.ts │ │ │ └── products.module.ts │ ├── infra │ │ ├── env-config │ │ │ ├── services │ │ │ │ ├── env-config-service.interface.ts │ │ │ │ └── implementations │ │ │ │ │ └── env-config.service.ts │ │ │ └── env-config.module.ts │ │ ├── rabbitmq │ │ │ ├── services │ │ │ │ ├── rabbitmq-service.interface.ts │ │ │ │ ├── implementations │ │ │ │ │ └── order-status-event-queue.service.ts │ │ │ │ └── order-status-event-queue-service.interface.ts │ │ │ └── rabbitmq.module.ts │ │ └── database │ │ │ └── database.module.ts │ ├── common │ │ └── @types │ │ │ └── pagination.ts │ ├── app.module.ts │ └── main.ts ├── jest.config.ts ├── jest.unit.config.ts ├── Dockerfile ├── jest.e2e.config.ts ├── jest.int.config.ts ├── docker-compose.yml ├── tsconfig.json ├── docker-compose.dev.yml ├── .gitignore ├── .eslintrc.js └── README.md ├── users ├── .prettierrc ├── src │ ├── core │ │ ├── auth │ │ │ ├── dtos │ │ │ │ ├── token.dto.ts │ │ │ │ ├── login.dto.ts │ │ │ │ └── signup.dto.ts │ │ │ ├── services │ │ │ │ ├── hash-service.interface.ts │ │ │ │ ├── jwt-service.interface.ts │ │ │ │ └── implementations │ │ │ │ │ ├── hash.service.ts │ │ │ │ │ └── jwt.service.ts │ │ │ ├── usecases │ │ │ │ ├── signup.usecase.ts │ │ │ │ └── login.usecase.ts │ │ │ ├── controllers │ │ │ │ └── auth.controller.ts │ │ │ └── auth.module.ts │ │ └── users │ │ │ ├── enums │ │ │ └── role.enum.ts │ │ │ ├── dtos │ │ │ └── user.dto.ts │ │ │ ├── services │ │ │ ├── user-service.interface.ts │ │ │ └── implementations │ │ │ │ └── user.service.ts │ │ │ ├── mappers │ │ │ └── user.mapper.ts │ │ │ ├── usecases │ │ │ └── find-user-by-id.usecase.ts │ │ │ ├── factories │ │ │ └── users.factory.ts │ │ │ ├── controllers │ │ │ └── users.controller.ts │ │ │ ├── entities │ │ │ └── user.entity.ts │ │ │ └── users.module.ts │ ├── infra │ │ ├── env-config │ │ │ ├── services │ │ │ │ ├── env-config-service.interface.ts │ │ │ │ └── implementations │ │ │ │ │ └── env-config.service.ts │ │ │ └── env-config.module.ts │ │ └── database │ │ │ └── database.module.ts │ ├── app.module.ts │ └── main.ts ├── users-service.png ├── tsconfig.build.json ├── .env.example ├── nest-cli.json ├── jest.config.ts ├── jest.unit.config.ts ├── jest.e2e.config.ts ├── jest.int.config.ts ├── Dockerfile ├── docker-compose.yml ├── tsconfig.json ├── docker-compose.dev.yml ├── .gitignore ├── .eslintrc.js └── README.md ├── notifications ├── .prettierrc ├── .env.example ├── notification-service.png ├── tsconfig.build.json ├── docker-compose.yml ├── nest-cli.json ├── src │ ├── infra │ │ ├── template-engine │ │ │ ├── services │ │ │ │ ├── template-engine-service.interface.ts │ │ │ │ └── implementations │ │ │ │ │ └── template-engine.service.ts │ │ │ └── template-engine.module.ts │ │ ├── env-config │ │ │ ├── services │ │ │ │ ├── env-config-service.interface.ts │ │ │ │ └── implementations │ │ │ │ │ └── env-config.service.ts │ │ │ └── env-config.module.ts │ │ ├── mail │ │ │ ├── services │ │ │ │ ├── mail-service.interface.ts │ │ │ │ └── implementations │ │ │ │ │ └── mail.service.ts │ │ │ └── mail.module.ts │ │ └── rabbitmq │ │ │ ├── services │ │ │ ├── rabbitmq-service.interface.ts │ │ │ ├── order-status-event-queue-service.interface.ts │ │ │ └── implementations │ │ │ │ └── order-status-event-queue.service.ts │ │ │ └── rabbitmq.module.ts │ ├── main.ts │ ├── app.module.ts │ └── core │ │ └── notifications │ │ ├── notifications.module.ts │ │ └── usecases │ │ ├── send-email-after-order-creation.usecase.ts │ │ ├── send-email-after-unsuccessful-payment.usecase.ts │ │ └── send-email-after-successful-payment.usecase.ts ├── jest.config.ts ├── jest.unit.config.ts ├── jest.e2e.config.ts ├── jest.int.config.ts ├── Dockerfile ├── tsconfig.json ├── .gitignore ├── .eslintrc.js ├── templates │ ├── order-created.hbs │ ├── order-payment-failed.hbs │ └── order-payment-succeed.hbs └── README.md ├── ecommerce-erd.png ├── soa-ecommerce-backend.png ├── rabbit-mq ├── docker-compose.yml ├── docker-compose.dev.yml └── README.md └── README.md /api-gateway/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /carts/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /orders/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /payments/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /products/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /users/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /notifications/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /users/src/core/auth/dtos/token.dto.ts: -------------------------------------------------------------------------------- 1 | export type TokenDto = { 2 | token: string; 3 | }; 4 | -------------------------------------------------------------------------------- /ecommerce-erd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevVictor19/ecommerce-backend-soa/HEAD/ecommerce-erd.png -------------------------------------------------------------------------------- /carts/carts-service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevVictor19/ecommerce-backend-soa/HEAD/carts/carts-service.png -------------------------------------------------------------------------------- /orders/payment-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevVictor19/ecommerce-backend-soa/HEAD/orders/payment-flow.png -------------------------------------------------------------------------------- /users/src/core/users/enums/role.enum.ts: -------------------------------------------------------------------------------- 1 | export enum ROLE { 2 | CLIENT = 'CLIENT', 3 | ADMIN = 'ADMIN', 4 | } 5 | -------------------------------------------------------------------------------- /users/users-service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevVictor19/ecommerce-backend-soa/HEAD/users/users-service.png -------------------------------------------------------------------------------- /orders/orders-service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevVictor19/ecommerce-backend-soa/HEAD/orders/orders-service.png -------------------------------------------------------------------------------- /soa-ecommerce-backend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevVictor19/ecommerce-backend-soa/HEAD/soa-ecommerce-backend.png -------------------------------------------------------------------------------- /payments/payments-service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevVictor19/ecommerce-backend-soa/HEAD/payments/payments-service.png -------------------------------------------------------------------------------- /products/products-service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevVictor19/ecommerce-backend-soa/HEAD/products/products-service.png -------------------------------------------------------------------------------- /notifications/.env.example: -------------------------------------------------------------------------------- 1 | # Server 2 | RABBIT_MQ_URL= 3 | 4 | # Mail 5 | MAIL_HOST_USER= 6 | MAIL_HOST_NAME= 7 | MAIL_HOST_PASSWORD= -------------------------------------------------------------------------------- /payments/.env.example: -------------------------------------------------------------------------------- 1 | # Server 2 | PRODUCT_SERVICE_URL= 3 | RABBIT_MQ_URL= 4 | 5 | # Gateways 6 | PAYMENT_GW_URL= 7 | PAYMENT_GW_KEY= -------------------------------------------------------------------------------- /carts/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /users/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /orders/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /payments/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /products/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /api-gateway/.env.example: -------------------------------------------------------------------------------- 1 | # server 2 | SERVER_URL= 3 | 4 | # services 5 | USERS_SERVICE= 6 | PRODUCTS_SERVICE= 7 | CARTS_SERVICE= 8 | ORDERS_SERVICE= -------------------------------------------------------------------------------- /notifications/notification-service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevVictor19/ecommerce-backend-soa/HEAD/notifications/notification-service.png -------------------------------------------------------------------------------- /notifications/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /orders/src/core/orders/enums/payment-method.enum.ts: -------------------------------------------------------------------------------- 1 | export enum PAYMENT_METHOD { 2 | DEBIT_CARD = 'DEBIT_CARD', 3 | CREDIT_CARD = 'CREDIT_CARD', 4 | } 5 | -------------------------------------------------------------------------------- /products/.env.example: -------------------------------------------------------------------------------- 1 | # Database 2 | DB_URI= 3 | DB_NAME= 4 | DB_USER= 5 | DB_PASSWORD= 6 | DB_PORT= 7 | 8 | # Server 9 | SERVER_PORT= 10 | RABBIT_MQ_URL= 11 | 12 | -------------------------------------------------------------------------------- /users/.env.example: -------------------------------------------------------------------------------- 1 | # Database 2 | DB_URI= 3 | DB_NAME= 4 | DB_USER= 5 | DB_PASSWORD= 6 | DB_PORT= 7 | 8 | # Server 9 | SERVER_PORT= 10 | SERVER_JWT_SECRET= 11 | 12 | -------------------------------------------------------------------------------- /api-gateway/src/utils/http-methods.js: -------------------------------------------------------------------------------- 1 | const httpMethods = { 2 | get: "GET", 3 | post: "POST", 4 | put: "PUT", 5 | delete: "DELETE", 6 | }; 7 | 8 | module.exports = { httpMethods }; 9 | -------------------------------------------------------------------------------- /payments/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | backend: 3 | container_name: payments-nest-api 4 | restart: always 5 | build: 6 | context: . 7 | env_file: 8 | - .env 9 | -------------------------------------------------------------------------------- /notifications/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | backend: 3 | container_name: notifications-nest-api 4 | restart: always 5 | build: 6 | context: . 7 | env_file: 8 | - .env 9 | -------------------------------------------------------------------------------- /orders/src/core/orders/enums/order-status.enum.ts: -------------------------------------------------------------------------------- 1 | export enum ORDER_STATUS { 2 | WAITING_PAYMENT = 'WAITING_PAYMENT', 3 | PROCESSING_PAYMENT = 'PROCESSING_PAYMENT', 4 | PAID = 'PAID', 5 | } 6 | -------------------------------------------------------------------------------- /carts/.env.example: -------------------------------------------------------------------------------- 1 | # Database 2 | DB_URI= 3 | DB_NAME= 4 | DB_USER= 5 | DB_PASSWORD= 6 | DB_PORT= 7 | 8 | # Server 9 | SERVER_PORT= 10 | 11 | # Services 12 | PRODUCTS_SERVICE_URL= 13 | 14 | 15 | -------------------------------------------------------------------------------- /orders/src/core/users/services/user-service.interface.ts: -------------------------------------------------------------------------------- 1 | import { UserDto } from '../dtos/user.dto'; 2 | 3 | export abstract class UserService { 4 | abstract findById(userId: string): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /carts/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /orders/.env.example: -------------------------------------------------------------------------------- 1 | # Database 2 | DB_URI= 3 | DB_NAME= 4 | DB_USER= 5 | DB_PASSWORD= 6 | DB_PORT= 7 | 8 | # Server 9 | SERVER_PORT= 10 | CART_SERVICE_URL= 11 | USER_SERVICE_URL= 12 | RABBIT_MQ_URL= 13 | 14 | -------------------------------------------------------------------------------- /orders/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /users/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /payments/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /products/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /users/src/core/auth/services/hash-service.interface.ts: -------------------------------------------------------------------------------- 1 | export abstract class HashService { 2 | abstract hash(value: string): Promise; 3 | abstract compare(value: string, encoded: string): Promise; 4 | } 5 | -------------------------------------------------------------------------------- /notifications/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /carts/src/core/products/services/product-service.interface.ts: -------------------------------------------------------------------------------- 1 | import { ProductDto } from '../dtos/product.dto'; 2 | 3 | export abstract class ProductService { 4 | abstract findById(productId: string): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /api-gateway/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | proxy: 3 | container_name: fastify-api-gateway 4 | restart: always 5 | build: 6 | context: . 7 | env_file: 8 | - .env 9 | ports: 10 | - 8080:8080 11 | -------------------------------------------------------------------------------- /products/src/core/products/dtos/cart-product.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsPositive, IsUUID } from 'class-validator'; 2 | 3 | export class CartProductDto { 4 | @IsUUID() 5 | id: string; 6 | 7 | @IsPositive() 8 | inCartQuantity: number; 9 | } 10 | -------------------------------------------------------------------------------- /users/src/core/auth/dtos/login.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail, IsString, Length } from 'class-validator'; 2 | 3 | export class LoginDto { 4 | @IsEmail() 5 | email: string; 6 | 7 | @IsString() 8 | @Length(8, 20) 9 | password: string; 10 | } 11 | -------------------------------------------------------------------------------- /orders/src/core/carts/services/cart-service.interface.ts: -------------------------------------------------------------------------------- 1 | import { CartDto } from '../dtos/cart.dto'; 2 | 3 | export abstract class CartService { 4 | abstract findByUserId(userId: string): Promise; 5 | abstract deleteById(cartId: string): Promise; 6 | } 7 | -------------------------------------------------------------------------------- /notifications/src/infra/template-engine/services/template-engine-service.interface.ts: -------------------------------------------------------------------------------- 1 | export abstract class TemplateEngineService { 2 | abstract compile( 3 | filename: string, 4 | variables?: { [key: string]: string | number | object }, 5 | ): Promise; 6 | } 7 | -------------------------------------------------------------------------------- /payments/src/core/products/services/product-service.interface.ts: -------------------------------------------------------------------------------- 1 | export type CartProductDto = { 2 | id: string; 3 | inCartQuantity: number; 4 | }; 5 | 6 | export abstract class ProductService { 7 | abstract checkStockAvailability(products: CartProductDto[]): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /notifications/src/infra/env-config/services/env-config-service.interface.ts: -------------------------------------------------------------------------------- 1 | export abstract class EnvConfigService { 2 | abstract getRabbitMQUrl(): string; 3 | abstract getMailHostUser(): string; 4 | abstract getMailHostName(): string; 5 | abstract getMailHostPassword(): string; 6 | } 7 | -------------------------------------------------------------------------------- /payments/src/infra/env-config/services/env-config-service.interface.ts: -------------------------------------------------------------------------------- 1 | export abstract class EnvConfigService { 2 | abstract getProductServiceUrl(): string; 3 | abstract getRabbitMQUrl(): string; 4 | abstract getPaymentGatewayUrl(): string; 5 | abstract getPaymentGatewayKey(): string; 6 | } 7 | -------------------------------------------------------------------------------- /users/src/core/auth/dtos/signup.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail, IsString, Length } from 'class-validator'; 2 | 3 | export class SignupDto { 4 | @IsString() 5 | @Length(3, 55) 6 | username: string; 7 | 8 | @IsEmail() 9 | email: string; 10 | 11 | @IsString() 12 | @Length(8, 20) 13 | password: string; 14 | } 15 | -------------------------------------------------------------------------------- /api-gateway/src/utils/app-roles.js: -------------------------------------------------------------------------------- 1 | const appRoles = { 2 | admin: "ADMIN", 3 | client: "CLIENT", 4 | }; 5 | 6 | const addAdminPermission = () => [appRoles.admin]; 7 | const addClientPermission = () => [appRoles.client, appRoles.admin]; 8 | 9 | module.exports = { appRoles, addAdminPermission, addClientPermission }; 10 | -------------------------------------------------------------------------------- /notifications/src/infra/mail/services/mail-service.interface.ts: -------------------------------------------------------------------------------- 1 | export type Address = { 2 | email: string; 3 | name: string; 4 | }; 5 | 6 | export type Message = { 7 | to: Address; 8 | subject: string; 9 | html: string | Buffer; 10 | }; 11 | 12 | export abstract class MailService { 13 | abstract sendMail(message: Message): Promise; 14 | } 15 | -------------------------------------------------------------------------------- /carts/src/core/carts/services/cart-service.interface.ts: -------------------------------------------------------------------------------- 1 | import { Cart } from '../entities/cart.entity'; 2 | 3 | export abstract class CartService { 4 | abstract create(cart: Cart): Promise; 5 | abstract update(cart: Cart): Promise; 6 | abstract findByUserId(userId: string): Promise; 7 | abstract delete(cartId: string): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /users/src/core/auth/services/jwt-service.interface.ts: -------------------------------------------------------------------------------- 1 | import { User } from '@/core/users/entities/user.entity'; 2 | import { ROLE } from '@/core/users/enums/role.enum'; 3 | 4 | export type JwtPayload = { 5 | userId: string; 6 | roles: ROLE[]; 7 | }; 8 | 9 | export abstract class JwtService { 10 | abstract generateToken(user: User): Promise; 11 | } 12 | -------------------------------------------------------------------------------- /orders/src/core/users/dtos/user.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsArray, IsDateString, IsEmail, IsString } from 'class-validator'; 2 | 3 | export class UserDto { 4 | @IsString() 5 | id: string; 6 | 7 | @IsString() 8 | username: string; 9 | 10 | @IsEmail() 11 | email: string; 12 | 13 | @IsArray() 14 | roles: string[]; 15 | 16 | @IsDateString() 17 | createdAt: string; 18 | } 19 | -------------------------------------------------------------------------------- /products/src/infra/env-config/services/env-config-service.interface.ts: -------------------------------------------------------------------------------- 1 | export abstract class EnvConfigService { 2 | abstract getDbUri(): string; 3 | abstract getDbName(): string; 4 | abstract getDbUser(): string; 5 | abstract getDbPassword(): string; 6 | abstract getDbPort(): number; 7 | abstract getServerPort(): number; 8 | abstract getRabbitMQUrl(): string; 9 | } 10 | -------------------------------------------------------------------------------- /users/src/core/users/dtos/user.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsArray, IsDateString, IsEmail, IsString } from 'class-validator'; 2 | 3 | export class UserDto { 4 | @IsString() 5 | id: string; 6 | 7 | @IsString() 8 | username: string; 9 | 10 | @IsEmail() 11 | email: string; 12 | 13 | @IsArray() 14 | roles: string[]; 15 | 16 | @IsDateString() 17 | createdAt: string; 18 | } 19 | -------------------------------------------------------------------------------- /users/src/infra/env-config/services/env-config-service.interface.ts: -------------------------------------------------------------------------------- 1 | export abstract class EnvConfigService { 2 | abstract getDbUri(): string; 3 | abstract getDbName(): string; 4 | abstract getDbUser(): string; 5 | abstract getDbPassword(): string; 6 | abstract getDbPort(): number; 7 | abstract getServerPort(): number; 8 | abstract getServerJwtSecret(): string; 9 | } 10 | -------------------------------------------------------------------------------- /carts/src/infra/env-config/services/env-config-service.interface.ts: -------------------------------------------------------------------------------- 1 | export abstract class EnvConfigService { 2 | abstract getDbUri(): string; 3 | abstract getDbName(): string; 4 | abstract getDbUser(): string; 5 | abstract getDbPassword(): string; 6 | abstract getDbPort(): number; 7 | abstract getServerPort(): number; 8 | abstract getProductsServiceUrl(): string; 9 | } 10 | -------------------------------------------------------------------------------- /notifications/src/infra/mail/mail.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { MailServiceImpl } from './services/implementations/mail.service'; 4 | import { MailService } from './services/mail-service.interface'; 5 | 6 | @Module({ 7 | providers: [{ provide: MailService, useClass: MailServiceImpl }], 8 | exports: [MailService], 9 | }) 10 | export class MailModule {} 11 | -------------------------------------------------------------------------------- /rabbit-mq/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | broker: 3 | container_name: rabbitmq 4 | image: rabbitmq:4 5 | restart: always 6 | environment: 7 | - RABBITMQ_DEFAULT_PASS=admin 8 | - RABBITMQ_DEFAULT_USER=admin 9 | volumes: 10 | - rabbitmq_data:/var/lib/rabbitmq 11 | - rabbitmq_logs:/var/log/rabbitmq 12 | 13 | volumes: 14 | rabbitmq_data: 15 | rabbitmq_logs: 16 | -------------------------------------------------------------------------------- /carts/src/core/carts/usecases/delete-cart-by-id.usecase.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { CartService } from '../services/cart-service.interface'; 4 | 5 | @Injectable() 6 | export class DeleteCartByIdUseCase { 7 | constructor(private readonly cartService: CartService) {} 8 | 9 | async execute(cartId: string) { 10 | await this.cartService.delete(cartId); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /users/src/core/users/services/user-service.interface.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../entities/user.entity'; 2 | 3 | export abstract class UserService { 4 | abstract createClient( 5 | username: string, 6 | email: string, 7 | password: string, 8 | ): Promise; 9 | abstract findById(userId: string): Promise; 10 | abstract findByEmail(email: string): Promise; 11 | } 12 | -------------------------------------------------------------------------------- /carts/src/core/carts/usecases/find-user-cart.usecase.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { CartService } from '../services/cart-service.interface'; 4 | 5 | @Injectable() 6 | export class FindUserCartUseCase { 7 | constructor(private readonly cartService: CartService) {} 8 | 9 | async execute(userId: string) { 10 | return this.cartService.findByUserId(userId); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /products/src/core/products/usecases/delete-product.usecase.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { ProductService } from '../services/product-service.interface'; 4 | 5 | @Injectable() 6 | export class DeleteProductUseCase { 7 | constructor(private readonly productService: ProductService) {} 8 | 9 | async execute(productId: string) { 10 | await this.productService.delete(productId); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /orders/src/common/@types/pagination.ts: -------------------------------------------------------------------------------- 1 | export type Page = { 2 | content: T[]; 3 | page: { 4 | size: number; 5 | number: number; 6 | totalElements: number; 7 | totalPages: number; 8 | }; 9 | }; 10 | 11 | export type SortOrder = 'ASC' | 'DESC'; 12 | 13 | export type Params = { 14 | page: number; 15 | size: number; 16 | sort: SortOrder; 17 | sortBy: keyof T; 18 | } & { [key in keyof T]?: string }; 19 | -------------------------------------------------------------------------------- /products/src/common/@types/pagination.ts: -------------------------------------------------------------------------------- 1 | export type Page = { 2 | content: T[]; 3 | page: { 4 | size: number; 5 | number: number; 6 | totalElements: number; 7 | totalPages: number; 8 | }; 9 | }; 10 | 11 | export type SortOrder = 'ASC' | 'DESC'; 12 | 13 | export type Params = { 14 | page: number; 15 | size: number; 16 | sort: SortOrder; 17 | sortBy: keyof T; 18 | } & { [key in keyof T]?: string }; 19 | -------------------------------------------------------------------------------- /api-gateway/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-gateway", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "start": "node src/server.js" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "description": "", 12 | "dependencies": { 13 | "@fastify/http-proxy": "^11.0.0", 14 | "dotenv": "^16.4.5", 15 | "fastify": "^5.1.0", 16 | "jsonwebtoken": "^9.0.2", 17 | "path-to-regexp": "^8.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /users/src/core/users/mappers/user.mapper.ts: -------------------------------------------------------------------------------- 1 | import { UserDto } from '../dtos/user.dto'; 2 | import { User } from '../entities/user.entity'; 3 | 4 | export class UserMapper { 5 | static toDto(entity: User): UserDto { 6 | const dto = new UserDto(); 7 | dto.id = entity._id; 8 | dto.username = entity.username; 9 | dto.email = entity.email; 10 | dto.roles = entity.roles; 11 | dto.createdAt = entity.createdAt.toISOString(); 12 | return dto; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /rabbit-mq/docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | services: 2 | broker: 3 | container_name: rabbitmq 4 | image: rabbitmq:4.0-management 5 | restart: always 6 | environment: 7 | - RABBITMQ_DEFAULT_PASS=admin 8 | - RABBITMQ_DEFAULT_USER=admin 9 | ports: 10 | - "5672:5672" 11 | - "15672:15672" 12 | volumes: 13 | - rabbitmq_data:/var/lib/rabbitmq 14 | - rabbitmq_logs:/var/log/rabbitmq 15 | 16 | volumes: 17 | rabbitmq_data: 18 | rabbitmq_logs: 19 | -------------------------------------------------------------------------------- /products/src/core/products/dtos/create-product.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsInt, IsPositive, IsString, IsUrl, Length } from 'class-validator'; 2 | 3 | export class CreateProductDto { 4 | @IsPositive() 5 | @IsInt() 6 | price: number; 7 | 8 | @IsString() 9 | @Length(4, 100) 10 | name: string; 11 | 12 | @IsString() 13 | @Length(4, 100) 14 | description: string; 15 | 16 | @IsUrl() 17 | photoUrl: string; 18 | 19 | @IsPositive() 20 | @IsInt() 21 | stockQuantity: number; 22 | } 23 | -------------------------------------------------------------------------------- /products/src/core/products/dtos/update-product.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsInt, IsPositive, IsString, IsUrl, Length } from 'class-validator'; 2 | 3 | export class UpdateProductDto { 4 | @IsPositive() 5 | @IsInt() 6 | price: number; 7 | 8 | @IsString() 9 | @Length(4, 100) 10 | name: string; 11 | 12 | @IsString() 13 | @Length(4, 100) 14 | description: string; 15 | 16 | @IsUrl() 17 | photoUrl: string; 18 | 19 | @IsPositive() 20 | @IsInt() 21 | stockQuantity: number; 22 | } 23 | -------------------------------------------------------------------------------- /carts/src/core/carts/factories/cart.factory.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from 'node:crypto'; 2 | 3 | import { Cart } from '../entities/cart.entity'; 4 | 5 | export class CartFactory { 6 | static create(userId: string) { 7 | const entity = new Cart(); 8 | 9 | entity._id = randomUUID(); 10 | entity.createdAt = new Date(); 11 | entity.products = []; 12 | entity.productsQuantity = 0; 13 | entity.totalPrice = 0; 14 | entity.userId = userId; 15 | 16 | return entity; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /orders/src/infra/env-config/services/env-config-service.interface.ts: -------------------------------------------------------------------------------- 1 | export abstract class EnvConfigService { 2 | abstract getDbUri(): string; 3 | abstract getDbName(): string; 4 | abstract getDbUser(): string; 5 | abstract getDbPassword(): string; 6 | abstract getDbPort(): number; 7 | abstract getServerPort(): number; 8 | abstract getServerJwtSecret(): string; 9 | abstract getCartServiceUrl(): string; 10 | abstract getUserServiceUrl(): string; 11 | abstract getRabbitMQUrl(): string; 12 | } 13 | -------------------------------------------------------------------------------- /notifications/src/infra/template-engine/template-engine.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { TemplateEngineServiceImpl } from './services/implementations/template-engine.service'; 4 | import { TemplateEngineService } from './services/template-engine-service.interface'; 5 | 6 | @Module({ 7 | providers: [ 8 | { provide: TemplateEngineService, useClass: TemplateEngineServiceImpl }, 9 | ], 10 | exports: [TemplateEngineService], 11 | }) 12 | export class TemplateEngineModule {} 13 | -------------------------------------------------------------------------------- /orders/src/core/orders/mappers/payment.mapper.ts: -------------------------------------------------------------------------------- 1 | import { PaymentDto } from '../dtos/payment.dto'; 2 | import { Payment } from '../entities/payment.entity'; 3 | 4 | export class PaymentMapper { 5 | static toDto(entity: Payment): PaymentDto { 6 | const dto = new PaymentDto(); 7 | 8 | dto.id = entity._id; 9 | dto.price = entity.price; 10 | dto.method = entity.method; 11 | dto.parcels = entity.parcels; 12 | dto.createdAt = entity.createdAt.toISOString(); 13 | 14 | return dto; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /api-gateway/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-alpine AS builder 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json ./ 6 | COPY package-lock.json ./ 7 | 8 | RUN npm install 9 | 10 | COPY . . 11 | 12 | RUN npm prune --production 13 | 14 | FROM node:22-alpine 15 | 16 | WORKDIR /app 17 | 18 | COPY --from=builder /app/package.json ./ 19 | COPY --from=builder /app/package-lock.json ./ 20 | COPY --from=builder /app/node_modules ./node_modules 21 | COPY --from=builder /app/src ./src 22 | 23 | EXPOSE 8080 24 | 25 | CMD ["node", "src/server.js"] -------------------------------------------------------------------------------- /carts/src/core/carts/dtos/cart-product.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsInt, IsPositive, IsString, IsUrl, Length } from 'class-validator'; 2 | 3 | export class CartProductDto { 4 | @IsString() 5 | id: string; 6 | 7 | @IsPositive() 8 | @IsInt() 9 | price: number; 10 | 11 | @IsString() 12 | @Length(4, 100) 13 | name: string; 14 | 15 | @IsString() 16 | @Length(4, 100) 17 | description: string; 18 | 19 | @IsUrl() 20 | photoUrl: string; 21 | 22 | @IsPositive() 23 | @IsInt() 24 | inCartQuantity: number; 25 | } 26 | -------------------------------------------------------------------------------- /orders/src/core/carts/dtos/cart-product.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsInt, IsPositive, IsString, IsUrl, Length } from 'class-validator'; 2 | 3 | export class CartProductDto { 4 | @IsString() 5 | id: string; 6 | 7 | @IsPositive() 8 | @IsInt() 9 | price: number; 10 | 11 | @IsString() 12 | @Length(4, 100) 13 | name: string; 14 | 15 | @IsString() 16 | @Length(4, 100) 17 | description: string; 18 | 19 | @IsUrl() 20 | photoUrl: string; 21 | 22 | @IsPositive() 23 | @IsInt() 24 | inCartQuantity: number; 25 | } 26 | -------------------------------------------------------------------------------- /products/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { LoggerModule } from 'nestjs-pino'; 3 | 4 | import { ProductsModule } from './core/products/products.module'; 5 | import { DatabaseModule } from './infra/database/database.module'; 6 | import { EnvConfigModule } from './infra/env-config/env-config.module'; 7 | 8 | @Module({ 9 | imports: [ 10 | DatabaseModule, 11 | EnvConfigModule, 12 | LoggerModule.forRoot(), 13 | ProductsModule, 14 | ], 15 | }) 16 | export class AppModule {} 17 | -------------------------------------------------------------------------------- /notifications/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { 3 | FastifyAdapter, 4 | NestFastifyApplication, 5 | } from '@nestjs/platform-fastify'; 6 | import { Logger } from 'nestjs-pino'; 7 | 8 | import { AppModule } from './app.module'; 9 | 10 | async function bootstrap() { 11 | const app = await NestFactory.create( 12 | AppModule, 13 | new FastifyAdapter(), 14 | ); 15 | 16 | app.useLogger(app.get(Logger)); 17 | 18 | await app.init(); 19 | } 20 | bootstrap(); 21 | -------------------------------------------------------------------------------- /payments/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-slim AS builder 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json ./ 6 | COPY package-lock.json ./ 7 | 8 | RUN npm install 9 | 10 | COPY . . 11 | 12 | RUN npm run build 13 | 14 | RUN npm prune --production 15 | 16 | FROM node:22-slim 17 | 18 | WORKDIR /app 19 | 20 | COPY --from=builder /app/package.json ./ 21 | COPY --from=builder /app/package-lock.json ./ 22 | COPY --from=builder /app/node_modules ./node_modules 23 | COPY --from=builder /app/dist ./dist 24 | 25 | CMD ["node", "dist/src/main.js"] -------------------------------------------------------------------------------- /products/src/core/products/dtos/check-products-stock-availability.dto.ts: -------------------------------------------------------------------------------- 1 | import { Type } from 'class-transformer'; 2 | import { 3 | ArrayMaxSize, 4 | ArrayMinSize, 5 | IsArray, 6 | ValidateNested, 7 | } from 'class-validator'; 8 | 9 | import { CartProductDto } from './cart-product.dto'; 10 | 11 | export class CheckProductsStockAvailabilityDto { 12 | @Type(() => CartProductDto) 13 | @ValidateNested({ each: true }) 14 | @IsArray() 15 | @ArrayMinSize(1) 16 | @ArrayMaxSize(30) 17 | products: CartProductDto[]; 18 | } 19 | -------------------------------------------------------------------------------- /carts/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\..*spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /carts/jest.unit.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /orders/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\..*spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /payments/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\..*spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /products/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\..*spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /users/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\..*spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /users/jest.unit.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /carts/jest.e2e.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.e2e-spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /carts/jest.int.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.int-spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /notifications/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\..*spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /orders/jest.e2e.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.e2e-spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /orders/jest.int.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.int-spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /orders/jest.unit.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /payments/jest.unit.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /products/jest.unit.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /users/jest.e2e.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.e2e-spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /users/jest.int.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.int-spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /carts/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-slim AS builder 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json ./ 6 | COPY package-lock.json ./ 7 | 8 | RUN npm install 9 | 10 | COPY . . 11 | 12 | RUN npm run build 13 | 14 | RUN npm prune --production 15 | 16 | FROM node:22-slim 17 | 18 | WORKDIR /app 19 | 20 | COPY --from=builder /app/package.json ./ 21 | COPY --from=builder /app/package-lock.json ./ 22 | COPY --from=builder /app/node_modules ./node_modules 23 | COPY --from=builder /app/dist ./dist 24 | 25 | EXPOSE 8080 26 | 27 | CMD ["node", "dist/src/main.js"] -------------------------------------------------------------------------------- /notifications/jest.unit.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /orders/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-slim AS builder 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json ./ 6 | COPY package-lock.json ./ 7 | 8 | RUN npm install 9 | 10 | COPY . . 11 | 12 | RUN npm run build 13 | 14 | RUN npm prune --production 15 | 16 | FROM node:22-slim 17 | 18 | WORKDIR /app 19 | 20 | COPY --from=builder /app/package.json ./ 21 | COPY --from=builder /app/package-lock.json ./ 22 | COPY --from=builder /app/node_modules ./node_modules 23 | COPY --from=builder /app/dist ./dist 24 | 25 | EXPOSE 8080 26 | 27 | CMD ["node", "dist/src/main.js"] -------------------------------------------------------------------------------- /payments/jest.e2e.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.e2e-spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /payments/jest.int.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.int-spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /products/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-slim AS builder 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json ./ 6 | COPY package-lock.json ./ 7 | 8 | RUN npm install 9 | 10 | COPY . . 11 | 12 | RUN npm run build 13 | 14 | RUN npm prune --production 15 | 16 | FROM node:22-slim 17 | 18 | WORKDIR /app 19 | 20 | COPY --from=builder /app/package.json ./ 21 | COPY --from=builder /app/package-lock.json ./ 22 | COPY --from=builder /app/node_modules ./node_modules 23 | COPY --from=builder /app/dist ./dist 24 | 25 | EXPOSE 8080 26 | 27 | CMD ["node", "dist/src/main.js"] -------------------------------------------------------------------------------- /products/jest.e2e.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.e2e-spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /products/jest.int.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.int-spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /users/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-slim AS builder 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json ./ 6 | COPY package-lock.json ./ 7 | 8 | RUN npm install 9 | 10 | COPY . . 11 | 12 | RUN npm run build 13 | 14 | RUN npm prune --production 15 | 16 | FROM node:22-slim 17 | 18 | WORKDIR /app 19 | 20 | COPY --from=builder /app/package.json ./ 21 | COPY --from=builder /app/package-lock.json ./ 22 | COPY --from=builder /app/node_modules ./node_modules 23 | COPY --from=builder /app/dist ./dist 24 | 25 | EXPOSE 8080 26 | 27 | CMD ["node", "dist/src/main.js"] -------------------------------------------------------------------------------- /carts/src/infra/env-config/env-config.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { ConfigModule } from '@nestjs/config'; 3 | 4 | import { EnvConfigService } from './services/env-config-service.interface'; 5 | import { EnvConfigServiceImpl } from './services/implementations/env-config.service'; 6 | 7 | @Global() 8 | @Module({ 9 | imports: [ConfigModule.forRoot()], 10 | providers: [{ provide: EnvConfigService, useClass: EnvConfigServiceImpl }], 11 | exports: [EnvConfigService], 12 | }) 13 | export class EnvConfigModule {} 14 | -------------------------------------------------------------------------------- /notifications/jest.e2e.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.e2e-spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /notifications/jest.int.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | 4 | export default { 5 | moduleFileExtensions: ['js', 'json', 'ts'], 6 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 7 | prefix: '/', 8 | }), 9 | testRegex: '.*\\.int-spec\\.ts$', 10 | transform: { 11 | '^.+\\.(t|j)s$': 'ts-jest', 12 | }, 13 | collectCoverageFrom: ['**/*.(t|j)s'], 14 | coverageDirectory: '../coverage', 15 | testEnvironment: 'node', 16 | }; 17 | -------------------------------------------------------------------------------- /orders/src/infra/env-config/env-config.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { ConfigModule } from '@nestjs/config'; 3 | 4 | import { EnvConfigService } from './services/env-config-service.interface'; 5 | import { EnvConfigServiceImpl } from './services/implementations/env-config.service'; 6 | 7 | @Global() 8 | @Module({ 9 | imports: [ConfigModule.forRoot()], 10 | providers: [{ provide: EnvConfigService, useClass: EnvConfigServiceImpl }], 11 | exports: [EnvConfigService], 12 | }) 13 | export class EnvConfigModule {} 14 | -------------------------------------------------------------------------------- /payments/src/infra/env-config/env-config.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { ConfigModule } from '@nestjs/config'; 3 | 4 | import { EnvConfigService } from './services/env-config-service.interface'; 5 | import { EnvConfigServiceImpl } from './services/implementations/env-config.service'; 6 | 7 | @Global() 8 | @Module({ 9 | imports: [ConfigModule.forRoot()], 10 | providers: [{ provide: EnvConfigService, useClass: EnvConfigServiceImpl }], 11 | exports: [EnvConfigService], 12 | }) 13 | export class EnvConfigModule {} 14 | -------------------------------------------------------------------------------- /products/src/infra/env-config/env-config.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { ConfigModule } from '@nestjs/config'; 3 | 4 | import { EnvConfigService } from './services/env-config-service.interface'; 5 | import { EnvConfigServiceImpl } from './services/implementations/env-config.service'; 6 | 7 | @Global() 8 | @Module({ 9 | imports: [ConfigModule.forRoot()], 10 | providers: [{ provide: EnvConfigService, useClass: EnvConfigServiceImpl }], 11 | exports: [EnvConfigService], 12 | }) 13 | export class EnvConfigModule {} 14 | -------------------------------------------------------------------------------- /users/src/infra/env-config/env-config.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { ConfigModule } from '@nestjs/config'; 3 | 4 | import { EnvConfigService } from './services/env-config-service.interface'; 5 | import { EnvConfigServiceImpl } from './services/implementations/env-config.service'; 6 | 7 | @Global() 8 | @Module({ 9 | imports: [ConfigModule.forRoot()], 10 | providers: [{ provide: EnvConfigService, useClass: EnvConfigServiceImpl }], 11 | exports: [EnvConfigService], 12 | }) 13 | export class EnvConfigModule {} 14 | -------------------------------------------------------------------------------- /notifications/src/infra/env-config/env-config.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { ConfigModule } from '@nestjs/config'; 3 | 4 | import { EnvConfigService } from './services/env-config-service.interface'; 5 | import { EnvConfigServiceImpl } from './services/implementations/env-config.service'; 6 | 7 | @Global() 8 | @Module({ 9 | imports: [ConfigModule.forRoot()], 10 | providers: [{ provide: EnvConfigService, useClass: EnvConfigServiceImpl }], 11 | exports: [EnvConfigService], 12 | }) 13 | export class EnvConfigModule {} 14 | -------------------------------------------------------------------------------- /users/src/core/users/usecases/find-user-by-id.usecase.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | 3 | import { UserService } from '../services/user-service.interface'; 4 | 5 | @Injectable() 6 | export class FindUserByIdUseCase { 7 | constructor(private readonly userService: UserService) {} 8 | 9 | async execute(userId: string) { 10 | const user = await this.userService.findById(userId); 11 | 12 | if (!user) { 13 | throw new NotFoundException('User not found'); 14 | } 15 | 16 | return user; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /users/src/core/auth/services/implementations/hash.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { compare, genSalt, hash } from 'bcrypt'; 3 | 4 | import { HashService } from '../hash-service.interface'; 5 | 6 | @Injectable() 7 | export class HashServiceImpl implements HashService { 8 | async hash(value: string): Promise { 9 | const salt = await genSalt(10); 10 | return hash(value, salt); 11 | } 12 | 13 | compare(value: string, encoded: string): Promise { 14 | return compare(value, encoded); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /carts/src/core/carts/usecases/find-cart-by-user-id.usecase.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | 3 | import { CartService } from '../services/cart-service.interface'; 4 | 5 | @Injectable() 6 | export class FindCartByUserIdUseCase { 7 | constructor(private readonly cartService: CartService) {} 8 | 9 | async execute(userId: string) { 10 | const cart = await this.cartService.findByUserId(userId); 11 | 12 | if (!cart) { 13 | throw new NotFoundException('Cart not found'); 14 | } 15 | 16 | return cart; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /orders/src/core/orders/usecases/find-order-by-id.usecase.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | 3 | import { OrderService } from '../services/order-service.interface'; 4 | 5 | @Injectable() 6 | export class FindOrderByIdUseCase { 7 | constructor(private readonly orderService: OrderService) {} 8 | 9 | async execute(orderId: string) { 10 | const order = await this.orderService.findById(orderId); 11 | 12 | if (!order) { 13 | throw new NotFoundException('Order not found'); 14 | } 15 | 16 | return order; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /carts/src/core/carts/mappers/cart-product.mapper.ts: -------------------------------------------------------------------------------- 1 | import { CartProductDto } from '../dtos/cart-product.dto'; 2 | import { CartProduct } from '../entities/cart-product.entity'; 3 | 4 | export class CartProductMapper { 5 | static toDto(entity: CartProduct): CartProductDto { 6 | const dto = new CartProductDto(); 7 | dto.id = entity._id; 8 | dto.price = entity.price; 9 | dto.name = entity.name; 10 | dto.description = entity.description; 11 | dto.photoUrl = entity.photoUrl; 12 | dto.inCartQuantity = entity.inCartQuantity; 13 | return dto; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /carts/src/core/carts/usecases/clear-cart.usecase.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | 3 | import { CartService } from '../services/cart-service.interface'; 4 | 5 | @Injectable() 6 | export class ClearCartUseCase { 7 | constructor(private readonly cartService: CartService) {} 8 | 9 | async execute(userId: string) { 10 | const cart = await this.cartService.findByUserId(userId); 11 | 12 | if (!cart) { 13 | throw new NotFoundException('Cart not found'); 14 | } 15 | 16 | await this.cartService.delete(cart._id); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /notifications/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-slim AS builder 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json ./ 6 | COPY package-lock.json ./ 7 | 8 | RUN npm install 9 | 10 | COPY . . 11 | 12 | RUN npm run build 13 | 14 | RUN npm prune --production 15 | 16 | FROM node:22-slim 17 | 18 | WORKDIR /app 19 | 20 | COPY --from=builder /app/package.json ./ 21 | COPY --from=builder /app/package-lock.json ./ 22 | COPY --from=builder /app/node_modules ./node_modules 23 | COPY --from=builder /app/dist ./dist 24 | COPY --from=builder /app/templates ./templates 25 | 26 | CMD ["node", "dist/src/main.js"] -------------------------------------------------------------------------------- /products/src/core/products/entities/product.entity.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | 3 | @Schema({ collection: 'products' }) 4 | export class Product { 5 | @Prop() 6 | _id: string; 7 | 8 | @Prop() 9 | price: number; 10 | 11 | @Prop({ index: 'text' }) 12 | name: string; 13 | 14 | @Prop() 15 | description: string; 16 | 17 | @Prop() 18 | photoUrl: string; 19 | 20 | @Prop() 21 | stockQuantity: number; 22 | 23 | @Prop() 24 | createdAt: Date; 25 | } 26 | 27 | export const ProductSchema = SchemaFactory.createForClass(Product); 28 | -------------------------------------------------------------------------------- /users/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { LoggerModule } from 'nestjs-pino'; 3 | 4 | import { AuthModule } from './core/auth/auth.module'; 5 | import { UsersModule } from './core/users/users.module'; 6 | import { DatabaseModule } from './infra/database/database.module'; 7 | import { EnvConfigModule } from './infra/env-config/env-config.module'; 8 | 9 | @Module({ 10 | imports: [ 11 | DatabaseModule, 12 | EnvConfigModule, 13 | AuthModule, 14 | UsersModule, 15 | LoggerModule.forRoot(), 16 | ], 17 | }) 18 | export class AppModule {} 19 | -------------------------------------------------------------------------------- /orders/src/core/users/services/implementations/user.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpService } from '@nestjs/axios'; 2 | import { Injectable } from '@nestjs/common'; 3 | 4 | import { UserDto } from '../../dtos/user.dto'; 5 | import { UserService } from '../user-service.interface'; 6 | 7 | @Injectable() 8 | export class UserServiceImpl implements UserService { 9 | constructor(private readonly httpService: HttpService) {} 10 | 11 | async findById(userId: string): Promise { 12 | const { data } = await this.httpService.axiosRef.get(`/users/${userId}`); 13 | return data; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /orders/src/core/orders/dtos/payment.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsDateString, 3 | IsIn, 4 | IsInt, 5 | IsPositive, 6 | IsString, 7 | } from 'class-validator'; 8 | 9 | import { PAYMENT_METHOD } from '../enums/payment-method.enum'; 10 | 11 | export class PaymentDto { 12 | @IsString() 13 | id: string; 14 | 15 | @IsPositive() 16 | @IsInt() 17 | price: number; 18 | 19 | @IsIn([PAYMENT_METHOD.CREDIT_CARD, PAYMENT_METHOD.DEBIT_CARD]) 20 | method: PAYMENT_METHOD; 21 | 22 | @IsPositive() 23 | @IsInt() 24 | parcels: number; 25 | 26 | @IsDateString() 27 | createdAt: string; 28 | } 29 | -------------------------------------------------------------------------------- /carts/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { LoggerModule } from 'nestjs-pino'; 3 | 4 | import { CartsModule } from './core/carts/carts.module'; 5 | import { ProductsModule } from './core/products/products.module'; 6 | import { DatabaseModule } from './infra/database/database.module'; 7 | import { EnvConfigModule } from './infra/env-config/env-config.module'; 8 | 9 | @Module({ 10 | imports: [ 11 | DatabaseModule, 12 | EnvConfigModule, 13 | LoggerModule.forRoot(), 14 | CartsModule, 15 | ProductsModule, 16 | ], 17 | }) 18 | export class AppModule {} 19 | -------------------------------------------------------------------------------- /carts/src/core/carts/mappers/cart.mapper.ts: -------------------------------------------------------------------------------- 1 | import { CartDto } from '../dtos/cart.dto'; 2 | import { Cart } from '../entities/cart.entity'; 3 | import { CartProductMapper } from './cart-product.mapper'; 4 | 5 | export class CartMapper { 6 | static toDto(entity: Cart): CartDto { 7 | const dto = new CartDto(); 8 | dto.id = entity._id; 9 | dto.products = entity.products.map(CartProductMapper.toDto); 10 | dto.productsQuantity = entity.productsQuantity; 11 | dto.totalPrice = entity.totalPrice; 12 | dto.createdAt = entity.createdAt.toISOString(); 13 | return dto; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /payments/src/core/products/services/implementations/product.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpService } from '@nestjs/axios'; 2 | import { Injectable } from '@nestjs/common'; 3 | 4 | import { CartProductDto, ProductService } from '../product-service.interface'; 5 | 6 | @Injectable() 7 | export class ProductServiceImpl implements ProductService { 8 | constructor(private readonly httpService: HttpService) {} 9 | 10 | async checkStockAvailability(products: CartProductDto[]): Promise { 11 | await this.httpService.axiosRef.post('/products/availability', { 12 | products, 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /products/src/core/products/usecases/find-product-by-id.usecase.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | 3 | import { ProductService } from '../services/product-service.interface'; 4 | 5 | @Injectable() 6 | export class FindProductByIdUseCase { 7 | constructor(private readonly productService: ProductService) {} 8 | 9 | async execute(productId: string) { 10 | const product = await this.productService.findById(productId); 11 | 12 | if (!product) { 13 | throw new NotFoundException('Product not found'); 14 | } 15 | 16 | return product; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /orders/src/infra/rabbitmq/services/rabbitmq-service.interface.ts: -------------------------------------------------------------------------------- 1 | export abstract class RabbitMQService { 2 | abstract publishToQueue(queue: string, message: any): Promise; 3 | 4 | abstract consumeFromQueue( 5 | queue: string, 6 | onMessage: (msg: any) => void, 7 | ): Promise; 8 | 9 | abstract publishToDirectExchange( 10 | exchange: string, 11 | routeKey: string, 12 | message: any, 13 | ): Promise; 14 | 15 | abstract consumeFromDirectExchange( 16 | exchange: string, 17 | routeKey: string, 18 | onMessage: (msg: any) => void, 19 | ): Promise; 20 | } 21 | -------------------------------------------------------------------------------- /payments/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { LoggerModule } from 'nestjs-pino'; 3 | 4 | import { PaymentsModule } from './core/payments/payments.module'; 5 | import { ProductsModule } from './core/products/products.module'; 6 | import { EnvConfigModule } from './infra/env-config/env-config.module'; 7 | import { RabbitMQModule } from './infra/rabbitmq/rabbitmq.module'; 8 | 9 | @Module({ 10 | imports: [ 11 | EnvConfigModule, 12 | LoggerModule.forRoot(), 13 | RabbitMQModule, 14 | PaymentsModule, 15 | ProductsModule, 16 | ], 17 | }) 18 | export class AppModule {} 19 | -------------------------------------------------------------------------------- /payments/src/infra/rabbitmq/services/rabbitmq-service.interface.ts: -------------------------------------------------------------------------------- 1 | export abstract class RabbitMQService { 2 | abstract publishToQueue(queue: string, message: any): Promise; 3 | 4 | abstract consumeFromQueue( 5 | queue: string, 6 | onMessage: (msg: any) => void, 7 | ): Promise; 8 | 9 | abstract publishToDirectExchange( 10 | exchange: string, 11 | routeKey: string, 12 | message: any, 13 | ): Promise; 14 | 15 | abstract consumeFromDirectExchange( 16 | exchange: string, 17 | routeKey: string, 18 | onMessage: (msg: any) => void, 19 | ): Promise; 20 | } 21 | -------------------------------------------------------------------------------- /products/src/core/products/mappers/product.mapper.ts: -------------------------------------------------------------------------------- 1 | import { ProductDto } from '../dtos/product.dto'; 2 | import { Product } from '../entities/product.entity'; 3 | 4 | export class ProductMapper { 5 | static toDto(entity: Product): ProductDto { 6 | const dto = new ProductDto(); 7 | 8 | dto.id = entity._id; 9 | dto.price = entity.price; 10 | dto.name = entity.name; 11 | dto.description = entity.description; 12 | dto.photoUrl = entity.photoUrl; 13 | dto.stockQuantity = entity.stockQuantity; 14 | dto.createdAt = entity.createdAt.toISOString(); 15 | 16 | return dto; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /products/src/infra/rabbitmq/services/rabbitmq-service.interface.ts: -------------------------------------------------------------------------------- 1 | export abstract class RabbitMQService { 2 | abstract publishToQueue(queue: string, message: any): Promise; 3 | 4 | abstract consumeFromQueue( 5 | queue: string, 6 | onMessage: (msg: any) => void, 7 | ): Promise; 8 | 9 | abstract publishToDirectExchange( 10 | exchange: string, 11 | routeKey: string, 12 | message: any, 13 | ): Promise; 14 | 15 | abstract consumeFromDirectExchange( 16 | exchange: string, 17 | routeKey: string, 18 | onMessage: (msg: any) => void, 19 | ): Promise; 20 | } 21 | -------------------------------------------------------------------------------- /notifications/src/infra/rabbitmq/services/rabbitmq-service.interface.ts: -------------------------------------------------------------------------------- 1 | export abstract class RabbitMQService { 2 | abstract publishToQueue(queue: string, message: any): Promise; 3 | 4 | abstract consumeFromQueue( 5 | queue: string, 6 | onMessage: (msg: any) => void, 7 | ): Promise; 8 | 9 | abstract publishToDirectExchange( 10 | exchange: string, 11 | routeKey: string, 12 | message: any, 13 | ): Promise; 14 | 15 | abstract consumeFromDirectExchange( 16 | exchange: string, 17 | routeKey: string, 18 | onMessage: (msg: any) => void, 19 | ): Promise; 20 | } 21 | -------------------------------------------------------------------------------- /payments/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPipe } from '@nestjs/common'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { 4 | FastifyAdapter, 5 | NestFastifyApplication, 6 | } from '@nestjs/platform-fastify'; 7 | import { Logger } from 'nestjs-pino'; 8 | 9 | import { AppModule } from './app.module'; 10 | 11 | async function bootstrap() { 12 | const app = await NestFactory.create( 13 | AppModule, 14 | new FastifyAdapter(), 15 | ); 16 | 17 | app.useGlobalPipes(new ValidationPipe()); 18 | app.useLogger(app.get(Logger)); 19 | 20 | await app.init(); 21 | } 22 | bootstrap(); 23 | -------------------------------------------------------------------------------- /carts/src/core/products/dtos/product.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsDateString, 3 | IsInt, 4 | IsPositive, 5 | IsString, 6 | IsUrl, 7 | Length, 8 | } from 'class-validator'; 9 | 10 | export class ProductDto { 11 | @IsString() 12 | id: string; 13 | 14 | @IsPositive() 15 | @IsInt() 16 | price: number; 17 | 18 | @IsString() 19 | @Length(4, 100) 20 | name: string; 21 | 22 | @IsString() 23 | @Length(4, 100) 24 | description: string; 25 | 26 | @IsUrl() 27 | photoUrl: string; 28 | 29 | @IsPositive() 30 | @IsInt() 31 | stockQuantity: number; 32 | 33 | @IsDateString() 34 | createdAt: string; 35 | } 36 | -------------------------------------------------------------------------------- /carts/src/core/carts/entities/cart-product.entity.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { HydratedDocument } from 'mongoose'; 3 | 4 | export type CartProductDocument = HydratedDocument; 5 | 6 | @Schema() 7 | export class CartProduct { 8 | @Prop() 9 | _id: string; 10 | 11 | @Prop() 12 | price: number; 13 | 14 | @Prop() 15 | name: string; 16 | 17 | @Prop() 18 | description: string; 19 | 20 | @Prop() 21 | photoUrl: string; 22 | 23 | @Prop() 24 | inCartQuantity: number; 25 | } 26 | 27 | export const CartProductSchema = SchemaFactory.createForClass(CartProduct); 28 | -------------------------------------------------------------------------------- /products/src/core/products/dtos/product.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsDateString, 3 | IsInt, 4 | IsPositive, 5 | IsString, 6 | IsUrl, 7 | Length, 8 | } from 'class-validator'; 9 | 10 | export class ProductDto { 11 | @IsString() 12 | id: string; 13 | 14 | @IsPositive() 15 | @IsInt() 16 | price: number; 17 | 18 | @IsString() 19 | @Length(4, 100) 20 | name: string; 21 | 22 | @IsString() 23 | @Length(4, 100) 24 | description: string; 25 | 26 | @IsUrl() 27 | photoUrl: string; 28 | 29 | @IsPositive() 30 | @IsInt() 31 | stockQuantity: number; 32 | 33 | @IsDateString() 34 | createdAt: string; 35 | } 36 | -------------------------------------------------------------------------------- /orders/src/core/orders/entities/cart-product.entity.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { HydratedDocument } from 'mongoose'; 3 | 4 | export type CartProductDocument = HydratedDocument; 5 | 6 | @Schema() 7 | export class CartProduct { 8 | @Prop() 9 | _id: string; 10 | 11 | @Prop() 12 | price: number; 13 | 14 | @Prop() 15 | name: string; 16 | 17 | @Prop() 18 | description: string; 19 | 20 | @Prop() 21 | photoUrl: string; 22 | 23 | @Prop() 24 | inCartQuantity: number; 25 | } 26 | 27 | export const CartProductSchema = SchemaFactory.createForClass(CartProduct); 28 | -------------------------------------------------------------------------------- /users/src/core/users/factories/users.factory.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from 'node:crypto'; 2 | 3 | import { User } from '../entities/user.entity'; 4 | import { ROLE } from '../enums/role.enum'; 5 | 6 | export class UsersFactory { 7 | static createWithClientRole( 8 | username: string, 9 | email: string, 10 | password: string, 11 | ): User { 12 | const entity = new User(); 13 | entity._id = randomUUID(); 14 | entity.username = username; 15 | entity.password = password; 16 | entity.email = email; 17 | entity.roles = [ROLE.CLIENT]; 18 | entity.createdAt = new Date(); 19 | return entity; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /carts/src/core/carts/factories/cart-product.factory.ts: -------------------------------------------------------------------------------- 1 | import { CartProduct } from '../entities/cart-product.entity'; 2 | 3 | export class CartProductFactory { 4 | static create( 5 | id: string, 6 | price: number, 7 | name: string, 8 | description: string, 9 | photoUrl: string, 10 | inCartQuantity: number, 11 | ) { 12 | const entity = new CartProduct(); 13 | 14 | entity._id = id; 15 | entity.description = description; 16 | entity.inCartQuantity = inCartQuantity; 17 | entity.name = name; 18 | entity.photoUrl = photoUrl; 19 | entity.price = price; 20 | 21 | return entity; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /orders/src/infra/rabbitmq/services/implementations/payments-queue.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { 4 | PaymentsQueueMessage, 5 | PaymentsQueueService, 6 | } from '../payments-queue-service.interface'; 7 | import { RabbitMQService } from '../rabbitmq-service.interface'; 8 | 9 | @Injectable() 10 | export class PaymentsQueueServiceImpl implements PaymentsQueueService { 11 | constructor(private readonly rabbitMQService: RabbitMQService) {} 12 | 13 | async publish(message: PaymentsQueueMessage): Promise { 14 | await this.rabbitMQService.publishToQueue('payments-queue', message); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /orders/src/core/orders/services/order-service.interface.ts: -------------------------------------------------------------------------------- 1 | import { Page, Params } from '@/common/@types/pagination'; 2 | 3 | import { Order } from '../entities/order.entity'; 4 | 5 | export abstract class OrderService { 6 | abstract create(order: Order): Promise; 7 | abstract findById(orderId: string): Promise; 8 | abstract delete(orderId: string): Promise; 9 | abstract update(order: Order): Promise; 10 | 11 | abstract findByIdAndUserId( 12 | orderId: string, 13 | userId: string, 14 | ): Promise; 15 | 16 | abstract findAllPaginated(params: Params): Promise>; 17 | } 18 | -------------------------------------------------------------------------------- /carts/src/core/products/services/implementations/product.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpService } from '@nestjs/axios'; 2 | import { Injectable } from '@nestjs/common'; 3 | 4 | import { ProductDto } from '../../dtos/product.dto'; 5 | import { ProductService } from '../product-service.interface'; 6 | 7 | @Injectable() 8 | export class ProductServiceImpl implements ProductService { 9 | constructor(private readonly httpService: HttpService) {} 10 | 11 | async findById(productId: string): Promise { 12 | const { data } = await this.httpService.axiosRef.get( 13 | `/products/${productId}`, 14 | ); 15 | 16 | return data; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /orders/src/core/orders/mappers/order.mapper.ts: -------------------------------------------------------------------------------- 1 | import { OrderDto } from '../dtos/order.dto'; 2 | import { Order } from '../entities/order.entity'; 3 | import { OrderCartMapper } from './order-cart.mapper'; 4 | import { PaymentMapper } from './payment.mapper'; 5 | 6 | export class OrderMapper { 7 | static toDto(entity: Order): OrderDto { 8 | const dto = new OrderDto(); 9 | 10 | dto.id = entity._id; 11 | dto.status = entity.status; 12 | dto.cart = OrderCartMapper.toDto(entity.cart); 13 | dto.payment = entity.payment && PaymentMapper.toDto(entity.payment); 14 | dto.createdAt = entity.createdAt.toISOString(); 15 | 16 | return dto; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /payments/src/infra/rabbitmq/services/implementations/payments-queue.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { 4 | PaymentsQueueMessage, 5 | PaymentsQueueService, 6 | } from '../payments-queue-service.interface'; 7 | import { RabbitMQService } from '../rabbitmq-service.interface'; 8 | 9 | @Injectable() 10 | export class PaymentsQueueServiceImpl implements PaymentsQueueService { 11 | constructor(private readonly rabbitMQService: RabbitMQService) {} 12 | 13 | async consume(onMessage: (msg: PaymentsQueueMessage) => void): Promise { 14 | await this.rabbitMQService.consumeFromQueue('payments-queue', onMessage); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /orders/src/core/orders/factories/order.factory.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from 'node:crypto'; 2 | 3 | import { Order } from '../entities/order.entity'; 4 | import { OrderCart } from '../entities/order-cart.entity'; 5 | import { ORDER_STATUS } from '../enums/order-status.enum'; 6 | 7 | export class OrderFactory { 8 | static create(userId: string, cart: OrderCart): Order { 9 | const entity = new Order(); 10 | 11 | entity._id = randomUUID(); 12 | entity.cart = cart; 13 | entity.createdAt = new Date(); 14 | entity.payment = null; 15 | entity.status = ORDER_STATUS.WAITING_PAYMENT; 16 | entity.userId = userId; 17 | 18 | return entity; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /orders/src/core/orders/entities/order-cart.entity.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { HydratedDocument } from 'mongoose'; 3 | 4 | import { CartProduct } from './cart-product.entity'; 5 | 6 | export type OrderCartDocument = HydratedDocument; 7 | 8 | @Schema() 9 | export class OrderCart { 10 | @Prop() 11 | _id: string; 12 | 13 | @Prop({ type: [CartProduct] }) 14 | products: CartProduct[]; 15 | 16 | @Prop() 17 | productsQuantity: number; 18 | 19 | @Prop() 20 | totalPrice: number; 21 | 22 | @Prop() 23 | createdAt: Date; 24 | } 25 | 26 | export const OrderCartSchema = SchemaFactory.createForClass(OrderCart); 27 | -------------------------------------------------------------------------------- /orders/src/core/orders/factories/order-cart.factory.ts: -------------------------------------------------------------------------------- 1 | import { CartProduct } from '../entities/cart-product.entity'; 2 | import { OrderCart } from '../entities/order-cart.entity'; 3 | 4 | export class OrderCartFactory { 5 | static create( 6 | id: string, 7 | products: CartProduct[], 8 | productsQuantity: number, 9 | totalPrice: number, 10 | createdAt: Date, 11 | ): OrderCart { 12 | const entity = new OrderCart(); 13 | 14 | entity._id = id; 15 | entity.createdAt = createdAt; 16 | entity.products = products; 17 | entity.productsQuantity = productsQuantity; 18 | entity.totalPrice = totalPrice; 19 | 20 | return entity; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /users/src/core/users/controllers/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Param, ParseUUIDPipe } from '@nestjs/common'; 2 | 3 | import { UserDto } from '../dtos/user.dto'; 4 | import { UserMapper } from '../mappers/user.mapper'; 5 | import { FindUserByIdUseCase } from '../usecases/find-user-by-id.usecase'; 6 | 7 | @Controller('users') 8 | export class UsersController { 9 | constructor(private readonly findUserByIdUseCase: FindUserByIdUseCase) {} 10 | 11 | @Get(':userId') 12 | async findById( 13 | @Param('userId', ParseUUIDPipe) userId: string, 14 | ): Promise { 15 | return this.findUserByIdUseCase.execute(userId).then(UserMapper.toDto); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /carts/src/core/carts/dtos/cart.dto.ts: -------------------------------------------------------------------------------- 1 | import { Type } from 'class-transformer'; 2 | import { 3 | IsArray, 4 | IsDateString, 5 | IsInt, 6 | IsPositive, 7 | IsString, 8 | ValidateNested, 9 | } from 'class-validator'; 10 | 11 | import { CartProductDto } from './cart-product.dto'; 12 | 13 | export class CartDto { 14 | @IsString() 15 | id: string; 16 | 17 | @Type(() => CartProductDto) 18 | @ValidateNested({ each: true }) 19 | @IsArray() 20 | products: CartProductDto[]; 21 | 22 | @IsPositive() 23 | @IsInt() 24 | productsQuantity: number; 25 | 26 | @IsPositive() 27 | @IsInt() 28 | totalPrice: number; 29 | 30 | @IsDateString() 31 | createdAt: string; 32 | } 33 | -------------------------------------------------------------------------------- /orders/src/core/carts/dtos/cart.dto.ts: -------------------------------------------------------------------------------- 1 | import { Type } from 'class-transformer'; 2 | import { 3 | IsArray, 4 | IsDateString, 5 | IsInt, 6 | IsPositive, 7 | IsString, 8 | ValidateNested, 9 | } from 'class-validator'; 10 | 11 | import { CartProductDto } from './cart-product.dto'; 12 | 13 | export class CartDto { 14 | @IsString() 15 | id: string; 16 | 17 | @Type(() => CartProductDto) 18 | @ValidateNested({ each: true }) 19 | @IsArray() 20 | products: CartProductDto[]; 21 | 22 | @IsPositive() 23 | @IsInt() 24 | productsQuantity: number; 25 | 26 | @IsPositive() 27 | @IsInt() 28 | totalPrice: number; 29 | 30 | @IsDateString() 31 | createdAt: string; 32 | } 33 | -------------------------------------------------------------------------------- /orders/src/core/orders/dtos/order.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsDateString, 3 | IsIn, 4 | IsObject, 5 | IsOptional, 6 | IsString, 7 | } from 'class-validator'; 8 | 9 | import { CartDto } from '@/core/carts/dtos/cart.dto'; 10 | 11 | import { ORDER_STATUS } from '../enums/order-status.enum'; 12 | import { PaymentDto } from './payment.dto'; 13 | 14 | export class OrderDto { 15 | @IsString() 16 | id: string; 17 | 18 | @IsIn([ORDER_STATUS.PAID, ORDER_STATUS.WAITING_PAYMENT]) 19 | status: ORDER_STATUS; 20 | 21 | @IsObject() 22 | cart: CartDto; 23 | 24 | @IsObject() 25 | @IsOptional() 26 | payment: PaymentDto | null; 27 | 28 | @IsDateString() 29 | createdAt: string; 30 | } 31 | -------------------------------------------------------------------------------- /users/src/core/users/entities/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { HydratedDocument } from 'mongoose'; 3 | 4 | import { ROLE } from '../enums/role.enum'; 5 | 6 | export type UserDocument = HydratedDocument; 7 | 8 | @Schema({ collection: 'users' }) 9 | export class User { 10 | @Prop() 11 | _id: string; 12 | 13 | @Prop() 14 | username: string; 15 | 16 | @Prop({ index: 'text' }) 17 | email: string; 18 | 19 | @Prop() 20 | password: string; 21 | 22 | @Prop({ type: [String] }) 23 | roles: ROLE[]; 24 | 25 | @Prop() 26 | createdAt: Date; 27 | } 28 | 29 | export const UserSchema = SchemaFactory.createForClass(User); 30 | -------------------------------------------------------------------------------- /products/src/core/products/factories/product.factory.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from 'node:crypto'; 2 | 3 | import { Product } from '../entities/product.entity'; 4 | 5 | export class ProductFactory { 6 | static create( 7 | price: number, 8 | name: string, 9 | description: string, 10 | photoUrl: string, 11 | stockQuantity: number, 12 | ): Product { 13 | const entity = new Product(); 14 | entity._id = randomUUID(); 15 | entity.createdAt = new Date(); 16 | entity.description = description; 17 | entity.name = name; 18 | entity.photoUrl = photoUrl; 19 | entity.price = price; 20 | entity.stockQuantity = stockQuantity; 21 | return entity; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /carts/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | database: 3 | container_name: carts-mongodb 4 | restart: always 5 | image: mongo 6 | environment: 7 | - MONGO_INITDB_ROOT_USERNAME=${DB_USER} 8 | - MONGO_INITDB_ROOT_PASSWORD=${DB_PASSWORD} 9 | networks: 10 | - carts-service-network 11 | volumes: 12 | - carts-db-volume:/data/db 13 | 14 | backend: 15 | container_name: carts-nest-api 16 | depends_on: 17 | - database 18 | restart: always 19 | build: 20 | context: . 21 | env_file: 22 | - .env 23 | networks: 24 | - carts-service-network 25 | 26 | networks: 27 | carts-service-network: 28 | 29 | volumes: 30 | carts-db-volume: 31 | -------------------------------------------------------------------------------- /orders/src/core/orders/entities/payment.entity.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { HydratedDocument } from 'mongoose'; 3 | 4 | import { PAYMENT_METHOD } from '../enums/payment-method.enum'; 5 | 6 | export type PaymentDocument = HydratedDocument; 7 | 8 | @Schema() 9 | export class Payment { 10 | @Prop() 11 | _id: string; 12 | 13 | @Prop() 14 | transactionCode: string; 15 | 16 | @Prop() 17 | price: number; 18 | 19 | @Prop({ type: String }) 20 | method: PAYMENT_METHOD; 21 | 22 | @Prop() 23 | parcels: number; 24 | 25 | @Prop() 26 | createdAt: Date; 27 | } 28 | 29 | export const PaymentSchema = SchemaFactory.createForClass(Payment); 30 | -------------------------------------------------------------------------------- /users/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | database: 3 | container_name: users-mongodb 4 | restart: always 5 | image: mongo 6 | environment: 7 | - MONGO_INITDB_ROOT_USERNAME=${DB_USER} 8 | - MONGO_INITDB_ROOT_PASSWORD=${DB_PASSWORD} 9 | networks: 10 | - users-service-network 11 | volumes: 12 | - users-db-volume:/data/db 13 | 14 | backend: 15 | container_name: users-nest-api 16 | depends_on: 17 | - database 18 | restart: always 19 | build: 20 | context: . 21 | env_file: 22 | - .env 23 | networks: 24 | - users-service-network 25 | 26 | networks: 27 | users-service-network: 28 | 29 | volumes: 30 | users-db-volume: 31 | -------------------------------------------------------------------------------- /carts/src/infra/database/database.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | 4 | import { EnvConfigService } from '../env-config/services/env-config-service.interface'; 5 | 6 | @Module({ 7 | imports: [ 8 | MongooseModule.forRootAsync({ 9 | useFactory: (evnConfigService: EnvConfigService) => ({ 10 | uri: evnConfigService.getDbUri(), 11 | dbName: evnConfigService.getDbName(), 12 | user: evnConfigService.getDbUser(), 13 | pass: evnConfigService.getDbPassword(), 14 | autoIndex: true, 15 | }), 16 | inject: [EnvConfigService], 17 | }), 18 | ], 19 | }) 20 | export class DatabaseModule {} 21 | -------------------------------------------------------------------------------- /orders/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | database: 3 | container_name: orders-mongodb 4 | restart: always 5 | image: mongo 6 | environment: 7 | - MONGO_INITDB_ROOT_USERNAME=${DB_USER} 8 | - MONGO_INITDB_ROOT_PASSWORD=${DB_PASSWORD} 9 | networks: 10 | - orders-service-network 11 | volumes: 12 | - orders-db-volume:/data/db 13 | 14 | backend: 15 | container_name: orders-nest-api 16 | depends_on: 17 | - database 18 | restart: always 19 | build: 20 | context: . 21 | env_file: 22 | - .env 23 | networks: 24 | - orders-service-network 25 | 26 | networks: 27 | orders-service-network: 28 | 29 | volumes: 30 | orders-db-volume: 31 | -------------------------------------------------------------------------------- /orders/src/infra/database/database.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | 4 | import { EnvConfigService } from '../env-config/services/env-config-service.interface'; 5 | 6 | @Module({ 7 | imports: [ 8 | MongooseModule.forRootAsync({ 9 | useFactory: (evnConfigService: EnvConfigService) => ({ 10 | uri: evnConfigService.getDbUri(), 11 | dbName: evnConfigService.getDbName(), 12 | user: evnConfigService.getDbUser(), 13 | pass: evnConfigService.getDbPassword(), 14 | autoIndex: true, 15 | }), 16 | inject: [EnvConfigService], 17 | }), 18 | ], 19 | }) 20 | export class DatabaseModule {} 21 | -------------------------------------------------------------------------------- /products/src/infra/database/database.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | 4 | import { EnvConfigService } from '../env-config/services/env-config-service.interface'; 5 | 6 | @Module({ 7 | imports: [ 8 | MongooseModule.forRootAsync({ 9 | useFactory: (evnConfigService: EnvConfigService) => ({ 10 | uri: evnConfigService.getDbUri(), 11 | dbName: evnConfigService.getDbName(), 12 | user: evnConfigService.getDbUser(), 13 | pass: evnConfigService.getDbPassword(), 14 | autoIndex: true, 15 | }), 16 | inject: [EnvConfigService], 17 | }), 18 | ], 19 | }) 20 | export class DatabaseModule {} 21 | -------------------------------------------------------------------------------- /users/src/infra/database/database.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | 4 | import { EnvConfigService } from '@/infra/env-config/services/env-config-service.interface'; 5 | 6 | @Module({ 7 | imports: [ 8 | MongooseModule.forRootAsync({ 9 | useFactory: (evnConfigService: EnvConfigService) => ({ 10 | uri: evnConfigService.getDbUri(), 11 | dbName: evnConfigService.getDbName(), 12 | user: evnConfigService.getDbUser(), 13 | pass: evnConfigService.getDbPassword(), 14 | autoIndex: true, 15 | }), 16 | inject: [EnvConfigService], 17 | }), 18 | ], 19 | }) 20 | export class DatabaseModule {} 21 | -------------------------------------------------------------------------------- /carts/src/core/carts/entities/cart.entity.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { HydratedDocument } from 'mongoose'; 3 | 4 | import { CartProduct } from './cart-product.entity'; 5 | 6 | export type CartDocument = HydratedDocument; 7 | 8 | @Schema({ collection: 'carts' }) 9 | export class Cart { 10 | @Prop() 11 | _id: string; 12 | 13 | @Prop({ unique: true }) 14 | userId: string; 15 | 16 | @Prop({ type: [CartProduct] }) 17 | products: CartProduct[]; 18 | 19 | @Prop() 20 | productsQuantity: number; 21 | 22 | @Prop() 23 | totalPrice: number; 24 | 25 | @Prop() 26 | createdAt: Date; 27 | } 28 | 29 | export const CartSchema = SchemaFactory.createForClass(Cart); 30 | -------------------------------------------------------------------------------- /products/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | database: 3 | container_name: products-mongodb 4 | restart: always 5 | image: mongo 6 | environment: 7 | - MONGO_INITDB_ROOT_USERNAME=${DB_USER} 8 | - MONGO_INITDB_ROOT_PASSWORD=${DB_PASSWORD} 9 | networks: 10 | - products-service-network 11 | volumes: 12 | - products-db-volume:/data/db 13 | 14 | backend: 15 | container_name: products-nest-api 16 | depends_on: 17 | - database 18 | restart: always 19 | build: 20 | context: . 21 | env_file: 22 | - .env 23 | networks: 24 | - products-service-network 25 | 26 | networks: 27 | products-service-network: 28 | 29 | volumes: 30 | products-db-volume: 31 | -------------------------------------------------------------------------------- /carts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "paths": { 14 | "@/*": ["./src/*"] 15 | }, 16 | "incremental": true, 17 | "skipLibCheck": true, 18 | "strictNullChecks": true, 19 | "noImplicitAny": true, 20 | "strictBindCallApply": true, 21 | "forceConsistentCasingInFileNames": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "resolveJsonModule": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /orders/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "paths": { 14 | "@/*": ["./src/*"] 15 | }, 16 | "incremental": true, 17 | "skipLibCheck": true, 18 | "strictNullChecks": true, 19 | "noImplicitAny": true, 20 | "strictBindCallApply": true, 21 | "forceConsistentCasingInFileNames": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "resolveJsonModule": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /payments/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "paths": { 14 | "@/*": ["./src/*"] 15 | }, 16 | "incremental": true, 17 | "skipLibCheck": true, 18 | "strictNullChecks": true, 19 | "noImplicitAny": true, 20 | "strictBindCallApply": true, 21 | "forceConsistentCasingInFileNames": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "resolveJsonModule": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /products/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "paths": { 14 | "@/*": ["./src/*"] 15 | }, 16 | "incremental": true, 17 | "skipLibCheck": true, 18 | "strictNullChecks": true, 19 | "noImplicitAny": true, 20 | "strictBindCallApply": true, 21 | "forceConsistentCasingInFileNames": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "resolveJsonModule": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /users/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "paths": { 14 | "@/*": ["./src/*"] 15 | }, 16 | "incremental": true, 17 | "skipLibCheck": true, 18 | "strictNullChecks": true, 19 | "noImplicitAny": true, 20 | "strictBindCallApply": true, 21 | "forceConsistentCasingInFileNames": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "resolveJsonModule": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /notifications/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "paths": { 14 | "@/*": ["./src/*"] 15 | }, 16 | "incremental": true, 17 | "skipLibCheck": true, 18 | "strictNullChecks": true, 19 | "noImplicitAny": true, 20 | "strictBindCallApply": true, 21 | "forceConsistentCasingInFileNames": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "resolveJsonModule": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /notifications/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { LoggerModule } from 'nestjs-pino'; 3 | 4 | import { NotificationsModule } from './core/notifications/notifications.module'; 5 | import { EnvConfigModule } from './infra/env-config/env-config.module'; 6 | import { MailModule } from './infra/mail/mail.module'; 7 | import { RabbitMQModule } from './infra/rabbitmq/rabbitmq.module'; 8 | import { TemplateEngineModule } from './infra/template-engine/template-engine.module'; 9 | 10 | @Module({ 11 | imports: [ 12 | LoggerModule.forRoot(), 13 | EnvConfigModule, 14 | MailModule, 15 | RabbitMQModule, 16 | TemplateEngineModule, 17 | NotificationsModule, 18 | ], 19 | }) 20 | export class AppModule {} 21 | -------------------------------------------------------------------------------- /orders/src/infra/rabbitmq/services/payments-queue-service.interface.ts: -------------------------------------------------------------------------------- 1 | export type PaymentsQueueMessage = { 2 | user: { 3 | id: string; 4 | name: string; 5 | email: string; 6 | remoteIp: string; 7 | }; 8 | order: { 9 | id: string; 10 | price: number; 11 | products: { id: string; inCartQuantity: number }[]; 12 | }; 13 | payment: { 14 | document: string; 15 | method: string; 16 | parcels: number; 17 | card: { 18 | number: string; 19 | expiryMonth: string; 20 | holderName: string; 21 | expiryYear: string; 22 | ccv: string; 23 | }; 24 | }; 25 | }; 26 | 27 | export abstract class PaymentsQueueService { 28 | abstract publish(message: PaymentsQueueMessage): Promise; 29 | } 30 | -------------------------------------------------------------------------------- /users/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPipe } from '@nestjs/common'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { 4 | FastifyAdapter, 5 | NestFastifyApplication, 6 | } from '@nestjs/platform-fastify'; 7 | import { Logger } from 'nestjs-pino'; 8 | 9 | import { AppModule } from './app.module'; 10 | import { EnvConfigService } from './infra/env-config/services/env-config-service.interface'; 11 | 12 | async function bootstrap() { 13 | const app = await NestFactory.create( 14 | AppModule, 15 | new FastifyAdapter(), 16 | ); 17 | 18 | app.useGlobalPipes(new ValidationPipe()); 19 | app.useLogger(app.get(Logger)); 20 | 21 | await app.listen(app.get(EnvConfigService).getServerPort(), '0.0.0.0'); 22 | } 23 | bootstrap(); 24 | -------------------------------------------------------------------------------- /orders/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { LoggerModule } from 'nestjs-pino'; 3 | 4 | import { CartsModule } from './core/carts/carts.module'; 5 | import { OrdersModule } from './core/orders/orders.module'; 6 | import { UsersModule } from './core/users/users.module'; 7 | import { DatabaseModule } from './infra/database/database.module'; 8 | import { EnvConfigModule } from './infra/env-config/env-config.module'; 9 | import { RabbitMQModule } from './infra/rabbitmq/rabbitmq.module'; 10 | 11 | @Module({ 12 | imports: [ 13 | DatabaseModule, 14 | EnvConfigModule, 15 | LoggerModule.forRoot(), 16 | OrdersModule, 17 | CartsModule, 18 | UsersModule, 19 | RabbitMQModule, 20 | ], 21 | }) 22 | export class AppModule {} 23 | -------------------------------------------------------------------------------- /orders/src/core/carts/services/implementations/cart.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpService } from '@nestjs/axios'; 2 | import { Injectable } from '@nestjs/common'; 3 | 4 | import { CartDto } from '@/core/carts/dtos/cart.dto'; 5 | 6 | import { CartService } from '../cart-service.interface'; 7 | 8 | @Injectable() 9 | export class CartServiceImpl implements CartService { 10 | constructor(private readonly httpService: HttpService) {} 11 | 12 | async findByUserId(userId: string): Promise { 13 | const { data } = await this.httpService.axiosRef.get( 14 | `/carts/user/${userId}`, 15 | ); 16 | return data; 17 | } 18 | 19 | async deleteById(cartId: string): Promise { 20 | await this.httpService.axiosRef.delete(`/carts/${cartId}`); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /products/src/core/products/usecases/find-all-products.usecase.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { SortOrder } from '@/common/@types/pagination'; 4 | 5 | import { Product } from '../entities/product.entity'; 6 | import { ProductService } from '../services/product-service.interface'; 7 | 8 | @Injectable() 9 | export class FindAllProductsUseCase { 10 | constructor(private readonly productService: ProductService) {} 11 | 12 | async execute( 13 | page: number, 14 | size: number, 15 | sort: SortOrder, 16 | sortBy: keyof Product, 17 | name?: string, 18 | ) { 19 | return this.productService.findAllPaginated({ 20 | page, 21 | size, 22 | sort, 23 | sortBy, 24 | name, 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /products/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPipe } from '@nestjs/common'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { 4 | FastifyAdapter, 5 | NestFastifyApplication, 6 | } from '@nestjs/platform-fastify'; 7 | import { Logger } from 'nestjs-pino'; 8 | 9 | import { AppModule } from './app.module'; 10 | import { EnvConfigService } from './infra/env-config/services/env-config-service.interface'; 11 | 12 | async function bootstrap() { 13 | const app = await NestFactory.create( 14 | AppModule, 15 | new FastifyAdapter(), 16 | ); 17 | 18 | app.useGlobalPipes(new ValidationPipe()); 19 | app.useLogger(app.get(Logger)); 20 | 21 | await app.listen(app.get(EnvConfigService).getServerPort(), '0.0.0.0'); 22 | } 23 | bootstrap(); 24 | -------------------------------------------------------------------------------- /api-gateway/src/utils/env-config.js: -------------------------------------------------------------------------------- 1 | const getOrThrow = (name) => { 2 | const value = process.env[name]; 3 | if (!value) throw new Error(`${name} env not set`); 4 | return value; 5 | }; 6 | 7 | const getServerPort = () => getOrThrow("SERVER_PORT"); 8 | const getServerJwtSecret = () => getOrThrow("SERVER_JWT_SECRET"); 9 | 10 | const getUsersServiceUrl = () => getOrThrow("USERS_SERVICE"); 11 | const getProductsServiceUrl = () => getOrThrow("PRODUCTS_SERVICE"); 12 | const getCartsServiceUrl = () => getOrThrow("CARTS_SERVICE"); 13 | const getOrdersServiceUrl = () => getOrThrow("ORDERS_SERVICE"); 14 | 15 | module.exports = { 16 | getServerPort, 17 | getServerJwtSecret, 18 | getUsersServiceUrl, 19 | getProductsServiceUrl, 20 | getCartsServiceUrl, 21 | getOrdersServiceUrl, 22 | }; 23 | -------------------------------------------------------------------------------- /payments/src/core/payments/services/payment-service.interface.ts: -------------------------------------------------------------------------------- 1 | export type Card = { 2 | holderName: string; 3 | number: string; 4 | expiryMonth: string; 5 | expiryYear: string; 6 | ccv: string; 7 | }; 8 | 9 | export type Charge = { 10 | id: string; 11 | }; 12 | 13 | export type Customer = { 14 | id: string; 15 | }; 16 | 17 | export abstract class PaymentService { 18 | abstract createCustomer( 19 | name: string, 20 | email: string, 21 | document: string, 22 | ): Promise; 23 | abstract findCustomerByDocument(document: string): Promise; 24 | abstract createCreditCardCharge( 25 | customerId: string, 26 | remoteIp: string, 27 | price: number, 28 | parcels: number, 29 | card: Card, 30 | ): Promise; 31 | } 32 | -------------------------------------------------------------------------------- /carts/docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | services: 2 | database: 3 | container_name: carts-mongodb 4 | restart: always 5 | image: mongo 6 | environment: 7 | - MONGO_INITDB_ROOT_USERNAME=${DB_USER} 8 | - MONGO_INITDB_ROOT_PASSWORD=${DB_PASSWORD} 9 | networks: 10 | - carts-service-network 11 | volumes: 12 | - carts-db-volume:/data/db 13 | ports: 14 | - 27019:27017 15 | 16 | backend: 17 | container_name: carts-nest-api 18 | depends_on: 19 | - database 20 | restart: always 21 | build: 22 | context: . 23 | env_file: 24 | - .env 25 | networks: 26 | - carts-service-network 27 | ports: 28 | - 8081:8080 29 | 30 | networks: 31 | carts-service-network: 32 | 33 | volumes: 34 | carts-db-volume: 35 | -------------------------------------------------------------------------------- /users/docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | services: 2 | database: 3 | container_name: users-mongodb 4 | restart: always 5 | image: mongo 6 | environment: 7 | - MONGO_INITDB_ROOT_USERNAME=${DB_USER} 8 | - MONGO_INITDB_ROOT_PASSWORD=${DB_PASSWORD} 9 | networks: 10 | - users-service-network 11 | volumes: 12 | - users-db-volume:/data/db 13 | ports: 14 | - 27018:27017 15 | 16 | backend: 17 | container_name: users-nest-api 18 | depends_on: 19 | - database 20 | restart: always 21 | build: 22 | context: . 23 | env_file: 24 | - .env 25 | networks: 26 | - users-service-network 27 | ports: 28 | - 8083:8080 29 | 30 | networks: 31 | users-service-network: 32 | 33 | volumes: 34 | users-db-volume: 35 | -------------------------------------------------------------------------------- /orders/docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | services: 2 | database: 3 | container_name: orders-mongodb 4 | restart: always 5 | image: mongo 6 | environment: 7 | - MONGO_INITDB_ROOT_USERNAME=${DB_USER} 8 | - MONGO_INITDB_ROOT_PASSWORD=${DB_PASSWORD} 9 | networks: 10 | - orders-service-network 11 | volumes: 12 | - orders-db-volume:/data/db 13 | ports: 14 | - 27020:27017 15 | 16 | backend: 17 | container_name: orders-nest-api 18 | depends_on: 19 | - database 20 | restart: always 21 | build: 22 | context: . 23 | env_file: 24 | - .env 25 | networks: 26 | - orders-service-network 27 | ports: 28 | - 8084:8080 29 | 30 | networks: 31 | orders-service-network: 32 | 33 | volumes: 34 | orders-db-volume: 35 | -------------------------------------------------------------------------------- /payments/src/infra/rabbitmq/services/payments-queue-service.interface.ts: -------------------------------------------------------------------------------- 1 | export type PaymentsQueueMessage = { 2 | user: { 3 | id: string; 4 | name: string; 5 | email: string; 6 | remoteIp: string; 7 | }; 8 | order: { 9 | id: string; 10 | price: number; 11 | products: { id: string; inCartQuantity: number }[]; 12 | }; 13 | payment: { 14 | document: string; 15 | method: string; 16 | parcels: number; 17 | card: { 18 | number: string; 19 | expiryMonth: string; 20 | holderName: string; 21 | expiryYear: string; 22 | ccv: string; 23 | }; 24 | }; 25 | }; 26 | 27 | export abstract class PaymentsQueueService { 28 | abstract consume( 29 | onMessage: (msg: PaymentsQueueMessage) => void, 30 | ): Promise; 31 | } 32 | -------------------------------------------------------------------------------- /products/src/infra/rabbitmq/rabbitmq.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { OrderStatusEventQueueServiceImpl } from './services/implementations/order-status-event-queue.service'; 4 | import { RabbitMQServiceImpl } from './services/implementations/rabbitmq.service'; 5 | import { OrderStatusEventQueueService } from './services/order-status-event-queue-service.interface'; 6 | import { RabbitMQService } from './services/rabbitmq-service.interface'; 7 | 8 | @Module({ 9 | providers: [ 10 | { provide: RabbitMQService, useClass: RabbitMQServiceImpl }, 11 | { 12 | provide: OrderStatusEventQueueService, 13 | useClass: OrderStatusEventQueueServiceImpl, 14 | }, 15 | ], 16 | exports: [OrderStatusEventQueueService], 17 | }) 18 | export class RabbitMQModule {} 19 | -------------------------------------------------------------------------------- /notifications/src/infra/rabbitmq/rabbitmq.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { OrderStatusEventQueueServiceImpl } from './services/implementations/order-status-event-queue.service'; 4 | import { RabbitMQServiceImpl } from './services/implementations/rabbitmq.service'; 5 | import { OrderStatusEventQueueService } from './services/order-status-event-queue-service.interface'; 6 | import { RabbitMQService } from './services/rabbitmq-service.interface'; 7 | 8 | @Module({ 9 | providers: [ 10 | { provide: RabbitMQService, useClass: RabbitMQServiceImpl }, 11 | { 12 | provide: OrderStatusEventQueueService, 13 | useClass: OrderStatusEventQueueServiceImpl, 14 | }, 15 | ], 16 | exports: [OrderStatusEventQueueService], 17 | }) 18 | export class RabbitMQModule {} 19 | -------------------------------------------------------------------------------- /products/docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | services: 2 | database: 3 | container_name: products-mongodb 4 | restart: always 5 | image: mongo 6 | environment: 7 | - MONGO_INITDB_ROOT_USERNAME=${DB_USER} 8 | - MONGO_INITDB_ROOT_PASSWORD=${DB_PASSWORD} 9 | networks: 10 | - products-service-network 11 | volumes: 12 | - products-db-volume:/data/db 13 | ports: 14 | - 27017:27017 15 | 16 | backend: 17 | container_name: products-nest-api 18 | depends_on: 19 | - database 20 | restart: always 21 | build: 22 | context: . 23 | env_file: 24 | - .env 25 | networks: 26 | - products-service-network 27 | ports: 28 | - 8082:8080 29 | 30 | networks: 31 | products-service-network: 32 | 33 | volumes: 34 | products-db-volume: 35 | -------------------------------------------------------------------------------- /orders/src/core/orders/usecases/find-all-orders.usecase.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { SortOrder } from '@/common/@types/pagination'; 4 | 5 | import { Order } from '../entities/order.entity'; 6 | import { ORDER_STATUS } from '../enums/order-status.enum'; 7 | import { OrderService } from '../services/order-service.interface'; 8 | 9 | @Injectable() 10 | export class FindAllOrdersUseCase { 11 | constructor(private readonly orderService: OrderService) {} 12 | 13 | async execute( 14 | page: number, 15 | size: number, 16 | sort: SortOrder, 17 | sortBy: keyof Order, 18 | status?: keyof typeof ORDER_STATUS, 19 | ) { 20 | return this.orderService.findAllPaginated({ 21 | page, 22 | size, 23 | sort, 24 | sortBy, 25 | status, 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /notifications/src/infra/template-engine/services/implementations/template-engine.service.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from 'node:fs/promises'; 2 | import { join } from 'node:path'; 3 | 4 | import { Injectable } from '@nestjs/common'; 5 | import { compile } from 'handlebars'; 6 | 7 | import { TemplateEngineService } from '../template-engine-service.interface'; 8 | 9 | @Injectable() 10 | export class TemplateEngineServiceImpl implements TemplateEngineService { 11 | async compile( 12 | filename: string, 13 | variables?: { [key: string]: string | object }, 14 | ): Promise { 15 | const workdir = process.cwd(); 16 | 17 | const html = ( 18 | await readFile(join(workdir, 'templates', filename)) 19 | ).toString(); 20 | 21 | const template = compile(html); 22 | 23 | return template(variables); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /products/src/core/products/services/product-service.interface.ts: -------------------------------------------------------------------------------- 1 | import { Page, Params } from '@/common/@types/pagination'; 2 | 3 | import { Product } from '../entities/product.entity'; 4 | 5 | export abstract class ProductService { 6 | abstract findAllPaginated(params: Params): Promise>; 7 | 8 | abstract create( 9 | price: number, 10 | name: string, 11 | description: string, 12 | photoUrl: string, 13 | stockQuantity: number, 14 | ): Promise; 15 | 16 | abstract update( 17 | productId: string, 18 | fields: Partial>, 19 | ): Promise; 20 | 21 | abstract findByName(name: string): Promise; 22 | abstract findById(productId: string): Promise; 23 | abstract delete(productId: string): Promise; 24 | } 25 | -------------------------------------------------------------------------------- /users/src/core/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | 4 | import { UsersController } from './controllers/users.controller'; 5 | import { User, UserSchema } from './entities/user.entity'; 6 | import { UserServiceImpl } from './services/implementations/user.service'; 7 | import { UserService } from './services/user-service.interface'; 8 | import { FindUserByIdUseCase } from './usecases/find-user-by-id.usecase'; 9 | 10 | @Module({ 11 | imports: [ 12 | MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), 13 | ], 14 | providers: [ 15 | { provide: UserService, useClass: UserServiceImpl }, 16 | FindUserByIdUseCase, 17 | ], 18 | controllers: [UsersController], 19 | exports: [UserService], 20 | }) 21 | export class UsersModule {} 22 | -------------------------------------------------------------------------------- /orders/src/core/carts/carts.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule } from '@nestjs/axios'; 2 | import { Module } from '@nestjs/common'; 3 | 4 | import { EnvConfigService } from '@/infra/env-config/services/env-config-service.interface'; 5 | 6 | import { CartService } from './services/cart-service.interface'; 7 | import { CartServiceImpl } from './services/implementations/cart.service'; 8 | 9 | @Module({ 10 | imports: [ 11 | HttpModule.registerAsync({ 12 | useFactory: (envConfigService: EnvConfigService) => ({ 13 | timeout: 5000, 14 | maxRedirects: 3, 15 | baseURL: envConfigService.getCartServiceUrl(), 16 | }), 17 | inject: [EnvConfigService], 18 | }), 19 | ], 20 | providers: [{ provide: CartService, useClass: CartServiceImpl }], 21 | exports: [CartService], 22 | }) 23 | export class CartsModule {} 24 | -------------------------------------------------------------------------------- /orders/src/core/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule } from '@nestjs/axios'; 2 | import { Module } from '@nestjs/common'; 3 | 4 | import { EnvConfigService } from '@/infra/env-config/services/env-config-service.interface'; 5 | 6 | import { UserServiceImpl } from './services/implementations/user.service'; 7 | import { UserService } from './services/user-service.interface'; 8 | 9 | @Module({ 10 | imports: [ 11 | HttpModule.registerAsync({ 12 | useFactory: (envConfigService: EnvConfigService) => ({ 13 | timeout: 5000, 14 | maxRedirects: 3, 15 | baseURL: envConfigService.getUserServiceUrl(), 16 | }), 17 | inject: [EnvConfigService], 18 | }), 19 | ], 20 | providers: [{ provide: UserService, useClass: UserServiceImpl }], 21 | exports: [UserService], 22 | }) 23 | export class UsersModule {} 24 | -------------------------------------------------------------------------------- /orders/src/core/orders/entities/order.entity.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { HydratedDocument } from 'mongoose'; 3 | 4 | import { ORDER_STATUS } from '../enums/order-status.enum'; 5 | import { OrderCart } from './order-cart.entity'; 6 | import { Payment } from './payment.entity'; 7 | 8 | export type OrderDocument = HydratedDocument; 9 | 10 | @Schema({ collection: 'orders' }) 11 | export class Order { 12 | @Prop() 13 | _id: string; 14 | 15 | @Prop() 16 | userId: string; 17 | 18 | @Prop({ type: String }) 19 | status: ORDER_STATUS; 20 | 21 | @Prop({ type: OrderCart }) 22 | cart: OrderCart; 23 | 24 | @Prop({ type: Payment }) 25 | payment: Payment | null; 26 | 27 | @Prop() 28 | createdAt: Date; 29 | } 30 | 31 | export const OrderSchema = SchemaFactory.createForClass(Order); 32 | -------------------------------------------------------------------------------- /api-gateway/src/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | const { getUsersServiceUrl } = require("../utils/env-config"); 2 | const { httpMethods } = require("../utils/http-methods"); 3 | 4 | /** 5 | * List of allowed authentication routes with their respective HTTP methods. 6 | * 7 | * @constant 8 | * @type {Array<{ path: string, method: string }>} 9 | * @property {string} path - The URL path of the route. 10 | * @property {string} method - The HTTP method allowed for the route (e.g., "GET", "POST"). 11 | */ 12 | const authAllowedRoutes = [ 13 | { 14 | path: "/auth/login", 15 | method: httpMethods.post, 16 | }, 17 | { 18 | path: "/auth/signup", 19 | method: httpMethods.post, 20 | }, 21 | ]; 22 | const authPrefix = "/auth"; 23 | const authServiceUrl = getUsersServiceUrl(); 24 | 25 | module.exports = { authAllowedRoutes, authPrefix, authServiceUrl }; 26 | -------------------------------------------------------------------------------- /products/src/core/products/usecases/create-product.usecase.ts: -------------------------------------------------------------------------------- 1 | import { ConflictException, Injectable } from '@nestjs/common'; 2 | 3 | import { ProductService } from '../services/product-service.interface'; 4 | 5 | @Injectable() 6 | export class CreateProductUseCase { 7 | constructor(private readonly productService: ProductService) {} 8 | 9 | async execute( 10 | price: number, 11 | name: string, 12 | description: string, 13 | photoUrl: string, 14 | stockQuantity: number, 15 | ) { 16 | const product = await this.productService.findByName(name); 17 | 18 | if (product) { 19 | throw new ConflictException(`Product with name:${name} already exists`); 20 | } 21 | 22 | await this.productService.create( 23 | price, 24 | name, 25 | description, 26 | photoUrl, 27 | stockQuantity, 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /carts/src/core/products/products.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule } from '@nestjs/axios'; 2 | import { Module } from '@nestjs/common'; 3 | 4 | import { EnvConfigService } from '@/infra/env-config/services/env-config-service.interface'; 5 | 6 | import { ProductServiceImpl } from './services/implementations/product.service'; 7 | import { ProductService } from './services/product-service.interface'; 8 | 9 | @Module({ 10 | imports: [ 11 | HttpModule.registerAsync({ 12 | useFactory: (envConfigService: EnvConfigService) => ({ 13 | timeout: 5000, 14 | maxRedirects: 3, 15 | baseURL: envConfigService.getProductsServiceUrl(), 16 | }), 17 | inject: [EnvConfigService], 18 | }), 19 | ], 20 | providers: [{ provide: ProductService, useClass: ProductServiceImpl }], 21 | exports: [ProductService], 22 | }) 23 | export class ProductsModule {} 24 | -------------------------------------------------------------------------------- /payments/src/core/products/products.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule } from '@nestjs/axios'; 2 | import { Module } from '@nestjs/common'; 3 | 4 | import { EnvConfigService } from '@/infra/env-config/services/env-config-service.interface'; 5 | 6 | import { ProductServiceImpl } from './services/implementations/product.service'; 7 | import { ProductService } from './services/product-service.interface'; 8 | 9 | @Module({ 10 | imports: [ 11 | HttpModule.registerAsync({ 12 | useFactory: (envConfigService: EnvConfigService) => ({ 13 | timeout: 5000, 14 | maxRedirects: 3, 15 | baseURL: envConfigService.getProductServiceUrl(), 16 | }), 17 | inject: [EnvConfigService], 18 | }), 19 | ], 20 | providers: [{ provide: ProductService, useClass: ProductServiceImpl }], 21 | exports: [ProductService], 22 | }) 23 | export class ProductsModule {} 24 | -------------------------------------------------------------------------------- /orders/src/core/orders/usecases/find-all-user-orders.usecase.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { SortOrder } from '@/common/@types/pagination'; 4 | 5 | import { Order } from '../entities/order.entity'; 6 | import { ORDER_STATUS } from '../enums/order-status.enum'; 7 | import { OrderService } from '../services/order-service.interface'; 8 | 9 | @Injectable() 10 | export class FindAllUserOrdersUseCase { 11 | constructor(private readonly orderService: OrderService) {} 12 | 13 | async execute( 14 | page: number, 15 | size: number, 16 | sort: SortOrder, 17 | sortBy: keyof Order, 18 | userId: string, 19 | status?: keyof typeof ORDER_STATUS, 20 | ) { 21 | return this.orderService.findAllPaginated({ 22 | page, 23 | size, 24 | sort, 25 | sortBy, 26 | userId, 27 | status, 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /users/src/core/auth/usecases/signup.usecase.ts: -------------------------------------------------------------------------------- 1 | import { ConflictException, Injectable } from '@nestjs/common'; 2 | 3 | import { UserService } from '@/core/users/services/user-service.interface'; 4 | 5 | import { HashService } from '../services/hash-service.interface'; 6 | 7 | @Injectable() 8 | export class SignupUseCase { 9 | constructor( 10 | private readonly userService: UserService, 11 | private readonly hashService: HashService, 12 | ) {} 13 | 14 | async execute(username: string, email: string, password: string) { 15 | const user = await this.userService.findByEmail(email); 16 | 17 | if (user) { 18 | throw new ConflictException('Email already in use by another user'); 19 | } 20 | 21 | const hashedPassword = await this.hashService.hash(password); 22 | 23 | await this.userService.createClient(username, email, hashedPassword); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /notifications/src/infra/env-config/services/implementations/env-config.service.ts: -------------------------------------------------------------------------------- 1 | import { Global, Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | 4 | import { EnvConfigService } from '../env-config-service.interface'; 5 | 6 | @Global() 7 | @Injectable() 8 | export class EnvConfigServiceImpl implements EnvConfigService { 9 | constructor(private readonly configService: ConfigService) {} 10 | 11 | getRabbitMQUrl(): string { 12 | return this.configService.getOrThrow('RABBIT_MQ_URL'); 13 | } 14 | 15 | getMailHostName(): string { 16 | return this.configService.getOrThrow('MAIL_HOST_NAME'); 17 | } 18 | 19 | getMailHostUser(): string { 20 | return this.configService.getOrThrow('MAIL_HOST_USER'); 21 | } 22 | 23 | getMailHostPassword(): string { 24 | return this.configService.getOrThrow('MAIL_HOST_PASSWORD'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /orders/src/core/orders/usecases/cancel-order.usecase.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BadRequestException, 3 | Injectable, 4 | NotFoundException, 5 | } from '@nestjs/common'; 6 | 7 | import { ORDER_STATUS } from '../enums/order-status.enum'; 8 | import { OrderService } from '../services/order-service.interface'; 9 | 10 | @Injectable() 11 | export class CancelOrderUseCase { 12 | constructor(private readonly orderService: OrderService) {} 13 | 14 | async execute(orderId: string, userId: string) { 15 | const order = await this.orderService.findByIdAndUserId(orderId, userId); 16 | 17 | if (!order) { 18 | throw new NotFoundException('Order not found'); 19 | } 20 | 21 | if (order.status !== ORDER_STATUS.WAITING_PAYMENT) { 22 | throw new BadRequestException('Only pending orders can be canceled'); 23 | } 24 | 25 | await this.orderService.delete(order._id); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /payments/src/infra/env-config/services/implementations/env-config.service.ts: -------------------------------------------------------------------------------- 1 | import { Global, Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | 4 | import { EnvConfigService } from '../env-config-service.interface'; 5 | 6 | @Global() 7 | @Injectable() 8 | export class EnvConfigServiceImpl implements EnvConfigService { 9 | constructor(private readonly configService: ConfigService) {} 10 | 11 | getProductServiceUrl(): string { 12 | return this.configService.getOrThrow('PRODUCT_SERVICE_URL'); 13 | } 14 | 15 | getRabbitMQUrl(): string { 16 | return this.configService.getOrThrow('RABBIT_MQ_URL'); 17 | } 18 | 19 | getPaymentGatewayUrl(): string { 20 | return this.configService.getOrThrow('PAYMENT_GW_URL'); 21 | } 22 | 23 | getPaymentGatewayKey(): string { 24 | return this.configService.getOrThrow('PAYMENT_GW_KEY'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /orders/src/core/orders/dtos/pay-order-with-credit-card.dto.ts: -------------------------------------------------------------------------------- 1 | import { Type } from 'class-transformer'; 2 | import { 3 | IsInt, 4 | IsPositive, 5 | IsString, 6 | Length, 7 | Max, 8 | ValidateNested, 9 | } from 'class-validator'; 10 | 11 | export class CardDto { 12 | @IsString() 13 | @Length(3, 55) 14 | holderName: string; 15 | 16 | @IsString() 17 | @Length(16, 16) 18 | number: string; 19 | 20 | @IsString() 21 | @Length(2, 2) 22 | expiryMonth: string; 23 | 24 | @IsString() 25 | @Length(4, 4) 26 | expiryYear: string; 27 | 28 | @IsString() 29 | @Length(3, 3) 30 | ccv: string; 31 | } 32 | 33 | export class PayOrderWithCreditCardDto { 34 | @IsString() 35 | @Length(11, 11) 36 | document: string; 37 | 38 | @IsPositive() 39 | @IsInt() 40 | @Max(12) 41 | parcels: number; 42 | 43 | @ValidateNested() 44 | @Type(() => CardDto) 45 | card: CardDto; 46 | } 47 | -------------------------------------------------------------------------------- /carts/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPipe } from '@nestjs/common'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { 4 | FastifyAdapter, 5 | NestFastifyApplication, 6 | } from '@nestjs/platform-fastify'; 7 | import { Logger } from 'nestjs-pino'; 8 | 9 | import { AppModule } from './app.module'; 10 | import { AxiosExceptionFilter } from './common/filters/axios-exception.filter'; 11 | import { EnvConfigService } from './infra/env-config/services/env-config-service.interface'; 12 | 13 | async function bootstrap() { 14 | const app = await NestFactory.create( 15 | AppModule, 16 | new FastifyAdapter(), 17 | ); 18 | 19 | app.useGlobalFilters(new AxiosExceptionFilter()); 20 | app.useGlobalPipes(new ValidationPipe()); 21 | app.useLogger(app.get(Logger)); 22 | 23 | await app.listen(app.get(EnvConfigService).getServerPort(), '0.0.0.0'); 24 | } 25 | bootstrap(); 26 | -------------------------------------------------------------------------------- /orders/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPipe } from '@nestjs/common'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { 4 | FastifyAdapter, 5 | NestFastifyApplication, 6 | } from '@nestjs/platform-fastify'; 7 | import { Logger } from 'nestjs-pino'; 8 | 9 | import { AppModule } from './app.module'; 10 | import { AxiosExceptionFilter } from './common/filters/axios-exception.filter'; 11 | import { EnvConfigService } from './infra/env-config/services/env-config-service.interface'; 12 | 13 | async function bootstrap() { 14 | const app = await NestFactory.create( 15 | AppModule, 16 | new FastifyAdapter(), 17 | ); 18 | 19 | app.useGlobalFilters(new AxiosExceptionFilter()); 20 | app.useGlobalPipes(new ValidationPipe()); 21 | app.useLogger(app.get(Logger)); 22 | 23 | await app.listen(app.get(EnvConfigService).getServerPort(), '0.0.0.0'); 24 | } 25 | bootstrap(); 26 | -------------------------------------------------------------------------------- /products/src/core/products/usecases/update-product.usecase.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestException, Injectable } from '@nestjs/common'; 2 | 3 | import { ProductService } from '../services/product-service.interface'; 4 | 5 | @Injectable() 6 | export class UpdateProductUseCase { 7 | constructor(private readonly productService: ProductService) {} 8 | 9 | async execute( 10 | productId: string, 11 | price: number, 12 | name: string, 13 | description: string, 14 | photoUrl: string, 15 | stockQuantity: number, 16 | ) { 17 | const product = await this.productService.findByName(name); 18 | 19 | if (product && product._id !== productId) { 20 | throw new BadRequestException('Product name already exists'); 21 | } 22 | 23 | await this.productService.update(productId, { 24 | price, 25 | name, 26 | description, 27 | photoUrl, 28 | stockQuantity, 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /carts/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | 38 | # dotenv environment variable files 39 | .env 40 | .env.development.local 41 | .env.test.local 42 | .env.production.local 43 | .env.local 44 | 45 | # temp directory 46 | .temp 47 | .tmp 48 | 49 | # Runtime data 50 | pids 51 | *.pid 52 | *.seed 53 | *.pid.lock 54 | 55 | # Diagnostic reports (https://nodejs.org/api/report.html) 56 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 57 | -------------------------------------------------------------------------------- /users/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | 38 | # dotenv environment variable files 39 | .env 40 | .env.development.local 41 | .env.test.local 42 | .env.production.local 43 | .env.local 44 | 45 | # temp directory 46 | .temp 47 | .tmp 48 | 49 | # Runtime data 50 | pids 51 | *.pid 52 | *.seed 53 | *.pid.lock 54 | 55 | # Diagnostic reports (https://nodejs.org/api/report.html) 56 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 57 | -------------------------------------------------------------------------------- /carts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin', 'simple-import-sort'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | 'simple-import-sort/imports': 'error', 25 | 'simple-import-sort/exports': 'error', 26 | 'no-console': ['warn', { allow: ['warn', 'error'] }], 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /orders/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | 38 | # dotenv environment variable files 39 | .env 40 | .env.development.local 41 | .env.test.local 42 | .env.production.local 43 | .env.local 44 | 45 | # temp directory 46 | .temp 47 | .tmp 48 | 49 | # Runtime data 50 | pids 51 | *.pid 52 | *.seed 53 | *.pid.lock 54 | 55 | # Diagnostic reports (https://nodejs.org/api/report.html) 56 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 57 | -------------------------------------------------------------------------------- /payments/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | 38 | # dotenv environment variable files 39 | .env 40 | .env.development.local 41 | .env.test.local 42 | .env.production.local 43 | .env.local 44 | 45 | # temp directory 46 | .temp 47 | .tmp 48 | 49 | # Runtime data 50 | pids 51 | *.pid 52 | *.seed 53 | *.pid.lock 54 | 55 | # Diagnostic reports (https://nodejs.org/api/report.html) 56 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 57 | -------------------------------------------------------------------------------- /products/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | 38 | # dotenv environment variable files 39 | .env 40 | .env.development.local 41 | .env.test.local 42 | .env.production.local 43 | .env.local 44 | 45 | # temp directory 46 | .temp 47 | .tmp 48 | 49 | # Runtime data 50 | pids 51 | *.pid 52 | *.seed 53 | *.pid.lock 54 | 55 | # Diagnostic reports (https://nodejs.org/api/report.html) 56 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 57 | -------------------------------------------------------------------------------- /users/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin', 'simple-import-sort'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | 'simple-import-sort/imports': 'error', 25 | 'simple-import-sort/exports': 'error', 26 | 'no-console': ['warn', { allow: ['warn', 'error'] }], 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /notifications/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | 38 | # dotenv environment variable files 39 | .env 40 | .env.development.local 41 | .env.test.local 42 | .env.production.local 43 | .env.local 44 | 45 | # temp directory 46 | .temp 47 | .tmp 48 | 49 | # Runtime data 50 | pids 51 | *.pid 52 | *.seed 53 | *.pid.lock 54 | 55 | # Diagnostic reports (https://nodejs.org/api/report.html) 56 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 57 | -------------------------------------------------------------------------------- /orders/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin', 'simple-import-sort'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | 'simple-import-sort/imports': 'error', 25 | 'simple-import-sort/exports': 'error', 26 | 'no-console': ['warn', { allow: ['warn', 'error'] }], 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /payments/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin', 'simple-import-sort'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | 'simple-import-sort/imports': 'error', 25 | 'simple-import-sort/exports': 'error', 26 | 'no-console': ['warn', { allow: ['warn', 'error'] }], 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /products/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin', 'simple-import-sort'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | 'simple-import-sort/imports': 'error', 25 | 'simple-import-sort/exports': 'error', 26 | 'no-console': ['warn', { allow: ['warn', 'error'] }], 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /notifications/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin', 'simple-import-sort'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | 'simple-import-sort/imports': 'error', 25 | 'simple-import-sort/exports': 'error', 26 | 'no-console': ['warn', { allow: ['warn', 'error'] }], 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /notifications/src/core/notifications/notifications.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { MailModule } from '@/infra/mail/mail.module'; 4 | import { RabbitMQModule } from '@/infra/rabbitmq/rabbitmq.module'; 5 | import { TemplateEngineModule } from '@/infra/template-engine/template-engine.module'; 6 | 7 | import { SendEmailAfterOrderCreationUseCase } from './usecases/send-email-after-order-creation.usecase'; 8 | import { SendEmailAfterSuccessfulPaymentUseCase } from './usecases/send-email-after-successful-payment.usecase'; 9 | import { SendEmailAfterUnsuccessfulPaymentUseCase } from './usecases/send-email-after-unsuccessful-payment.usecase'; 10 | 11 | @Module({ 12 | imports: [RabbitMQModule, MailModule, TemplateEngineModule], 13 | providers: [ 14 | SendEmailAfterOrderCreationUseCase, 15 | SendEmailAfterSuccessfulPaymentUseCase, 16 | SendEmailAfterUnsuccessfulPaymentUseCase, 17 | ], 18 | }) 19 | export class NotificationsModule {} 20 | -------------------------------------------------------------------------------- /orders/src/core/orders/mappers/cart-product.mapper.ts: -------------------------------------------------------------------------------- 1 | import { CartProductDto } from '@/core/carts/dtos/cart-product.dto'; 2 | 3 | import { CartProduct } from '../entities/cart-product.entity'; 4 | 5 | export class CartProductMapper { 6 | static toEntity(dto: CartProductDto): CartProduct { 7 | const entity = new CartProduct(); 8 | entity._id = dto.id; 9 | entity.description = dto.description; 10 | entity.inCartQuantity = dto.inCartQuantity; 11 | entity.name = dto.name; 12 | entity.photoUrl = dto.photoUrl; 13 | entity.price = dto.price; 14 | return entity; 15 | } 16 | 17 | static toDto(entity: CartProduct): CartProductDto { 18 | const dto = new CartProductDto(); 19 | dto.id = entity._id; 20 | dto.price = entity.price; 21 | dto.name = entity.name; 22 | dto.description = entity.description; 23 | dto.photoUrl = entity.photoUrl; 24 | dto.inCartQuantity = entity.inCartQuantity; 25 | return dto; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /carts/src/common/filters/axios-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArgumentsHost, 3 | Catch, 4 | ExceptionFilter, 5 | HttpException, 6 | HttpExceptionBody, 7 | HttpStatus, 8 | } from '@nestjs/common'; 9 | import { AxiosError } from 'axios'; 10 | import { FastifyReply } from 'fastify'; 11 | 12 | @Catch(AxiosError) 13 | export class AxiosExceptionFilter implements ExceptionFilter { 14 | catch(exception: AxiosError, host: ArgumentsHost) { 15 | const ctx = host.switchToHttp(); 16 | const response = ctx.getResponse(); 17 | 18 | const status = 19 | exception.response?.status || HttpStatus.INTERNAL_SERVER_ERROR; 20 | 21 | const payload: HttpExceptionBody = { 22 | statusCode: status, 23 | message: 24 | exception.response?.data.message || 'An unexpected error occurred', 25 | error: exception.response?.statusText || 'Error', 26 | }; 27 | 28 | response.status(status).send(payload); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /orders/src/common/filters/axios-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArgumentsHost, 3 | Catch, 4 | ExceptionFilter, 5 | HttpException, 6 | HttpExceptionBody, 7 | HttpStatus, 8 | } from '@nestjs/common'; 9 | import { AxiosError } from 'axios'; 10 | import { FastifyReply } from 'fastify'; 11 | 12 | @Catch(AxiosError) 13 | export class AxiosExceptionFilter implements ExceptionFilter { 14 | catch(exception: AxiosError, host: ArgumentsHost) { 15 | const ctx = host.switchToHttp(); 16 | const response = ctx.getResponse(); 17 | 18 | const status = 19 | exception.response?.status || HttpStatus.INTERNAL_SERVER_ERROR; 20 | 21 | const payload: HttpExceptionBody = { 22 | statusCode: status, 23 | message: 24 | exception.response?.data.message || 'An unexpected error occurred', 25 | error: exception.response?.statusText || 'Error', 26 | }; 27 | 28 | response.status(status).send(payload); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /orders/src/core/orders/mappers/order-cart.mapper.ts: -------------------------------------------------------------------------------- 1 | import { CartDto } from '@/core/carts/dtos/cart.dto'; 2 | 3 | import { OrderCart } from '../entities/order-cart.entity'; 4 | import { CartProductMapper } from './cart-product.mapper'; 5 | 6 | export class OrderCartMapper { 7 | static toDto(entity: OrderCart): CartDto { 8 | const dto = new CartDto(); 9 | 10 | dto.id = entity._id; 11 | dto.products = entity.products.map(CartProductMapper.toDto); 12 | dto.productsQuantity = entity.productsQuantity; 13 | dto.totalPrice = entity.totalPrice; 14 | dto.createdAt = entity.createdAt.toISOString(); 15 | 16 | return dto; 17 | } 18 | 19 | static toEntity(dto: CartDto): OrderCart { 20 | const entity = new OrderCart(); 21 | entity._id = dto.id; 22 | entity.createdAt = new Date(dto.createdAt); 23 | entity.products = dto.products.map(CartProductMapper.toEntity); 24 | entity.productsQuantity = dto.productsQuantity; 25 | entity.totalPrice = dto.totalPrice; 26 | return entity; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /users/src/core/auth/services/implementations/jwt.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { JwtService as NestJwtService } from '@nestjs/jwt'; 3 | 4 | import { User } from '@/core/users/entities/user.entity'; 5 | import { EnvConfigService } from '@/infra/env-config/services/env-config-service.interface'; 6 | 7 | import { JwtPayload, JwtService } from '../jwt-service.interface'; 8 | 9 | @Injectable() 10 | export class JwtServiceImpl implements JwtService { 11 | private readonly jwtSecret: string; 12 | 13 | constructor( 14 | private readonly nestJwtService: NestJwtService, 15 | envConfigService: EnvConfigService, 16 | ) { 17 | this.jwtSecret = envConfigService.getServerJwtSecret(); 18 | } 19 | 20 | generateToken(user: User): Promise { 21 | const payload: JwtPayload = { 22 | roles: user.roles, 23 | userId: user._id, 24 | }; 25 | 26 | return this.nestJwtService.signAsync(payload, { 27 | secret: this.jwtSecret, 28 | expiresIn: '4h', 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /orders/src/infra/rabbitmq/rabbitmq.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { OrderStatusEventQueueServiceImpl } from './services/implementations/order-status-event-queue.service'; 4 | import { PaymentsQueueServiceImpl } from './services/implementations/payments-queue.service'; 5 | import { RabbitMQServiceImpl } from './services/implementations/rabbitmq.service'; 6 | import { OrderStatusEventQueueService } from './services/order-status-event-queue-service.interface'; 7 | import { PaymentsQueueService } from './services/payments-queue-service.interface'; 8 | import { RabbitMQService } from './services/rabbitmq-service.interface'; 9 | 10 | @Module({ 11 | providers: [ 12 | { provide: RabbitMQService, useClass: RabbitMQServiceImpl }, 13 | { 14 | provide: OrderStatusEventQueueService, 15 | useClass: OrderStatusEventQueueServiceImpl, 16 | }, 17 | { provide: PaymentsQueueService, useClass: PaymentsQueueServiceImpl }, 18 | ], 19 | exports: [OrderStatusEventQueueService, PaymentsQueueService], 20 | }) 21 | export class RabbitMQModule {} 22 | -------------------------------------------------------------------------------- /payments/src/infra/rabbitmq/rabbitmq.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { OrderStatusEventQueueServiceImpl } from './services/implementations/order-status-event-queue.service'; 4 | import { PaymentsQueueServiceImpl } from './services/implementations/payments-queue.service'; 5 | import { RabbitMQServiceImpl } from './services/implementations/rabbitmq.service'; 6 | import { OrderStatusEventQueueService } from './services/order-status-event-queue-service.interface'; 7 | import { PaymentsQueueService } from './services/payments-queue-service.interface'; 8 | import { RabbitMQService } from './services/rabbitmq-service.interface'; 9 | 10 | @Module({ 11 | providers: [ 12 | { provide: RabbitMQService, useClass: RabbitMQServiceImpl }, 13 | { 14 | provide: OrderStatusEventQueueService, 15 | useClass: OrderStatusEventQueueServiceImpl, 16 | }, 17 | { provide: PaymentsQueueService, useClass: PaymentsQueueServiceImpl }, 18 | ], 19 | exports: [OrderStatusEventQueueService, PaymentsQueueService], 20 | }) 21 | export class RabbitMQModule {} 22 | -------------------------------------------------------------------------------- /orders/src/core/orders/factories/payment.factory.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from 'node:crypto'; 2 | 3 | import { Payment } from '../entities/payment.entity'; 4 | import { PAYMENT_METHOD } from '../enums/payment-method.enum'; 5 | 6 | export class PaymentFactory { 7 | static createForCreditCard( 8 | transactionCode: string, 9 | price: number, 10 | parcels: number, 11 | ): Payment { 12 | const entity = new Payment(); 13 | entity._id = randomUUID(); 14 | entity.createdAt = new Date(); 15 | entity.method = PAYMENT_METHOD.CREDIT_CARD; 16 | entity.parcels = parcels; 17 | entity.price = price; 18 | entity.transactionCode = transactionCode; 19 | return entity; 20 | } 21 | 22 | static createForDebitCard(transactionCode: string, price: number) { 23 | const entity = new Payment(); 24 | entity._id = randomUUID(); 25 | entity.createdAt = new Date(); 26 | entity.method = PAYMENT_METHOD.DEBIT_CARD; 27 | entity.parcels = 0; 28 | entity.price = price; 29 | entity.transactionCode = transactionCode; 30 | return entity; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /users/src/core/auth/usecases/login.usecase.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 2 | 3 | import { UserService } from '@/core/users/services/user-service.interface'; 4 | 5 | import { HashService } from '../services/hash-service.interface'; 6 | import { JwtService } from '../services/jwt-service.interface'; 7 | 8 | @Injectable() 9 | export class LoginUseCase { 10 | constructor( 11 | private readonly userService: UserService, 12 | private readonly hashService: HashService, 13 | private readonly jwtService: JwtService, 14 | ) {} 15 | 16 | async execute(email: string, password: string) { 17 | const user = await this.userService.findByEmail(email); 18 | 19 | if (!user) { 20 | throw new UnauthorizedException('Invalid email or password'); 21 | } 22 | 23 | const isValidPassword = await this.hashService.compare( 24 | password, 25 | user.password, 26 | ); 27 | 28 | if (!isValidPassword) { 29 | throw new UnauthorizedException('Invalid email or password'); 30 | } 31 | 32 | return this.jwtService.generateToken(user); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rabbit-mq/README.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ 2 | 3 | This is just a folder with some docker-compose files to you run locally on your machine. 4 | 5 | ## Starting RabbitMQ 6 | 7 | ### 1. Start container 8 | 9 | Just run: 10 | 11 | ``` 12 | docker compose up -d 13 | ``` 14 | 15 | If you wanna access the RabbitMQ dashboard on browser run the **docker-compose.dev.yml** 16 | 17 | ### 2. Connect to private network 18 | 19 | All the containers will run on the same docker network called **ecommerce-soa**. 20 | 21 | If you haven't created it yet just run: 22 | 23 | ``` 24 | docker network create ecommerce-soa 25 | ``` 26 | 27 | If you already done this step, just connect with it 28 | 29 | ``` 30 | docker network connect ecommerce-soa rabbitmq 31 | ``` 32 | 33 | ## Reminder 34 | 35 | Of course in a real world situation we don't wanna run all the services in the same machine, that is the opposite of a distribute service oriented architecture, doing that you just increase the project complexity and don't gain the benefits of this architecture. 36 | 37 | I'm doing this because I value my money and don't want to owe the value of a house to AWS services. 38 | -------------------------------------------------------------------------------- /products/src/infra/rabbitmq/services/implementations/order-status-event-queue.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { 4 | OrderPaymentSucceedEvent, 5 | OrderStatusEventQueueService, 6 | } from '../order-status-event-queue-service.interface'; 7 | import { RabbitMQService } from '../rabbitmq-service.interface'; 8 | 9 | @Injectable() 10 | export class OrderStatusEventQueueServiceImpl 11 | implements OrderStatusEventQueueService 12 | { 13 | private readonly ORDER_EXCHANGE = 'order-status'; 14 | 15 | private readonly ORDER_STATUS_EVENT_TYPE = { 16 | PAYMENT_FAILED: 'order-payment-failed', 17 | PAYMENT_SUCCEED: 'order-payment-succeed', 18 | ORDER_CREATED: 'order-created', 19 | } as const; 20 | 21 | constructor(private readonly rabbitMQService: RabbitMQService) {} 22 | 23 | async consumePaymentSucceedEvent( 24 | onEvent: (event: OrderPaymentSucceedEvent) => void, 25 | ): Promise { 26 | await this.rabbitMQService.consumeFromDirectExchange( 27 | this.ORDER_EXCHANGE, 28 | this.ORDER_STATUS_EVENT_TYPE.PAYMENT_SUCCEED, 29 | onEvent, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /products/src/infra/env-config/services/implementations/env-config.service.ts: -------------------------------------------------------------------------------- 1 | import { Global, Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | 4 | import { EnvConfigService } from '../env-config-service.interface'; 5 | 6 | @Global() 7 | @Injectable() 8 | export class EnvConfigServiceImpl implements EnvConfigService { 9 | constructor(private readonly configService: ConfigService) {} 10 | 11 | getDbUri(): string { 12 | return this.configService.getOrThrow('DB_URI'); 13 | } 14 | 15 | getDbName(): string { 16 | return this.configService.getOrThrow('DB_NAME'); 17 | } 18 | 19 | getDbUser(): string { 20 | return this.configService.getOrThrow('DB_USER'); 21 | } 22 | 23 | getDbPassword(): string { 24 | return this.configService.getOrThrow('DB_PASSWORD'); 25 | } 26 | 27 | getDbPort(): number { 28 | return this.configService.getOrThrow('DB_PORT'); 29 | } 30 | 31 | getServerPort(): number { 32 | return this.configService.getOrThrow('SERVER_PORT'); 33 | } 34 | 35 | getRabbitMQUrl(): string { 36 | return this.configService.getOrThrow('RABBIT_MQ_URL'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /users/src/core/auth/controllers/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'; 2 | 3 | import { LoginDto } from '../dtos/login.dto'; 4 | import { SignupDto } from '../dtos/signup.dto'; 5 | import { TokenDto } from '../dtos/token.dto'; 6 | import { LoginUseCase } from '../usecases/login.usecase'; 7 | import { SignupUseCase } from '../usecases/signup.usecase'; 8 | 9 | @Controller('auth') 10 | export class AuthController { 11 | constructor( 12 | private readonly loginUseCase: LoginUseCase, 13 | private readonly SignupUseCase: SignupUseCase, 14 | ) {} 15 | 16 | @Post('login') 17 | @HttpCode(HttpStatus.OK) 18 | async login(@Body() loginDto: LoginDto): Promise { 19 | const token = await this.loginUseCase.execute( 20 | loginDto.email, 21 | loginDto.password, 22 | ); 23 | 24 | return { 25 | token, 26 | }; 27 | } 28 | 29 | @Post('signup') 30 | async signup(@Body() signupDto: SignupDto) { 31 | await this.SignupUseCase.execute( 32 | signupDto.username, 33 | signupDto.email, 34 | signupDto.password, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /users/src/infra/env-config/services/implementations/env-config.service.ts: -------------------------------------------------------------------------------- 1 | import { Global, Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | 4 | import { EnvConfigService } from '../env-config-service.interface'; 5 | 6 | @Global() 7 | @Injectable() 8 | export class EnvConfigServiceImpl implements EnvConfigService { 9 | constructor(private readonly configService: ConfigService) {} 10 | 11 | getDbUri(): string { 12 | return this.configService.getOrThrow('DB_URI'); 13 | } 14 | 15 | getDbName(): string { 16 | return this.configService.getOrThrow('DB_NAME'); 17 | } 18 | 19 | getDbUser(): string { 20 | return this.configService.getOrThrow('DB_USER'); 21 | } 22 | 23 | getDbPassword(): string { 24 | return this.configService.getOrThrow('DB_PASSWORD'); 25 | } 26 | 27 | getDbPort(): number { 28 | return this.configService.getOrThrow('DB_PORT'); 29 | } 30 | 31 | getServerPort(): number { 32 | return this.configService.getOrThrow('SERVER_PORT'); 33 | } 34 | 35 | getServerJwtSecret(): string { 36 | return this.configService.getOrThrow('SERVER_JWT_SECRET'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /carts/src/core/carts/services/implementations/cart.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectModel } from '@nestjs/mongoose'; 3 | import { Model } from 'mongoose'; 4 | 5 | import { Cart } from '../../entities/cart.entity'; 6 | import { CartService } from '../cart-service.interface'; 7 | 8 | @Injectable() 9 | export class CartServiceImpl implements CartService { 10 | constructor( 11 | @InjectModel(Cart.name) 12 | private readonly model: Model, 13 | ) {} 14 | 15 | async create(cart: Cart): Promise { 16 | await new this.model(cart).save(); 17 | } 18 | 19 | async update(cart: Cart): Promise { 20 | await this.model.findByIdAndUpdate( 21 | { _id: cart._id }, 22 | { 23 | products: cart.products, 24 | totalPrice: cart.totalPrice, 25 | productsQuantity: cart.productsQuantity, 26 | }, 27 | ); 28 | } 29 | 30 | async findByUserId(userId: string): Promise { 31 | return this.model.findOne({ userId }); 32 | } 33 | 34 | async delete(cartId: string): Promise { 35 | await this.model.findByIdAndDelete(cartId); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /carts/src/infra/env-config/services/implementations/env-config.service.ts: -------------------------------------------------------------------------------- 1 | import { Global, Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | 4 | import { EnvConfigService } from '../env-config-service.interface'; 5 | 6 | @Global() 7 | @Injectable() 8 | export class EnvConfigServiceImpl implements EnvConfigService { 9 | constructor(private readonly configService: ConfigService) {} 10 | 11 | getDbUri(): string { 12 | return this.configService.getOrThrow('DB_URI'); 13 | } 14 | 15 | getDbName(): string { 16 | return this.configService.getOrThrow('DB_NAME'); 17 | } 18 | 19 | getDbUser(): string { 20 | return this.configService.getOrThrow('DB_USER'); 21 | } 22 | 23 | getDbPassword(): string { 24 | return this.configService.getOrThrow('DB_PASSWORD'); 25 | } 26 | 27 | getDbPort(): number { 28 | return this.configService.getOrThrow('DB_PORT'); 29 | } 30 | 31 | getServerPort(): number { 32 | return this.configService.getOrThrow('SERVER_PORT'); 33 | } 34 | 35 | getProductsServiceUrl(): string { 36 | return this.configService.getOrThrow('PRODUCTS_SERVICE_URL'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /users/src/core/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule } from '@nestjs/axios'; 2 | import { Module } from '@nestjs/common'; 3 | import { JwtModule } from '@nestjs/jwt'; 4 | 5 | import { UsersModule } from '@/core/users/users.module'; 6 | 7 | import { AuthController } from './controllers/auth.controller'; 8 | import { HashService } from './services/hash-service.interface'; 9 | import { HashServiceImpl } from './services/implementations/hash.service'; 10 | import { JwtServiceImpl } from './services/implementations/jwt.service'; 11 | import { JwtService } from './services/jwt-service.interface'; 12 | import { LoginUseCase } from './usecases/login.usecase'; 13 | import { SignupUseCase } from './usecases/signup.usecase'; 14 | 15 | @Module({ 16 | imports: [ 17 | HttpModule.register({ 18 | timeout: 5000, 19 | maxRedirects: 5, 20 | }), 21 | JwtModule, 22 | UsersModule, 23 | ], 24 | providers: [ 25 | { provide: HashService, useClass: HashServiceImpl }, 26 | { provide: JwtService, useClass: JwtServiceImpl }, 27 | LoginUseCase, 28 | SignupUseCase, 29 | ], 30 | controllers: [AuthController], 31 | }) 32 | export class AuthModule {} 33 | -------------------------------------------------------------------------------- /api-gateway/src/middlewares/check-authorization.js: -------------------------------------------------------------------------------- 1 | const { match } = require("path-to-regexp"); 2 | 3 | /** 4 | * Middleware to check use authorization 5 | * 6 | * @param {Array<{ path: string, method: string, roles: Array }>} allowedRoutes - An array of objects representing allowed routes. 7 | * @returns {(request: import('fastify').FastifyRequest, reply: import('fastify').FastifyReply, done: Function) => void} 8 | */ 9 | const checkAuthorization = (allowedRoutes) => (request, reply, done) => { 10 | const route = allowedRoutes.find((r) => { 11 | const isMethodMatching = r.method === request.method; 12 | const urlWithoutQuery = request.url.split("?")[0]; 13 | const isPathMatching = match(r.path, { decode: decodeURIComponent })( 14 | urlWithoutQuery 15 | ); 16 | 17 | return isMethodMatching && isPathMatching; 18 | }); 19 | 20 | if (!route) { 21 | reply.status(403).send({ message: "Unauthorized" }); 22 | return; 23 | } 24 | 25 | const isAuthorized = route.roles.some((role) => 26 | request.headers["x-user-roles"].includes(role) 27 | ); 28 | 29 | if (!isAuthorized) { 30 | reply.status(403).send({ message: "Unauthorized" }); 31 | return; 32 | } 33 | 34 | done(); 35 | }; 36 | 37 | module.exports = { checkAuthorization }; 38 | -------------------------------------------------------------------------------- /products/src/infra/rabbitmq/services/order-status-event-queue-service.interface.ts: -------------------------------------------------------------------------------- 1 | export type EventProduct = { id: string; inCartQuantity: number }; 2 | 3 | export type OrderPaymentFailedEvent = { 4 | user: { 5 | id: string; 6 | name: string; 7 | email: string; 8 | }; 9 | order: { 10 | id: string; 11 | price: number; 12 | products: EventProduct[]; 13 | }; 14 | payment: { 15 | method: string; 16 | parcels: number; 17 | transactionCode: null; 18 | }; 19 | }; 20 | 21 | export type OrderPaymentSucceedEvent = { 22 | user: { 23 | id: string; 24 | name: string; 25 | email: string; 26 | }; 27 | order: { 28 | id: string; 29 | price: number; 30 | products: EventProduct[]; 31 | }; 32 | payment: { 33 | method: string; 34 | parcels: number; 35 | transactionCode: string; 36 | }; 37 | }; 38 | 39 | export type OrderCreatedEvent = { 40 | user: { 41 | id: string; 42 | name: string; 43 | email: string; 44 | }; 45 | order: { 46 | id: string; 47 | price: number; 48 | products: EventProduct[]; 49 | }; 50 | }; 51 | 52 | export abstract class OrderStatusEventQueueService { 53 | abstract consumePaymentSucceedEvent( 54 | onEvent: (event: OrderPaymentSucceedEvent) => void, 55 | ): Promise; 56 | } 57 | -------------------------------------------------------------------------------- /users/src/core/users/services/implementations/user.service.ts: -------------------------------------------------------------------------------- 1 | import { ConflictException, Injectable } from '@nestjs/common'; 2 | import { InjectModel } from '@nestjs/mongoose'; 3 | import { Model } from 'mongoose'; 4 | 5 | import { UsersFactory } from '@/core/users/factories/users.factory'; 6 | 7 | import { User } from '../../entities/user.entity'; 8 | import { UserService } from '../user-service.interface'; 9 | 10 | @Injectable() 11 | export class UserServiceImpl implements UserService { 12 | constructor( 13 | @InjectModel(User.name) 14 | private readonly model: Model, 15 | ) {} 16 | 17 | async createClient( 18 | username: string, 19 | email: string, 20 | password: string, 21 | ): Promise { 22 | const existentUser = await this.model.findOne({ email }); 23 | 24 | if (existentUser) { 25 | throw new ConflictException('Email already in use by another user'); 26 | } 27 | 28 | const user = UsersFactory.createWithClientRole(username, email, password); 29 | 30 | await new this.model(user).save(); 31 | } 32 | 33 | async findById(userId: string): Promise { 34 | return this.model.findById(userId); 35 | } 36 | 37 | async findByEmail(email: string): Promise { 38 | return this.model.findOne({ email }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /api-gateway/src/middlewares/check-allowed-routes.js: -------------------------------------------------------------------------------- 1 | const { match } = require("path-to-regexp"); 2 | 3 | /** 4 | * Middleware to check if a route is allowed based on its path and HTTP method. 5 | * 6 | * @param {Array<{ path: string, method: string }>} allowedRoutes - An array of objects representing allowed routes. 7 | * Each object should have a `path` (string) and `method` (string, e.g., "GET", "POST"). 8 | * @returns {(request: import('fastify').FastifyRequest, reply: import('fastify').FastifyReply, done: Function) => void} Middleware function. 9 | * Validates if the incoming request matches an allowed route and HTTP method. 10 | * Responds with a 404 error if the route or method is not allowed. 11 | */ 12 | const checkAllowedRoutes = (allowedRoutes) => (request, reply, done) => { 13 | const isRouteAllowed = allowedRoutes.some((route) => { 14 | const isMethodMatching = route.method === request.method; 15 | const urlWithoutQuery = request.url.split("?")[0]; 16 | const isPathMatching = match(route.path, { decode: decodeURIComponent })( 17 | urlWithoutQuery 18 | ); 19 | 20 | return isMethodMatching && isPathMatching; 21 | }); 22 | 23 | if (!isRouteAllowed) { 24 | reply.status(404).send({ message: "Route not Found" }); 25 | return; 26 | } 27 | 28 | done(); 29 | }; 30 | 31 | module.exports = { checkAllowedRoutes }; 32 | -------------------------------------------------------------------------------- /payments/src/core/payments/payments.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule } from '@nestjs/axios'; 2 | import { Module } from '@nestjs/common'; 3 | 4 | import { EnvConfigService } from '@/infra/env-config/services/env-config-service.interface'; 5 | import { RabbitMQModule } from '@/infra/rabbitmq/rabbitmq.module'; 6 | 7 | import { ProductsModule } from '../products/products.module'; 8 | import { PaymentServiceImpl } from './services/implementations/payment.service'; 9 | import { PaymentService } from './services/payment-service.interface'; 10 | import { PayOrderWithCreditCardUseCase } from './usecases/pay-order-with-credit-card.usecase'; 11 | 12 | @Module({ 13 | imports: [ 14 | HttpModule.registerAsync({ 15 | useFactory: (envConfigService: EnvConfigService) => ({ 16 | timeout: 5000, 17 | maxRedirects: 3, 18 | baseURL: envConfigService.getPaymentGatewayUrl(), 19 | headers: { 20 | 'Content-Type': 'application/json', 21 | 'User-Agent': `nestjs api`, 22 | access_token: envConfigService.getPaymentGatewayKey(), 23 | }, 24 | }), 25 | inject: [EnvConfigService], 26 | }), 27 | RabbitMQModule, 28 | ProductsModule, 29 | ], 30 | providers: [ 31 | { provide: PaymentService, useClass: PaymentServiceImpl }, 32 | PayOrderWithCreditCardUseCase, 33 | ], 34 | }) 35 | export class PaymentsModule {} 36 | -------------------------------------------------------------------------------- /payments/src/infra/rabbitmq/services/order-status-event-queue-service.interface.ts: -------------------------------------------------------------------------------- 1 | export type EventProduct = { id: string; inCartQuantity: number }; 2 | 3 | export type OrderPaymentFailedEvent = { 4 | user: { 5 | id: string; 6 | name: string; 7 | email: string; 8 | }; 9 | order: { 10 | id: string; 11 | price: number; 12 | products: EventProduct[]; 13 | }; 14 | payment: { 15 | method: string; 16 | parcels: number; 17 | transactionCode: null; 18 | }; 19 | }; 20 | 21 | export type OrderPaymentSucceedEvent = { 22 | user: { 23 | id: string; 24 | name: string; 25 | email: string; 26 | }; 27 | order: { 28 | id: string; 29 | price: number; 30 | products: EventProduct[]; 31 | }; 32 | payment: { 33 | method: string; 34 | parcels: number; 35 | transactionCode: string; 36 | }; 37 | }; 38 | 39 | export type OrderCreatedEvent = { 40 | user: { 41 | id: string; 42 | name: string; 43 | email: string; 44 | }; 45 | order: { 46 | id: string; 47 | price: number; 48 | products: EventProduct[]; 49 | }; 50 | }; 51 | 52 | export abstract class OrderStatusEventQueueService { 53 | abstract emitPaymentFailedEvent( 54 | event: OrderPaymentFailedEvent, 55 | ): Promise; 56 | 57 | abstract emitPaymentSucceedEvent( 58 | event: OrderPaymentSucceedEvent, 59 | ): Promise; 60 | } 61 | -------------------------------------------------------------------------------- /payments/src/infra/rabbitmq/services/implementations/order-status-event-queue.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { 4 | OrderPaymentFailedEvent, 5 | OrderPaymentSucceedEvent, 6 | OrderStatusEventQueueService, 7 | } from '../order-status-event-queue-service.interface'; 8 | import { RabbitMQService } from '../rabbitmq-service.interface'; 9 | 10 | @Injectable() 11 | export class OrderStatusEventQueueServiceImpl 12 | implements OrderStatusEventQueueService 13 | { 14 | private readonly ORDER_EXCHANGE = 'order-status'; 15 | 16 | private readonly ORDER_STATUS_EVENT_TYPE = { 17 | PAYMENT_FAILED: 'order-payment-failed', 18 | PAYMENT_SUCCEED: 'order-payment-succeed', 19 | ORDER_CREATED: 'order-created', 20 | } as const; 21 | 22 | constructor(private readonly rabbitMQService: RabbitMQService) {} 23 | 24 | async emitPaymentFailedEvent(event: OrderPaymentFailedEvent): Promise { 25 | await this.rabbitMQService.publishToDirectExchange( 26 | this.ORDER_EXCHANGE, 27 | this.ORDER_STATUS_EVENT_TYPE.PAYMENT_FAILED, 28 | event, 29 | ); 30 | } 31 | 32 | async emitPaymentSucceedEvent( 33 | event: OrderPaymentSucceedEvent, 34 | ): Promise { 35 | await this.rabbitMQService.publishToDirectExchange( 36 | this.ORDER_EXCHANGE, 37 | this.ORDER_STATUS_EVENT_TYPE.PAYMENT_SUCCEED, 38 | event, 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /api-gateway/src/routes/carts.routes.js: -------------------------------------------------------------------------------- 1 | const { addClientPermission } = require("../utils/app-roles"); 2 | const { getCartsServiceUrl } = require("../utils/env-config"); 3 | const { httpMethods } = require("../utils/http-methods"); 4 | 5 | /** 6 | * List of allowed carts routes with their respective HTTP methods. 7 | * 8 | * @constant {Array} cartsAllowedRoutes - Array of product-related routes 9 | * @property {string} path - The route path, can include parameters (e.g., "/carts/:id") 10 | * @property {string} method - The HTTP method (e.g., 'GET', 'POST', 'PUT', 'DELETE') 11 | * @property {Array} roles - The roles allowed to access the route (e.g., 'client', 'admin') 12 | * 13 | * 14 | */ 15 | const cartsAllowedRoutes = [ 16 | { 17 | path: "/carts/my-cart", 18 | method: httpMethods.get, 19 | roles: addClientPermission(), 20 | }, 21 | { 22 | path: "/carts/my-cart", 23 | method: httpMethods.delete, 24 | roles: addClientPermission(), 25 | }, 26 | { 27 | path: "/carts/my-cart/products/:id", 28 | method: httpMethods.post, 29 | roles: addClientPermission(), 30 | }, 31 | { 32 | path: "/carts/my-cart/products/:id", 33 | method: httpMethods.delete, 34 | roles: addClientPermission(), 35 | }, 36 | ]; 37 | 38 | const cartsPrefix = "/carts"; 39 | const cartsServiceUrl = getCartsServiceUrl(); 40 | 41 | module.exports = { 42 | cartsAllowedRoutes, 43 | cartsPrefix, 44 | cartsServiceUrl, 45 | }; 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ecommerce Backend Soa 2 | 3 | Backend for a generic ecommerce, but using service oriented architecture 4 | 5 | ## Functional Requirements 6 | 7 | - Ability for users to sign up and log in. 8 | - Ability to add products to a cart. 9 | - Ability to remove products from a cart. 10 | - Ability to view and search for products. 11 | - Ability for users to checkout and pay for products. 12 | - Admin panel to add products, set the prices and manage inventory 13 | 14 | ## Non Functional Requirements 15 | 16 | - Use asaas as payments gateway 17 | - Use Next.js for build the frontend 18 | - Use server-client architecture 19 | - Use mongodb as database 20 | - Use NodeJS and NestJS for backend 21 | 22 | ## Entities 23 | 24 | - Users 25 | - Products 26 | - Carts 27 | - Orders 28 | 29 | ## Architecture 30 | 31 | soa png 32 | 33 | ## ERD 34 | 35 | soa png 36 | 37 | ## Running Locally 38 | 39 | To run locally you have to start each service using docker. 40 | 41 | I personally recommend you to start the RabbitMQ (just go to rabbit-mq folder and follow the instructions) first and then start the others services. 42 | 43 | Don't worry, each service folder has his own documentation, and the instructions to run locally. 44 | 45 | I left a postman collection for you to test the endpoints locally, just install the json located in the project's root folder and import it into your postman. 46 | -------------------------------------------------------------------------------- /carts/src/core/carts/carts.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | 4 | import { ProductsModule } from '../products/products.module'; 5 | import { CartsController } from './controllers/carts.controller'; 6 | import { Cart, CartSchema } from './entities/cart.entity'; 7 | import { CartService } from './services/cart-service.interface'; 8 | import { CartServiceImpl } from './services/implementations/cart.service'; 9 | import { AddProductToCartUseCase } from './usecases/add-product-to-cart.usecase'; 10 | import { ClearCartUseCase } from './usecases/clear-cart.usecase'; 11 | import { DeleteCartByIdUseCase } from './usecases/delete-cart-by-id.usecase'; 12 | import { FindCartByUserIdUseCase } from './usecases/find-cart-by-user-id.usecase'; 13 | import { FindUserCartUseCase } from './usecases/find-user-cart.usecase'; 14 | import { SubtractProductFromCartUseCase } from './usecases/subtract-product-from-cart.usecase'; 15 | 16 | @Module({ 17 | imports: [ 18 | MongooseModule.forFeature([{ name: Cart.name, schema: CartSchema }]), 19 | ProductsModule, 20 | ], 21 | providers: [ 22 | { provide: CartService, useClass: CartServiceImpl }, 23 | AddProductToCartUseCase, 24 | ClearCartUseCase, 25 | DeleteCartByIdUseCase, 26 | FindCartByUserIdUseCase, 27 | FindUserCartUseCase, 28 | SubtractProductFromCartUseCase, 29 | ], 30 | controllers: [CartsController], 31 | }) 32 | export class CartsModule {} 33 | -------------------------------------------------------------------------------- /products/src/core/products/usecases/check-products-stock-availability.usecase.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BadRequestException, 3 | Injectable, 4 | NotFoundException, 5 | } from '@nestjs/common'; 6 | 7 | import { CartProductDto } from '../dtos/cart-product.dto'; 8 | import { ProductService } from '../services/product-service.interface'; 9 | 10 | @Injectable() 11 | export class CheckProductsStockAvailabilityUseCase { 12 | constructor(private readonly productService: ProductService) {} 13 | 14 | async execute(products: CartProductDto[]) { 15 | if (products.length > 30) { 16 | throw new BadRequestException( 17 | 'Cannot process more than 30 products for stock availability check', 18 | ); 19 | } 20 | 21 | const promises: Promise[] = products.map(async (cartProduct) => { 22 | const stockProduct = await this.productService.findById(cartProduct.id); 23 | 24 | if (!stockProduct) { 25 | throw new NotFoundException( 26 | `Product with ID ${cartProduct.id} not found`, 27 | ); 28 | } 29 | 30 | const isAvailable = 31 | stockProduct.stockQuantity >= cartProduct.inCartQuantity; 32 | 33 | if (!isAvailable) { 34 | throw new BadRequestException( 35 | `Insufficient stock for product ID ${cartProduct.id}, available: ${stockProduct.stockQuantity}, Requested: ${cartProduct.inCartQuantity}`, 36 | ); 37 | } 38 | }); 39 | 40 | await Promise.all(promises); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /orders/src/infra/env-config/services/implementations/env-config.service.ts: -------------------------------------------------------------------------------- 1 | import { Global, Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | 4 | import { EnvConfigService } from '../env-config-service.interface'; 5 | 6 | @Global() 7 | @Injectable() 8 | export class EnvConfigServiceImpl implements EnvConfigService { 9 | constructor(private readonly configService: ConfigService) {} 10 | 11 | getDbUri(): string { 12 | return this.configService.getOrThrow('DB_URI'); 13 | } 14 | 15 | getDbName(): string { 16 | return this.configService.getOrThrow('DB_NAME'); 17 | } 18 | 19 | getDbUser(): string { 20 | return this.configService.getOrThrow('DB_USER'); 21 | } 22 | 23 | getDbPassword(): string { 24 | return this.configService.getOrThrow('DB_PASSWORD'); 25 | } 26 | 27 | getDbPort(): number { 28 | return this.configService.getOrThrow('DB_PORT'); 29 | } 30 | 31 | getServerPort(): number { 32 | return this.configService.getOrThrow('SERVER_PORT'); 33 | } 34 | 35 | getServerJwtSecret(): string { 36 | return this.configService.getOrThrow('SERVER_JWT_SECRET'); 37 | } 38 | 39 | getCartServiceUrl(): string { 40 | return this.configService.getOrThrow('CART_SERVICE_URL'); 41 | } 42 | 43 | getUserServiceUrl(): string { 44 | return this.configService.getOrThrow('USER_SERVICE_URL'); 45 | } 46 | 47 | getRabbitMQUrl(): string { 48 | return this.configService.getOrThrow('RABBIT_MQ_URL'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /api-gateway/src/middlewares/jwt-auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const { getServerJwtSecret } = require("../utils/env-config"); 3 | 4 | /** 5 | * Middleware for JWT authentication. Verifies if the JWT token is present and valid. 6 | * If the token is valid, the roles and userId extracted from the token are added to the request headers. 7 | * Otherwise, it returns a 401 error with an appropriate message. 8 | * 9 | * @param {import('fastify').FastifyRequest} request - The Fastify request object containing headers and other request details. 10 | * @param {import('fastify').FastifyReply} reply - The Fastify reply object used to send HTTP responses. 11 | * @param {Function} done - The callback function that should be called when the middleware is complete. 12 | * 13 | * @returns {void} 14 | */ 15 | const jwtAuth = (request, reply, done) => { 16 | const authHeader = request.headers.authorization; 17 | 18 | if (!authHeader || !authHeader.startsWith("Bearer ")) { 19 | reply.code(401).send({ message: "Token not provided or invalid" }); 20 | return; 21 | } 22 | 23 | const token = authHeader.split(" ")[1]; 24 | 25 | try { 26 | const decoded = jwt.verify(token, getServerJwtSecret()); 27 | 28 | request.headers["x-user-roles"] = decoded.roles; 29 | request.headers["x-user-id"] = decoded.userId; 30 | 31 | done(); 32 | } catch (err) { 33 | reply.code(401).send({ message: "Invalid or expired token" }); 34 | } 35 | }; 36 | 37 | module.exports = { jwtAuth }; 38 | -------------------------------------------------------------------------------- /orders/src/infra/rabbitmq/services/order-status-event-queue-service.interface.ts: -------------------------------------------------------------------------------- 1 | export type EventProduct = { id: string; inCartQuantity: number }; 2 | 3 | export type OrderPaymentFailedEvent = { 4 | user: { 5 | id: string; 6 | name: string; 7 | email: string; 8 | }; 9 | order: { 10 | id: string; 11 | price: number; 12 | products: EventProduct[]; 13 | }; 14 | payment: { 15 | method: string; 16 | parcels: number; 17 | transactionCode: null; 18 | }; 19 | }; 20 | 21 | export type OrderPaymentSucceedEvent = { 22 | user: { 23 | id: string; 24 | name: string; 25 | email: string; 26 | }; 27 | order: { 28 | id: string; 29 | price: number; 30 | products: EventProduct[]; 31 | }; 32 | payment: { 33 | method: string; 34 | parcels: number; 35 | transactionCode: string; 36 | }; 37 | }; 38 | 39 | export type OrderCreatedEvent = { 40 | user: { 41 | id: string; 42 | name: string; 43 | email: string; 44 | }; 45 | order: { 46 | id: string; 47 | price: number; 48 | products: EventProduct[]; 49 | }; 50 | }; 51 | 52 | export abstract class OrderStatusEventQueueService { 53 | abstract consumePaymentFailedEvent( 54 | onEvent: (event: OrderPaymentFailedEvent) => void, 55 | ): Promise; 56 | 57 | abstract consumePaymentSucceedEvent( 58 | onEvent: (event: OrderPaymentSucceedEvent) => void, 59 | ): Promise; 60 | 61 | abstract emitOrderCreatedEvent(event: OrderCreatedEvent): Promise; 62 | } 63 | -------------------------------------------------------------------------------- /notifications/src/infra/rabbitmq/services/order-status-event-queue-service.interface.ts: -------------------------------------------------------------------------------- 1 | export type EventProduct = { id: string; inCartQuantity: number }; 2 | 3 | export type OrderPaymentFailedEvent = { 4 | user: { 5 | id: string; 6 | name: string; 7 | email: string; 8 | }; 9 | order: { 10 | id: string; 11 | price: number; 12 | products: EventProduct[]; 13 | }; 14 | payment: { 15 | method: string; 16 | parcels: number; 17 | transactionCode: null; 18 | }; 19 | }; 20 | 21 | export type OrderPaymentSucceedEvent = { 22 | user: { 23 | id: string; 24 | name: string; 25 | email: string; 26 | }; 27 | order: { 28 | id: string; 29 | price: number; 30 | products: EventProduct[]; 31 | }; 32 | payment: { 33 | method: string; 34 | parcels: number; 35 | transactionCode: string; 36 | }; 37 | }; 38 | 39 | export type OrderCreatedEvent = { 40 | user: { 41 | id: string; 42 | name: string; 43 | email: string; 44 | }; 45 | order: { 46 | id: string; 47 | price: number; 48 | products: EventProduct[]; 49 | }; 50 | }; 51 | 52 | export abstract class OrderStatusEventQueueService { 53 | abstract consumePaymentFailedEvent( 54 | onEvent: (event: OrderPaymentFailedEvent) => void, 55 | ): Promise; 56 | 57 | abstract consumePaymentSucceedEvent( 58 | onEvent: (event: OrderPaymentSucceedEvent) => void, 59 | ): Promise; 60 | 61 | abstract consumeOrderCreatedEvent( 62 | onEvent: (event: OrderCreatedEvent) => void, 63 | ): Promise; 64 | } 65 | -------------------------------------------------------------------------------- /orders/src/core/orders/usecases/update-order-after-unsuccessful-payment.usecase.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | Logger, 4 | NotFoundException, 5 | OnModuleInit, 6 | } from '@nestjs/common'; 7 | 8 | import { 9 | OrderPaymentFailedEvent, 10 | OrderStatusEventQueueService, 11 | } from '@/infra/rabbitmq/services/order-status-event-queue-service.interface'; 12 | 13 | import { ORDER_STATUS } from '../enums/order-status.enum'; 14 | import { OrderService } from '../services/order-service.interface'; 15 | 16 | @Injectable() 17 | export class UpdateOrderAfterUnsuccessfulPaymentUseCase 18 | implements OnModuleInit 19 | { 20 | private readonly logger = new Logger( 21 | UpdateOrderAfterUnsuccessfulPaymentUseCase.name, 22 | ); 23 | 24 | constructor( 25 | private readonly orderService: OrderService, 26 | private readonly orderStatusEventQueueService: OrderStatusEventQueueService, 27 | ) {} 28 | 29 | async onModuleInit() { 30 | await this.orderStatusEventQueueService.consumePaymentFailedEvent( 31 | this.execute.bind(this), 32 | ); 33 | this.logger.log('Listening to order-payment-failed events...'); 34 | } 35 | 36 | private async execute(event: OrderPaymentFailedEvent) { 37 | this.logger.log('Processing order-payment-failed event'); 38 | 39 | const order = await this.orderService.findById(event.order.id); 40 | 41 | if (!order) { 42 | throw new NotFoundException('Order not found'); 43 | } 44 | 45 | order.status = ORDER_STATUS.WAITING_PAYMENT; 46 | 47 | await this.orderService.update(order); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /api-gateway/src/routes/products.routes.js: -------------------------------------------------------------------------------- 1 | const { 2 | addClientPermission, 3 | addAdminPermission, 4 | } = require("../utils/app-roles"); 5 | const { getProductsServiceUrl } = require("../utils/env-config"); 6 | const { httpMethods } = require("../utils/http-methods"); 7 | 8 | /** 9 | * List of allowed products routes with their respective HTTP methods. 10 | * 11 | * @constant {Array} productsAllowedRoutes - Array of product-related routes 12 | * @property {string} path - The route path, can include parameters (e.g., "/products/:id") 13 | * @property {string} method - The HTTP method (e.g., 'GET', 'POST', 'PUT', 'DELETE') 14 | * @property {Array} roles - The roles allowed to access the route (e.g., 'client', 'admin') 15 | * 16 | */ 17 | const productsAllowedRoutes = [ 18 | { 19 | path: "/products", 20 | method: httpMethods.get, 21 | roles: addClientPermission(), 22 | }, 23 | { 24 | path: "/products/:id", 25 | method: httpMethods.get, 26 | roles: addClientPermission(), 27 | }, 28 | { 29 | path: "/products", 30 | method: httpMethods.post, 31 | roles: addAdminPermission(), 32 | }, 33 | { 34 | path: "/products/:id", 35 | method: httpMethods.delete, 36 | roles: addAdminPermission(), 37 | }, 38 | { 39 | path: "/products/:id", 40 | method: httpMethods.put, 41 | roles: addAdminPermission(), 42 | }, 43 | ]; 44 | 45 | const productsPrefix = "/products"; 46 | const productsServiceUrl = getProductsServiceUrl(); 47 | 48 | module.exports = { 49 | productsAllowedRoutes, 50 | productsPrefix, 51 | productsServiceUrl, 52 | }; 53 | -------------------------------------------------------------------------------- /notifications/templates/order-created.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Order Confirmation - Awaiting Payment 5 | 15 | 16 | 17 |
18 |
19 |

Order Confirmation

20 |
21 |
22 |

Dear Customer,

23 |

Your order has been successfully confirmed, and we are currently 24 | awaiting payment to proceed further.

25 |

Order ID: {{orderId}}

26 |

Once the payment is completed, we will process your order and notify 27 | you about the next steps.

28 |

If you have any questions, feel free to contact our support team.

29 |

Thank you for choosing us!

30 |
31 | 34 |
35 | 36 | -------------------------------------------------------------------------------- /api-gateway/src/routes/orders.routes.js: -------------------------------------------------------------------------------- 1 | const { 2 | addAdminPermission, 3 | addClientPermission, 4 | } = require("../utils/app-roles"); 5 | const { getOrdersServiceUrl } = require("../utils/env-config"); 6 | const { httpMethods } = require("../utils/http-methods"); 7 | 8 | /** 9 | * List of allowed orders routes with their respective HTTP methods. 10 | * 11 | * @constant {Array} ordersAllowedRoutes - Array of product-related routes 12 | * @property {string} path - The route path, can include parameters (e.g., "/orders/:id") 13 | * @property {string} method - The HTTP method (e.g., 'GET', 'POST', 'PUT', 'DELETE') 14 | * @property {Array} roles - The roles allowed to access the route (e.g., 'client', 'admin') 15 | * 16 | */ 17 | const ordersAllowedRoutes = [ 18 | { 19 | path: "/orders", 20 | method: httpMethods.get, 21 | roles: addAdminPermission(), 22 | }, 23 | { 24 | path: "/orders/my-orders", 25 | method: httpMethods.get, 26 | roles: addClientPermission(), 27 | }, 28 | { 29 | path: "/orders/my-orders", 30 | method: httpMethods.post, 31 | roles: addClientPermission(), 32 | }, 33 | { 34 | path: "/orders/my-orders/:orderId", 35 | method: httpMethods.delete, 36 | roles: addClientPermission(), 37 | }, 38 | { 39 | path: "/orders/my-orders/:orderId/payments/credit", 40 | method: httpMethods.post, 41 | roles: addClientPermission(), 42 | }, 43 | ]; 44 | 45 | const ordersPrefix = "/orders"; 46 | const ordersServiceUrl = getOrdersServiceUrl(); 47 | 48 | module.exports = { 49 | ordersAllowedRoutes, 50 | ordersPrefix, 51 | ordersServiceUrl, 52 | }; 53 | -------------------------------------------------------------------------------- /notifications/src/core/notifications/usecases/send-email-after-order-creation.usecase.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; 2 | 3 | import { MailService } from '@/infra/mail/services/mail-service.interface'; 4 | import { 5 | OrderCreatedEvent, 6 | OrderStatusEventQueueService, 7 | } from '@/infra/rabbitmq/services/order-status-event-queue-service.interface'; 8 | import { TemplateEngineService } from '@/infra/template-engine/services/template-engine-service.interface'; 9 | 10 | @Injectable() 11 | export class SendEmailAfterOrderCreationUseCase implements OnModuleInit { 12 | private readonly logger = new Logger(SendEmailAfterOrderCreationUseCase.name); 13 | 14 | constructor( 15 | private readonly orderStatusEventQueueService: OrderStatusEventQueueService, 16 | private readonly mailService: MailService, 17 | private readonly templateEngineService: TemplateEngineService, 18 | ) {} 19 | 20 | async onModuleInit() { 21 | await this.orderStatusEventQueueService.consumeOrderCreatedEvent( 22 | this.execute.bind(this), 23 | ); 24 | this.logger.log('Listening for order creation events...'); 25 | } 26 | 27 | private async execute(event: OrderCreatedEvent) { 28 | this.logger.log('Processing order creation event'); 29 | 30 | const html = await this.templateEngineService.compile('order-created.hbs', { 31 | orderId: event.order.id, 32 | year: new Date().getFullYear(), 33 | }); 34 | 35 | this.mailService.sendMail({ 36 | html, 37 | subject: 'Order Confirmation - Awaiting Payment', 38 | to: { 39 | email: event.user.email, 40 | name: event.user.name, 41 | }, 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /notifications/src/infra/mail/services/implementations/mail.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger } from '@nestjs/common'; 2 | import { createTransport } from 'nodemailer'; 3 | import Mail from 'nodemailer/lib/mailer'; 4 | 5 | import { EnvConfigService } from '@/infra/env-config/services/env-config-service.interface'; 6 | 7 | import { MailService, Message } from '../mail-service.interface'; 8 | 9 | @Injectable() 10 | export class MailServiceImpl implements MailService { 11 | private readonly logger = new Logger(MailService.name); 12 | 13 | private readonly transporter: Mail; 14 | private readonly mailHostUser: string; 15 | private readonly mailHostName: string; 16 | private readonly mailHostPassword: string; 17 | 18 | constructor(envConfigService: EnvConfigService) { 19 | this.mailHostUser = envConfigService.getMailHostUser(); 20 | this.mailHostName = envConfigService.getMailHostName(); 21 | this.mailHostPassword = envConfigService.getMailHostPassword(); 22 | 23 | this.transporter = createTransport({ 24 | service: 'gmail', 25 | auth: { 26 | user: this.mailHostUser, 27 | pass: this.mailHostPassword, 28 | }, 29 | }); 30 | } 31 | 32 | async sendMail(message: Message): Promise { 33 | try { 34 | await this.transporter.sendMail({ 35 | to: { 36 | name: message.to.name, 37 | address: message.to.email, 38 | }, 39 | from: { 40 | name: this.mailHostName, 41 | address: this.mailHostUser, 42 | }, 43 | subject: message.subject, 44 | html: message.html, 45 | }); 46 | } catch (error) { 47 | this.logger.error(error); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /products/src/core/products/products.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | 4 | import { RabbitMQModule } from '@/infra/rabbitmq/rabbitmq.module'; 5 | 6 | import { ProductsController } from './controllers/products.controller'; 7 | import { Product, ProductSchema } from './entities/product.entity'; 8 | import { ProductServiceImpl } from './services/implementations/product.service'; 9 | import { ProductService } from './services/product-service.interface'; 10 | import { CheckProductsStockAvailabilityUseCase } from './usecases/check-products-stock-availability.usecase'; 11 | import { CreateProductUseCase } from './usecases/create-product.usecase'; 12 | import { DeleteProductUseCase } from './usecases/delete-product.usecase'; 13 | import { FindAllProductsUseCase } from './usecases/find-all-products.usecase'; 14 | import { FindProductByIdUseCase } from './usecases/find-product-by-id.usecase'; 15 | import { SubtractProductsFromStockUseCase } from './usecases/subtract-products-from-stock.usecase'; 16 | import { UpdateProductUseCase } from './usecases/update-product.usecase'; 17 | 18 | @Module({ 19 | imports: [ 20 | MongooseModule.forFeature([{ name: Product.name, schema: ProductSchema }]), 21 | RabbitMQModule, 22 | ], 23 | providers: [ 24 | { provide: ProductService, useClass: ProductServiceImpl }, 25 | CheckProductsStockAvailabilityUseCase, 26 | CreateProductUseCase, 27 | DeleteProductUseCase, 28 | FindAllProductsUseCase, 29 | FindProductByIdUseCase, 30 | SubtractProductsFromStockUseCase, 31 | UpdateProductUseCase, 32 | ], 33 | controllers: [ProductsController], 34 | }) 35 | export class ProductsModule {} 36 | -------------------------------------------------------------------------------- /orders/src/infra/rabbitmq/services/implementations/order-status-event-queue.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { 4 | OrderCreatedEvent, 5 | OrderPaymentFailedEvent, 6 | OrderPaymentSucceedEvent, 7 | OrderStatusEventQueueService, 8 | } from '../order-status-event-queue-service.interface'; 9 | import { RabbitMQService } from '../rabbitmq-service.interface'; 10 | 11 | @Injectable() 12 | export class OrderStatusEventQueueServiceImpl 13 | implements OrderStatusEventQueueService 14 | { 15 | private readonly ORDER_EXCHANGE = 'order-status'; 16 | 17 | private readonly ORDER_STATUS_EVENT_TYPE = { 18 | PAYMENT_FAILED: 'order-payment-failed', 19 | PAYMENT_SUCCEED: 'order-payment-succeed', 20 | ORDER_CREATED: 'order-created', 21 | } as const; 22 | 23 | constructor(private readonly rabbitMQService: RabbitMQService) {} 24 | 25 | async consumePaymentFailedEvent( 26 | onEvent: (event: OrderPaymentFailedEvent) => void, 27 | ): Promise { 28 | await this.rabbitMQService.consumeFromDirectExchange( 29 | this.ORDER_EXCHANGE, 30 | this.ORDER_STATUS_EVENT_TYPE.PAYMENT_FAILED, 31 | onEvent, 32 | ); 33 | } 34 | 35 | async consumePaymentSucceedEvent( 36 | onEvent: (event: OrderPaymentSucceedEvent) => void, 37 | ): Promise { 38 | await this.rabbitMQService.consumeFromDirectExchange( 39 | this.ORDER_EXCHANGE, 40 | this.ORDER_STATUS_EVENT_TYPE.PAYMENT_SUCCEED, 41 | onEvent, 42 | ); 43 | } 44 | 45 | async emitOrderCreatedEvent(event: OrderCreatedEvent): Promise { 46 | await this.rabbitMQService.publishToDirectExchange( 47 | this.ORDER_EXCHANGE, 48 | this.ORDER_STATUS_EVENT_TYPE.ORDER_CREATED, 49 | event, 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /notifications/src/infra/rabbitmq/services/implementations/order-status-event-queue.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { 4 | OrderCreatedEvent, 5 | OrderPaymentFailedEvent, 6 | OrderPaymentSucceedEvent, 7 | OrderStatusEventQueueService, 8 | } from '../order-status-event-queue-service.interface'; 9 | import { RabbitMQService } from '../rabbitmq-service.interface'; 10 | 11 | @Injectable() 12 | export class OrderStatusEventQueueServiceImpl 13 | implements OrderStatusEventQueueService 14 | { 15 | private readonly ORDER_EXCHANGE = 'order-status'; 16 | 17 | private readonly ORDER_STATUS_EVENT_TYPE = { 18 | PAYMENT_FAILED: 'order-payment-failed', 19 | PAYMENT_SUCCEED: 'order-payment-succeed', 20 | ORDER_CREATED: 'order-created', 21 | } as const; 22 | 23 | constructor(private readonly rabbitMQService: RabbitMQService) {} 24 | 25 | async consumePaymentFailedEvent( 26 | onEvent: (event: OrderPaymentFailedEvent) => void, 27 | ): Promise { 28 | await this.rabbitMQService.consumeFromDirectExchange( 29 | this.ORDER_EXCHANGE, 30 | this.ORDER_STATUS_EVENT_TYPE.PAYMENT_FAILED, 31 | onEvent, 32 | ); 33 | } 34 | 35 | async consumePaymentSucceedEvent( 36 | onEvent: (event: OrderPaymentSucceedEvent) => void, 37 | ): Promise { 38 | await this.rabbitMQService.consumeFromDirectExchange( 39 | this.ORDER_EXCHANGE, 40 | this.ORDER_STATUS_EVENT_TYPE.PAYMENT_SUCCEED, 41 | onEvent, 42 | ); 43 | } 44 | 45 | async consumeOrderCreatedEvent( 46 | onEvent: (event: OrderCreatedEvent) => void, 47 | ): Promise { 48 | await this.rabbitMQService.consumeFromDirectExchange( 49 | this.ORDER_EXCHANGE, 50 | this.ORDER_STATUS_EVENT_TYPE.ORDER_CREATED, 51 | onEvent, 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /notifications/src/core/notifications/usecases/send-email-after-unsuccessful-payment.usecase.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; 2 | 3 | import { MailService } from '@/infra/mail/services/mail-service.interface'; 4 | import { 5 | OrderPaymentFailedEvent, 6 | OrderStatusEventQueueService, 7 | } from '@/infra/rabbitmq/services/order-status-event-queue-service.interface'; 8 | import { TemplateEngineService } from '@/infra/template-engine/services/template-engine-service.interface'; 9 | 10 | @Injectable() 11 | export class SendEmailAfterUnsuccessfulPaymentUseCase implements OnModuleInit { 12 | private readonly logger = new Logger( 13 | SendEmailAfterUnsuccessfulPaymentUseCase.name, 14 | ); 15 | 16 | constructor( 17 | private readonly orderStatusEventQueueService: OrderStatusEventQueueService, 18 | private readonly mailService: MailService, 19 | private readonly templateEngineService: TemplateEngineService, 20 | ) {} 21 | 22 | async onModuleInit() { 23 | await this.orderStatusEventQueueService.consumePaymentFailedEvent( 24 | this.execute.bind(this), 25 | ); 26 | this.logger.log('Listening for orders payment failed events...'); 27 | } 28 | 29 | private async execute(event: OrderPaymentFailedEvent) { 30 | this.logger.log('Processing failed payment order event'); 31 | 32 | const html = await this.templateEngineService.compile( 33 | 'order-payment-failed.hbs', 34 | { 35 | orderId: event.order.id, 36 | totalPrice: (event.order.price / 100).toFixed(2), 37 | date: new Date().toLocaleDateString(), 38 | paymentMethod: event.payment.method, 39 | year: new Date().getFullYear(), 40 | }, 41 | ); 42 | 43 | this.mailService.sendMail({ 44 | html, 45 | subject: 'Payment Failed', 46 | to: { 47 | email: event.user.email, 48 | name: event.user.name, 49 | }, 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /notifications/src/core/notifications/usecases/send-email-after-successful-payment.usecase.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; 2 | 3 | import { MailService } from '@/infra/mail/services/mail-service.interface'; 4 | import { 5 | OrderPaymentSucceedEvent, 6 | OrderStatusEventQueueService, 7 | } from '@/infra/rabbitmq/services/order-status-event-queue-service.interface'; 8 | import { TemplateEngineService } from '@/infra/template-engine/services/template-engine-service.interface'; 9 | 10 | @Injectable() 11 | export class SendEmailAfterSuccessfulPaymentUseCase implements OnModuleInit { 12 | private readonly logger = new Logger( 13 | SendEmailAfterSuccessfulPaymentUseCase.name, 14 | ); 15 | 16 | constructor( 17 | private readonly orderStatusEventQueueService: OrderStatusEventQueueService, 18 | private readonly mailService: MailService, 19 | private readonly templateEngineService: TemplateEngineService, 20 | ) {} 21 | 22 | async onModuleInit() { 23 | await this.orderStatusEventQueueService.consumePaymentSucceedEvent( 24 | this.execute.bind(this), 25 | ); 26 | this.logger.log('Listening successful orders payment events...'); 27 | } 28 | 29 | private async execute(event: OrderPaymentSucceedEvent) { 30 | this.logger.log('Processing successful payment order event'); 31 | 32 | const html = await this.templateEngineService.compile( 33 | 'order-payment-succeed.hbs', 34 | { 35 | orderId: event.order.id, 36 | totalPrice: (event.order.price / 100).toFixed(2), 37 | date: new Date().toLocaleDateString(), 38 | paymentMethod: event.payment.method, 39 | year: new Date().getFullYear(), 40 | }, 41 | ); 42 | 43 | this.mailService.sendMail({ 44 | html, 45 | subject: 'Payment Confirmation', 46 | to: { 47 | email: event.user.email, 48 | name: event.user.name, 49 | }, 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /users/README.md: -------------------------------------------------------------------------------- 1 | # Service: orders 2 | 3 | This service is responsible to manage users and handle authentication. 4 | 5 | ## API 6 | 7 | ### Public functionalities 8 | 9 | 1. Login user 10 | 2. Signup user 11 | 12 | ### Public endpoints 13 | 14 | 1. **POST** /auth/login 15 | 2. **POST** /auth/signup 16 | 17 | ### Private functionalities 18 | 19 | 1. Find user by id 20 | 21 | ### Private endpoints 22 | 23 | 1. **GET** /users/:userId 24 | 25 | ## Dependencies 26 | 27 | ### Used by 28 | 29 | - orders service 30 | 31 | ### Dependencies Diagram 32 | 33 | users-service 34 | 35 | ## Running service using Docker 36 | 37 | ### 1. Setup .env file 38 | 39 | Create a file called .env on project's root folder, and put something like this inside: 40 | 41 | ``` 42 | # Database 43 | DB_URI=mongodb://database:27017 44 | DB_NAME=users-db 45 | DB_USER=admin 46 | DB_PASSWORD=admin 47 | DB_PORT=27017 48 | 49 | # Server 50 | SERVER_PORT=8080 51 | SERVER_JWT_SECRET=secret 52 | ``` 53 | 54 | ### 2. Starting the service 55 | 56 | Tu run in production mode just go to the project's root folder and run: 57 | 58 | ``` 59 | docker compose up -d 60 | ``` 61 | 62 | ### 3. Connect to private network 63 | 64 | All the containers will run on the same docker network called **ecommerce-soa**. 65 | 66 | If you haven't created it yet just run: 67 | 68 | ``` 69 | docker network create ecommerce-soa 70 | ``` 71 | 72 | If you already done this step, just connect with it 73 | 74 | ``` 75 | docker network connect ecommerce-soa users-nest-api 76 | ``` 77 | 78 | ## Reminder 79 | 80 | Of course in a real world situation we don't wanna run all the services in the same machine, that is the opposite of a distribute service oriented architecture, doing that you just increase the project complexity and don't gain the benefits of this architecture. 81 | 82 | I'm doing this because I value my money and don't want to owe the value of a house to AWS services. 83 | -------------------------------------------------------------------------------- /notifications/templates/order-payment-failed.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Payment Failed 5 | 17 | 18 | 19 |
20 |
21 |

Payment Failed

22 |
23 |
24 |

Dear Customer,

25 |

Unfortunately, your payment attempt for the following order has 26 | failed:

27 |
28 |

Order ID: {{orderId}}

29 |

Total Price: ${{totalPrice}}

30 |

Date: {{date}}

31 |

Payment Method: {{paymentMethod}}

32 |
33 |

We kindly ask you to try completing the payment again. If you 34 | continue to experience issues, please contact our support team for 35 | assistance.

36 |

Thank you for your patience, and we apologize for any inconvenience 37 | caused.

38 |
39 | 42 |
43 | 44 | -------------------------------------------------------------------------------- /orders/src/core/orders/orders.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | 4 | import { CartsModule } from '@/core/carts/carts.module'; 5 | import { RabbitMQModule } from '@/infra/rabbitmq/rabbitmq.module'; 6 | 7 | import { UsersModule } from '../users/users.module'; 8 | import { OrdersController } from './controllers/orders.controller'; 9 | import { Order, OrderSchema } from './entities/order.entity'; 10 | import { OrderServiceImpl } from './services/implementations/order.service'; 11 | import { OrderService } from './services/order-service.interface'; 12 | import { CancelOrderUseCase } from './usecases/cancel-order.usecase'; 13 | import { CreateOrderUseCase } from './usecases/create-order.usecase'; 14 | import { FindAllOrdersUseCase } from './usecases/find-all-orders.usecase'; 15 | import { FindAllUserOrdersUseCase } from './usecases/find-all-user-orders.usecase'; 16 | import { FindOrderByIdUseCase } from './usecases/find-order-by-id.usecase'; 17 | import { PayOrderWithCreditCardUseCase } from './usecases/pay-order-with-credit-card.usecase'; 18 | import { UpdateOrderAfterSuccessfulPaymentUseCase } from './usecases/update-order-after-successful-payment.usecase'; 19 | import { UpdateOrderAfterUnsuccessfulPaymentUseCase } from './usecases/update-order-after-unsuccessful-payment.usecase'; 20 | 21 | @Module({ 22 | imports: [ 23 | MongooseModule.forFeature([{ name: Order.name, schema: OrderSchema }]), 24 | CartsModule, 25 | UsersModule, 26 | RabbitMQModule, 27 | ], 28 | providers: [ 29 | { provide: OrderService, useClass: OrderServiceImpl }, 30 | CancelOrderUseCase, 31 | CreateOrderUseCase, 32 | FindAllOrdersUseCase, 33 | FindAllUserOrdersUseCase, 34 | FindOrderByIdUseCase, 35 | PayOrderWithCreditCardUseCase, 36 | UpdateOrderAfterSuccessfulPaymentUseCase, 37 | UpdateOrderAfterUnsuccessfulPaymentUseCase, 38 | ], 39 | controllers: [OrdersController], 40 | }) 41 | export class OrdersModule {} 42 | -------------------------------------------------------------------------------- /notifications/templates/order-payment-succeed.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Payment Confirmation 5 | 17 | 18 | 19 |
20 |
21 |

Payment Confirmation

22 |
23 |
24 |

Dear Customer,

25 |

We are pleased to inform you that your payment has been successfully 26 | received. Below are the details of your order:

27 |
28 |

Order ID: {{orderId}}

29 |

Total Price: ${{totalPrice}}

30 |

Date: {{date}}

31 |

Payment Method: {{paymentMethod}}

32 |
33 |

Your order is now being processed, and we’ll notify you once it has 34 | been shipped.

35 |

If you have any questions, feel free to contact our support team.

36 |

Thank you for shopping with us!

37 |
38 | 41 |
42 | 43 | -------------------------------------------------------------------------------- /products/README.md: -------------------------------------------------------------------------------- 1 | # Service: products 2 | 3 | This service is responsible to manage the products stock. 4 | 5 | ## API 6 | 7 | ### Public functionalities 8 | 9 | 1. List all products (client) 10 | 2. Find product by id (client) 11 | 3. Create product (admin) 12 | 4. Delete product (admin) 13 | 5. Update product (admin) 14 | 15 | ### Public endpoints 16 | 17 | 1. **GET** /products 18 | 2. **GET** /products/:id 19 | 3. **POST** /products 20 | 4. **DELETE** /products/:id 21 | 5. **PUT** /products/:id 22 | 23 | ## Dependencies 24 | 25 | ### Used by 26 | 27 | - carts service 28 | 29 | ### Dependencies Diagram 30 | 31 | products-service 32 | 33 | ## Running service using Docker 34 | 35 | ### 1. Setup .env file 36 | 37 | Create a file called .env on project's root folder, and put something like this inside: 38 | 39 | ``` 40 | # Database 41 | DB_URI=mongodb://database:27017 42 | DB_NAME=products-db 43 | DB_USER=admin 44 | DB_PASSWORD=admin 45 | DB_PORT=27017 46 | 47 | # Server 48 | SERVER_PORT=8080 49 | RABBIT_MQ_URL=amqp://admin:admin@rabbitmq 50 | ``` 51 | 52 | ### 2. Starting the service 53 | 54 | Tu run in production mode just go to the project's root folder and run: 55 | 56 | ``` 57 | docker compose up -d 58 | ``` 59 | 60 | ### 3. Connect to private network 61 | 62 | All the containers will run on the same docker network called **ecommerce-soa**. 63 | 64 | If you haven't created it yet just run: 65 | 66 | ``` 67 | docker network create ecommerce-soa 68 | ``` 69 | 70 | If you already done this step, just connect with it 71 | 72 | ``` 73 | docker network connect ecommerce-soa products-nest-api 74 | ``` 75 | 76 | ## Reminder 77 | 78 | Of course in a real world situation we don't wanna run all the services in the same machine, that is the opposite of a distribute service oriented architecture, doing that you just increase the project complexity and don't gain the benefits of this architecture. 79 | 80 | I'm doing this because I value my money and don't want to owe the value of a house to AWS services. 81 | -------------------------------------------------------------------------------- /notifications/README.md: -------------------------------------------------------------------------------- 1 | # Service: notifications 2 | 3 | This service is responsible to listen application events and send emails according to the event type. 4 | 5 | This service is fully private, that means he doesn't communicate with the external world, only listen to events inside the private network and responds to it. 6 | 7 | Here we're using RabbitMQ to enqueue the events, and let the notifications service consumes it. 8 | 9 | ## Functionalities 10 | 11 | - Send email for the user after order creation 12 | - Send email for the user after a successful order payment 13 | - Send email for the user after a unsuccessful order payment 14 | 15 | ## Events 16 | 17 | - order-paid 18 | - order-payment-failed 19 | - order-payment-succeed 20 | 21 | ## Diagram 22 | 23 | notifications-service 24 | 25 | ## Running service using Docker 26 | 27 | ### 1. Setup .env file 28 | 29 | Create a file called .env on project's root folder, and put something like this inside: 30 | 31 | ``` 32 | RABBIT_MQ_URL=amqp://admin:admin@rabbitmq 33 | 34 | # Mail 35 | MAIL_HOST_USER=your gmail address 36 | MAIL_HOST_NAME=your name 37 | MAIL_HOST_PASSWORD=your gmail app password 38 | ``` 39 | 40 | ### 2. Starting the service 41 | 42 | Tu run in production mode just go to the project's root folder and run: 43 | 44 | ``` 45 | docker compose up -d 46 | ``` 47 | 48 | ### 3. Connect to private network 49 | 50 | All the containers will run on the same docker network called **ecommerce-soa**. 51 | 52 | If you haven't created it yet just run: 53 | 54 | ``` 55 | docker network create ecommerce-soa 56 | ``` 57 | 58 | If you already done this step, just connect with it 59 | 60 | ``` 61 | docker network connect ecommerce-soa notifications-nest-api 62 | ``` 63 | 64 | ## Reminder 65 | 66 | Of course in a real world situation we don't wanna run all the services in the same machine, that is the opposite of a distribute service oriented architecture, doing that you just increase the project complexity and don't gain the benefits of this architecture. 67 | 68 | I'm doing this because I value my money and don't want to owe the value of a house to AWS services. 69 | --------------------------------------------------------------------------------