Merge branch 'hexagon' into 'main'
Hexagon; v1.0.0 See merge request v3/service/user!40
This commit is contained in:
		
						commit
						8e07b3b02a
					
				| 
						 | 
					@ -7,9 +7,9 @@ HEALTH_SERVICE_PORT=6001
 | 
				
			||||||
# PRISMA
 | 
					# PRISMA
 | 
				
			||||||
DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=user"
 | 
					DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=user"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# RABBIT MQ
 | 
					# MESSAGE BROKER
 | 
				
			||||||
RMQ_URI=amqp://v3-broker:5672
 | 
					MESSAGE_BROKER_URI=amqp://v3-broker:5672
 | 
				
			||||||
RMQ_EXCHANGE=mobicoop
 | 
					MESSAGE_BROKER_EXCHANGE=mobicoop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# REDIS
 | 
					# REDIS
 | 
				
			||||||
REDIS_HOST=v3-redis
 | 
					REDIS_HOST=v3-redis
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										23
									
								
								README.md
								
								
								
								
							
							
						
						
									
										23
									
								
								README.md
								
								
								
								
							| 
						 | 
					@ -48,24 +48,15 @@ npm run migrate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The app exposes the following [gRPC](https://grpc.io/) services :
 | 
					The app exposes the following [gRPC](https://grpc.io/) services :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-   **FindByUuid** : find a user by its uuid
 | 
					-   **FindById** : find a user by its id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ```json
 | 
					    ```json
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        "uuid": "80126a61-d128-4f96-afdb-92e33c75a3e1"
 | 
					        "id": "80126a61-d128-4f96-afdb-92e33c75a3e1"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    ```
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-   **FindAll** : find all users; you can use pagination with `page` (default:_1_) and `perPage` (default:_10_)
 | 
					-   **Create** : create a user
 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```json
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        "page": 1,
 | 
					 | 
				
			||||||
        "perPage": 10
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-   **Create** : create a user (note that uuid is optional, a uuid will be automatically attributed if it is not provided)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ```json
 | 
					    ```json
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
| 
						 | 
					@ -82,15 +73,15 @@ The app exposes the following [gRPC](https://grpc.io/) services :
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        "firstName": "Jezabel-Katarina",
 | 
					        "firstName": "Jezabel-Katarina",
 | 
				
			||||||
        "email": "jezabel-katarina.doe@email.com",
 | 
					        "email": "jezabel-katarina.doe@email.com",
 | 
				
			||||||
        "uuid": "30f49838-3f24-42bb-a489-8ffb480173ae"
 | 
					        "id": "30f49838-3f24-42bb-a489-8ffb480173ae"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    ```
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-   **Delete** : delete a user by its uuid
 | 
					-   **Delete** : delete a user by its id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ```json
 | 
					    ```json
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        "uuid": "80126a61-d128-4f96-afdb-92e33c75a3e1"
 | 
					        "id": "80126a61-d128-4f96-afdb-92e33c75a3e1"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    ```
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -102,7 +93,7 @@ As mentionned earlier, RabbitMQ messages are sent after these events :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-   **Update** (message : the updated user informations)
 | 
					-   **Update** (message : the updated user informations)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-   **Delete** (message : the uuid of the deleted user)
 | 
					-   **Delete** (message : the id of the deleted user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Various messages are also sent for logging purpose.
 | 
					Various messages are also sent for logging purpose.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										49
									
								
								package.json
								
								
								
								
							
							
						
						
									
										49
									
								
								package.json
								
								
								
								
							| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "name": "@mobicoop/user",
 | 
					  "name": "@mobicoop/user",
 | 
				
			||||||
  "version": "0.0.1",
 | 
					  "version": "1.0.0",
 | 
				
			||||||
  "description": "Mobicoop V3 User Service",
 | 
					  "description": "Mobicoop V3 User Service",
 | 
				
			||||||
  "author": "sbriat",
 | 
					  "author": "sbriat",
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
| 
						 | 
					@ -17,33 +17,32 @@
 | 
				
			||||||
    "lint:check": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix-dry-run --ignore-path .gitignore",
 | 
					    "lint:check": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix-dry-run --ignore-path .gitignore",
 | 
				
			||||||
    "pretty:check": "./node_modules/.bin/prettier --check .",
 | 
					    "pretty:check": "./node_modules/.bin/prettier --check .",
 | 
				
			||||||
    "pretty": "./node_modules/.bin/prettier --write .",
 | 
					    "pretty": "./node_modules/.bin/prettier --write .",
 | 
				
			||||||
    "test": "npm run migrate:test && dotenv -e .env.test jest",
 | 
					    "test": "npm run test:unit && npm run test:integration",
 | 
				
			||||||
    "test:unit": "jest --testPathPattern 'tests/unit/' --verbose",
 | 
					    "test:unit": "jest --testPathPattern 'tests/unit/' --verbose",
 | 
				
			||||||
    "test:unit:ci": "jest --testPathPattern 'tests/unit/' --coverage",
 | 
					    "test:unit:ci": "jest --testPathPattern 'tests/unit/' --coverage",
 | 
				
			||||||
    "test:integration": "npm run migrate:test && dotenv -e .env.test -- jest --testPathPattern 'tests/integration/' --verbose",
 | 
					    "test:integration": "npm run migrate:test && dotenv -e .env.test -- jest --testPathPattern 'tests/integration/' --verbose --runInBand",
 | 
				
			||||||
    "test:integration:ci": "npm run migrate:test:ci && dotenv -e ci/.env.ci -- jest --testPathPattern 'tests/integration/'",
 | 
					    "test:integration:ci": "npm run migrate:test:ci && dotenv -e ci/.env.ci -- jest --testPathPattern 'tests/integration/' --runInBand",
 | 
				
			||||||
    "test:cov": "jest --testPathPattern 'tests/unit/' --coverage",
 | 
					    "test:cov": "jest --testPathPattern 'tests/unit/' --coverage",
 | 
				
			||||||
    "test:e2e": "jest --config ./test/jest-e2e.json",
 | 
					    "test:e2e": "jest --config ./test/jest-e2e.json",
 | 
				
			||||||
    "generate": "docker exec v3-user-api sh -c 'npx prisma generate'",
 | 
					    "migrate": "docker exec v3-auth-api sh -c 'npx prisma migrate dev'",
 | 
				
			||||||
    "migrate": "docker exec v3-user-api sh -c 'npx prisma migrate dev'",
 | 
					 | 
				
			||||||
    "migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy",
 | 
					    "migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy",
 | 
				
			||||||
    "migrate:test:ci": "dotenv -e ci/.env.ci -- npx prisma migrate deploy",
 | 
					    "migrate:test:ci": "dotenv -e ci/.env.ci -- npx prisma migrate deploy",
 | 
				
			||||||
    "migrate:deploy": "npx prisma migrate deploy"
 | 
					    "migrate:deploy": "npx prisma migrate deploy"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@automapper/classes": "^8.7.7",
 | 
					 | 
				
			||||||
    "@automapper/core": "^8.7.7",
 | 
					 | 
				
			||||||
    "@automapper/nestjs": "^8.7.7",
 | 
					 | 
				
			||||||
    "@grpc/grpc-js": "^1.8.0",
 | 
					    "@grpc/grpc-js": "^1.8.0",
 | 
				
			||||||
    "@grpc/proto-loader": "^0.7.4",
 | 
					    "@grpc/proto-loader": "^0.7.4",
 | 
				
			||||||
    "@liaoliaots/nestjs-redis": "^9.0.5",
 | 
					    "@liaoliaots/nestjs-redis": "^9.0.5",
 | 
				
			||||||
    "@mobicoop/configuration-module": "^1.1.0",
 | 
					    "@mobicoop/configuration-module": "^1.2.0",
 | 
				
			||||||
    "@mobicoop/message-broker-module": "^1.0.5",
 | 
					    "@mobicoop/ddd-library": "^1.0.0",
 | 
				
			||||||
 | 
					    "@mobicoop/health-module": "^2.0.0",
 | 
				
			||||||
 | 
					    "@mobicoop/message-broker-module": "^1.2.0",
 | 
				
			||||||
    "@nestjs/cache-manager": "^1.0.0",
 | 
					    "@nestjs/cache-manager": "^1.0.0",
 | 
				
			||||||
    "@nestjs/common": "^9.0.0",
 | 
					    "@nestjs/common": "^9.0.0",
 | 
				
			||||||
    "@nestjs/config": "^2.2.0",
 | 
					    "@nestjs/config": "^2.2.0",
 | 
				
			||||||
    "@nestjs/core": "^9.0.0",
 | 
					    "@nestjs/core": "^9.0.0",
 | 
				
			||||||
    "@nestjs/cqrs": "^9.0.1",
 | 
					    "@nestjs/cqrs": "^9.0.1",
 | 
				
			||||||
 | 
					    "@nestjs/event-emitter": "^2.0.0",
 | 
				
			||||||
    "@nestjs/microservices": "^9.2.1",
 | 
					    "@nestjs/microservices": "^9.2.1",
 | 
				
			||||||
    "@nestjs/platform-express": "^9.0.0",
 | 
					    "@nestjs/platform-express": "^9.0.0",
 | 
				
			||||||
    "@nestjs/terminus": "^9.2.2",
 | 
					    "@nestjs/terminus": "^9.2.2",
 | 
				
			||||||
| 
						 | 
					@ -54,6 +53,7 @@
 | 
				
			||||||
    "cache-manager": "^5.2.1",
 | 
					    "cache-manager": "^5.2.1",
 | 
				
			||||||
    "cache-manager-ioredis-yet": "^1.1.0",
 | 
					    "cache-manager-ioredis-yet": "^1.1.0",
 | 
				
			||||||
    "class-transformer": "^0.5.1",
 | 
					    "class-transformer": "^0.5.1",
 | 
				
			||||||
 | 
					    "class-validator": "^0.14.0",
 | 
				
			||||||
    "dotenv-cli": "^6.0.0",
 | 
					    "dotenv-cli": "^6.0.0",
 | 
				
			||||||
    "ioredis": "^5.3.0"
 | 
					    "ioredis": "^5.3.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
| 
						 | 
					@ -63,6 +63,7 @@
 | 
				
			||||||
    "@nestjs/testing": "^9.0.0",
 | 
					    "@nestjs/testing": "^9.0.0",
 | 
				
			||||||
    "@types/jest": "^29.2.5",
 | 
					    "@types/jest": "^29.2.5",
 | 
				
			||||||
    "@types/node": "^18.11.18",
 | 
					    "@types/node": "^18.11.18",
 | 
				
			||||||
 | 
					    "@types/uuid": "^9.0.2",
 | 
				
			||||||
    "eslint": "^8.0.1",
 | 
					    "eslint": "^8.0.1",
 | 
				
			||||||
    "eslint-config-prettier": "^8.3.0",
 | 
					    "eslint-config-prettier": "^8.3.0",
 | 
				
			||||||
    "eslint-plugin-prettier": "^4.0.0",
 | 
					    "eslint-plugin-prettier": "^4.0.0",
 | 
				
			||||||
| 
						 | 
					@ -84,12 +85,13 @@
 | 
				
			||||||
      "ts"
 | 
					      "ts"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "modulePathIgnorePatterns": [
 | 
					    "modulePathIgnorePatterns": [
 | 
				
			||||||
      ".controller.ts",
 | 
					 | 
				
			||||||
      ".module.ts",
 | 
					      ".module.ts",
 | 
				
			||||||
      ".request.ts",
 | 
					      ".dto.ts",
 | 
				
			||||||
      ".presenter.ts",
 | 
					      ".constants.ts",
 | 
				
			||||||
      ".profile.ts",
 | 
					      ".di-tokens.ts",
 | 
				
			||||||
      ".exception.ts",
 | 
					      ".response.ts",
 | 
				
			||||||
 | 
					      ".port.ts",
 | 
				
			||||||
 | 
					      "prisma.service.ts",
 | 
				
			||||||
      "main.ts"
 | 
					      "main.ts"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "rootDir": "src",
 | 
					    "rootDir": "src",
 | 
				
			||||||
| 
						 | 
					@ -101,15 +103,20 @@
 | 
				
			||||||
      "**/*.(t|j)s"
 | 
					      "**/*.(t|j)s"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "coveragePathIgnorePatterns": [
 | 
					    "coveragePathIgnorePatterns": [
 | 
				
			||||||
      ".controller.ts",
 | 
					 | 
				
			||||||
      ".module.ts",
 | 
					      ".module.ts",
 | 
				
			||||||
      ".request.ts",
 | 
					      ".dto.ts",
 | 
				
			||||||
      ".presenter.ts",
 | 
					      ".constants.ts",
 | 
				
			||||||
      ".profile.ts",
 | 
					      ".di-tokens.ts",
 | 
				
			||||||
      ".exception.ts",
 | 
					      ".response.ts",
 | 
				
			||||||
 | 
					      ".port.ts",
 | 
				
			||||||
 | 
					      "prisma.service.ts",
 | 
				
			||||||
      "main.ts"
 | 
					      "main.ts"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "coverageDirectory": "../coverage",
 | 
					    "coverageDirectory": "../coverage",
 | 
				
			||||||
 | 
					    "moduleNameMapper": {
 | 
				
			||||||
 | 
					      "^@modules(.*)": "<rootDir>/modules/$1",
 | 
				
			||||||
 | 
					      "^@src(.*)": "<rootDir>$1"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "testEnvironment": "node"
 | 
					    "testEnvironment": "node"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,2 +0,0 @@
 | 
				
			||||||
export const MESSAGE_BROKER_PUBLISHER = Symbol();
 | 
					 | 
				
			||||||
export const MESSAGE_PUBLISHER = Symbol();
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,70 +1,67 @@
 | 
				
			||||||
import { classes } from '@automapper/classes';
 | 
					 | 
				
			||||||
import { AutomapperModule } from '@automapper/nestjs';
 | 
					 | 
				
			||||||
import { Module } from '@nestjs/common';
 | 
					import { Module } from '@nestjs/common';
 | 
				
			||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
 | 
					import { ConfigModule, ConfigService } from '@nestjs/config';
 | 
				
			||||||
import { HealthModule } from './modules/health/health.module';
 | 
					 | 
				
			||||||
import { UserModule } from './modules/user/user.module';
 | 
					import { UserModule } from './modules/user/user.module';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  ConfigurationModule,
 | 
					  ConfigurationModule,
 | 
				
			||||||
  ConfigurationModuleOptions,
 | 
					  ConfigurationModuleOptions,
 | 
				
			||||||
} from '@mobicoop/configuration-module';
 | 
					} from '@mobicoop/configuration-module';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  MessageBrokerModule,
 | 
					  HealthModule,
 | 
				
			||||||
  MessageBrokerModuleOptions,
 | 
					  HealthModuleOptions,
 | 
				
			||||||
} from '@mobicoop/message-broker-module';
 | 
					  HealthRepositoryPort,
 | 
				
			||||||
 | 
					} from '@mobicoop/health-module';
 | 
				
			||||||
 | 
					import { MessagerModule } from './modules/messager/messager.module';
 | 
				
			||||||
 | 
					import { USER_REPOSITORY } from './modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { MESSAGE_PUBLISHER } from './modules/messager/messager.di-tokens';
 | 
				
			||||||
 | 
					import { MessagePublisherPort } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { EventEmitterModule } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Module({
 | 
					@Module({
 | 
				
			||||||
  imports: [
 | 
					  imports: [
 | 
				
			||||||
    ConfigModule.forRoot({ isGlobal: true }),
 | 
					    ConfigModule.forRoot({ isGlobal: true }),
 | 
				
			||||||
    AutomapperModule.forRoot({ strategyInitializer: classes() }),
 | 
					    EventEmitterModule.forRoot(),
 | 
				
			||||||
    UserModule,
 | 
					    ConfigurationModule.forRootAsync({
 | 
				
			||||||
    MessageBrokerModule.forRootAsync(
 | 
					      imports: [ConfigModule],
 | 
				
			||||||
      {
 | 
					      inject: [ConfigService],
 | 
				
			||||||
        imports: [ConfigModule],
 | 
					      useFactory: async (
 | 
				
			||||||
        inject: [ConfigService],
 | 
					        configService: ConfigService,
 | 
				
			||||||
        useFactory: async (
 | 
					      ): Promise<ConfigurationModuleOptions> => ({
 | 
				
			||||||
          configService: ConfigService,
 | 
					        domain: configService.get<string>('SERVICE_CONFIGURATION_DOMAIN'),
 | 
				
			||||||
        ): Promise<MessageBrokerModuleOptions> => ({
 | 
					        messageBroker: {
 | 
				
			||||||
          uri: configService.get<string>('MESSAGE_BROKER_URI'),
 | 
					          uri: configService.get<string>('MESSAGE_BROKER_URI'),
 | 
				
			||||||
          exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
 | 
					          exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
 | 
				
			||||||
          handlers: {},
 | 
					        },
 | 
				
			||||||
        }),
 | 
					        redis: {
 | 
				
			||||||
      },
 | 
					          host: configService.get<string>('REDIS_HOST'),
 | 
				
			||||||
      false,
 | 
					          password: configService.get<string>('REDIS_PASSWORD'),
 | 
				
			||||||
    ),
 | 
					          port: configService.get<number>('REDIS_PORT'),
 | 
				
			||||||
    ConfigurationModule.forRootAsync(
 | 
					        },
 | 
				
			||||||
      {
 | 
					        setConfigurationBrokerQueue: 'user-configuration-create-update',
 | 
				
			||||||
        imports: [ConfigModule],
 | 
					        deleteConfigurationQueue: 'user-configuration-delete',
 | 
				
			||||||
        inject: [ConfigService],
 | 
					        propagateConfigurationQueue: 'user-configuration-propagate',
 | 
				
			||||||
        useFactory: async (
 | 
					      }),
 | 
				
			||||||
          configService: ConfigService,
 | 
					    }),
 | 
				
			||||||
        ): Promise<ConfigurationModuleOptions> => ({
 | 
					    HealthModule.forRootAsync({
 | 
				
			||||||
          domain: configService.get<string>('SERVICE_CONFIGURATION_DOMAIN'),
 | 
					      imports: [UserModule, MessagerModule],
 | 
				
			||||||
          messageBroker: {
 | 
					      inject: [USER_REPOSITORY, MESSAGE_PUBLISHER],
 | 
				
			||||||
            uri: configService.get<string>('MESSAGE_BROKER_URI'),
 | 
					      useFactory: async (
 | 
				
			||||||
            exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
 | 
					        userRepository: HealthRepositoryPort,
 | 
				
			||||||
 | 
					        messagePublisher: MessagePublisherPort,
 | 
				
			||||||
 | 
					      ): Promise<HealthModuleOptions> => ({
 | 
				
			||||||
 | 
					        serviceName: 'user',
 | 
				
			||||||
 | 
					        criticalLoggingKey: 'logging.user.health.crit',
 | 
				
			||||||
 | 
					        checkRepositories: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            name: 'UserRepository',
 | 
				
			||||||
 | 
					            repository: userRepository,
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          redis: {
 | 
					        ],
 | 
				
			||||||
            host: configService.get<string>('REDIS_HOST'),
 | 
					        messagePublisher,
 | 
				
			||||||
            password: configService.get<string>('REDIS_PASSWORD'),
 | 
					      }),
 | 
				
			||||||
            port: configService.get<number>('REDIS_PORT'),
 | 
					    }),
 | 
				
			||||||
          },
 | 
					    UserModule,
 | 
				
			||||||
          setConfigurationBrokerRoutingKeys: [
 | 
					    MessagerModule,
 | 
				
			||||||
            'configuration.create',
 | 
					 | 
				
			||||||
            'configuration.update',
 | 
					 | 
				
			||||||
          ],
 | 
					 | 
				
			||||||
          deleteConfigurationRoutingKey: 'configuration.delete',
 | 
					 | 
				
			||||||
          propagateConfigurationRoutingKey: 'configuration.propagate',
 | 
					 | 
				
			||||||
          setConfigurationBrokerQueue: 'user-configuration-create-update',
 | 
					 | 
				
			||||||
          deleteConfigurationQueue: 'user-configuration-delete',
 | 
					 | 
				
			||||||
          propagateConfigurationQueue: 'user-configuration-propagate',
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      true,
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    HealthModule,
 | 
					 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  controllers: [],
 | 
					  exports: [UserModule, MessagerModule],
 | 
				
			||||||
  providers: [],
 | 
					 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class AppModule {}
 | 
					export class AppModule {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,6 @@ syntax = "proto3";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package health;
 | 
					package health;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
service Health {
 | 
					service Health {
 | 
				
			||||||
  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
 | 
					  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -18,4 +17,5 @@ message HealthCheckResponse {
 | 
				
			||||||
    NOT_SERVING = 2;
 | 
					    NOT_SERVING = 2;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  ServingStatus status = 1;
 | 
					  ServingStatus status = 1;
 | 
				
			||||||
 | 
					  string message = 2;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,3 +0,0 @@
 | 
				
			||||||
export interface IPublishMessage {
 | 
					 | 
				
			||||||
  publish(routingKey: string, message: string): void;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -13,8 +13,8 @@ async function bootstrap() {
 | 
				
			||||||
    options: {
 | 
					    options: {
 | 
				
			||||||
      package: ['user', 'health'],
 | 
					      package: ['user', 'health'],
 | 
				
			||||||
      protoPath: [
 | 
					      protoPath: [
 | 
				
			||||||
        join(__dirname, 'modules/user/adapters/primaries/user.proto'),
 | 
					        join(__dirname, 'modules/user/interface/grpc-controllers/user.proto'),
 | 
				
			||||||
        join(__dirname, 'modules/health/adapters/primaries/health.proto'),
 | 
					        join(__dirname, 'health.proto'),
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      url: process.env.SERVICE_URL + ':' + process.env.SERVICE_PORT,
 | 
					      url: process.env.SERVICE_URL + ':' + process.env.SERVICE_PORT,
 | 
				
			||||||
      loader: { keepCase: true },
 | 
					      loader: { keepCase: true },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,259 +0,0 @@
 | 
				
			||||||
import { Injectable } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { Prisma } from '@prisma/client';
 | 
					 | 
				
			||||||
import { DatabaseException } from '../../exceptions/database.exception';
 | 
					 | 
				
			||||||
import { ICollection } from '../../interfaces/collection.interface';
 | 
					 | 
				
			||||||
import { IRepository } from '../../interfaces/repository.interface';
 | 
					 | 
				
			||||||
import { PrismaService } from './prisma-service';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Child classes MUST redefined _model property with appropriate model name
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
@Injectable()
 | 
					 | 
				
			||||||
export abstract class PrismaRepository<T> implements IRepository<T> {
 | 
					 | 
				
			||||||
  protected model: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(protected readonly prisma: PrismaService) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  findAll = async (
 | 
					 | 
				
			||||||
    page = 1,
 | 
					 | 
				
			||||||
    perPage = 10,
 | 
					 | 
				
			||||||
    where?: any,
 | 
					 | 
				
			||||||
    include?: any,
 | 
					 | 
				
			||||||
  ): Promise<ICollection<T>> => {
 | 
					 | 
				
			||||||
    const [data, total] = await this.prisma.$transaction([
 | 
					 | 
				
			||||||
      this.prisma[this.model].findMany({
 | 
					 | 
				
			||||||
        where,
 | 
					 | 
				
			||||||
        include,
 | 
					 | 
				
			||||||
        skip: (page - 1) * perPage,
 | 
					 | 
				
			||||||
        take: perPage,
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
      this.prisma[this.model].count({
 | 
					 | 
				
			||||||
        where,
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
    ]);
 | 
					 | 
				
			||||||
    return Promise.resolve({
 | 
					 | 
				
			||||||
      data,
 | 
					 | 
				
			||||||
      total,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  findOneByUuid = async (uuid: string): Promise<T> => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const entity = await this.prisma[this.model].findUnique({
 | 
					 | 
				
			||||||
        where: { uuid },
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return entity;
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      if (e instanceof Prisma.PrismaClientKnownRequestError) {
 | 
					 | 
				
			||||||
        throw new DatabaseException(
 | 
					 | 
				
			||||||
          Prisma.PrismaClientKnownRequestError.name,
 | 
					 | 
				
			||||||
          e.code,
 | 
					 | 
				
			||||||
          e.message,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        throw new DatabaseException();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  findOne = async (where: any, include?: any): Promise<T> => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const entity = await this.prisma[this.model].findFirst({
 | 
					 | 
				
			||||||
        where: where,
 | 
					 | 
				
			||||||
        include: include,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return entity;
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      if (e instanceof Prisma.PrismaClientKnownRequestError) {
 | 
					 | 
				
			||||||
        throw new DatabaseException(
 | 
					 | 
				
			||||||
          Prisma.PrismaClientKnownRequestError.name,
 | 
					 | 
				
			||||||
          e.code,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        throw new DatabaseException();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // TODO : using any is not good, but needed for nested entities
 | 
					 | 
				
			||||||
  // TODO : Refactor for good clean architecture ?
 | 
					 | 
				
			||||||
  create = async (entity: Partial<T> | any, include?: any): Promise<T> => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const res = await this.prisma[this.model].create({
 | 
					 | 
				
			||||||
        data: entity,
 | 
					 | 
				
			||||||
        include: include,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return res;
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      if (e instanceof Prisma.PrismaClientKnownRequestError) {
 | 
					 | 
				
			||||||
        throw new DatabaseException(
 | 
					 | 
				
			||||||
          Prisma.PrismaClientKnownRequestError.name,
 | 
					 | 
				
			||||||
          e.code,
 | 
					 | 
				
			||||||
          e.message,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        throw new DatabaseException();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  update = async (uuid: string, entity: Partial<T>): Promise<T> => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const updatedEntity = await this.prisma[this.model].update({
 | 
					 | 
				
			||||||
        where: { uuid },
 | 
					 | 
				
			||||||
        data: entity,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      return updatedEntity;
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      if (e instanceof Prisma.PrismaClientKnownRequestError) {
 | 
					 | 
				
			||||||
        throw new DatabaseException(
 | 
					 | 
				
			||||||
          Prisma.PrismaClientKnownRequestError.name,
 | 
					 | 
				
			||||||
          e.code,
 | 
					 | 
				
			||||||
          e.message,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        throw new DatabaseException();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  updateWhere = async (
 | 
					 | 
				
			||||||
    where: any,
 | 
					 | 
				
			||||||
    entity: Partial<T> | any,
 | 
					 | 
				
			||||||
    include?: any,
 | 
					 | 
				
			||||||
  ): Promise<T> => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const updatedEntity = await this.prisma[this.model].update({
 | 
					 | 
				
			||||||
        where: where,
 | 
					 | 
				
			||||||
        data: entity,
 | 
					 | 
				
			||||||
        include: include,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return updatedEntity;
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      if (e instanceof Prisma.PrismaClientKnownRequestError) {
 | 
					 | 
				
			||||||
        throw new DatabaseException(
 | 
					 | 
				
			||||||
          Prisma.PrismaClientKnownRequestError.name,
 | 
					 | 
				
			||||||
          e.code,
 | 
					 | 
				
			||||||
          e.message,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        throw new DatabaseException();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  delete = async (uuid: string): Promise<T> => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const entity = await this.prisma[this.model].delete({
 | 
					 | 
				
			||||||
        where: { uuid },
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return entity;
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      if (e instanceof Prisma.PrismaClientKnownRequestError) {
 | 
					 | 
				
			||||||
        throw new DatabaseException(
 | 
					 | 
				
			||||||
          Prisma.PrismaClientKnownRequestError.name,
 | 
					 | 
				
			||||||
          e.code,
 | 
					 | 
				
			||||||
          e.message,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        throw new DatabaseException();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  deleteMany = async (where: any): Promise<void> => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const entity = await this.prisma[this.model].deleteMany({
 | 
					 | 
				
			||||||
        where: where,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return entity;
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      if (e instanceof Prisma.PrismaClientKnownRequestError) {
 | 
					 | 
				
			||||||
        throw new DatabaseException(
 | 
					 | 
				
			||||||
          Prisma.PrismaClientKnownRequestError.name,
 | 
					 | 
				
			||||||
          e.code,
 | 
					 | 
				
			||||||
          e.message,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        throw new DatabaseException();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  findAllByQuery = async (
 | 
					 | 
				
			||||||
    include: string[],
 | 
					 | 
				
			||||||
    where: string[],
 | 
					 | 
				
			||||||
  ): Promise<ICollection<T>> => {
 | 
					 | 
				
			||||||
    const query = `SELECT ${include.join(',')} FROM ${
 | 
					 | 
				
			||||||
      this.model
 | 
					 | 
				
			||||||
    } WHERE ${where.join(' AND ')}`;
 | 
					 | 
				
			||||||
    const data: T[] = await this.prisma.$queryRawUnsafe(query);
 | 
					 | 
				
			||||||
    return Promise.resolve({
 | 
					 | 
				
			||||||
      data,
 | 
					 | 
				
			||||||
      total: data.length,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  createWithFields = async (fields: object): Promise<number> => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const command = `INSERT INTO ${this.model} ("${Object.keys(fields).join(
 | 
					 | 
				
			||||||
        '","',
 | 
					 | 
				
			||||||
      )}") VALUES (${Object.values(fields).join(',')})`;
 | 
					 | 
				
			||||||
      return await this.prisma.$executeRawUnsafe(command);
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      if (e instanceof Prisma.PrismaClientKnownRequestError) {
 | 
					 | 
				
			||||||
        throw new DatabaseException(
 | 
					 | 
				
			||||||
          Prisma.PrismaClientKnownRequestError.name,
 | 
					 | 
				
			||||||
          e.code,
 | 
					 | 
				
			||||||
          e.message,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        throw new DatabaseException();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  updateWithFields = async (uuid: string, entity: object): Promise<number> => {
 | 
					 | 
				
			||||||
    entity['"updatedAt"'] = `to_timestamp(${Date.now()} / 1000.0)`;
 | 
					 | 
				
			||||||
    const values = Object.keys(entity).map((key) => `${key} = ${entity[key]}`);
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const command = `UPDATE ${this.model} SET ${values.join(
 | 
					 | 
				
			||||||
        ', ',
 | 
					 | 
				
			||||||
      )} WHERE uuid = '${uuid}'`;
 | 
					 | 
				
			||||||
      return await this.prisma.$executeRawUnsafe(command);
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      if (e instanceof Prisma.PrismaClientKnownRequestError) {
 | 
					 | 
				
			||||||
        throw new DatabaseException(
 | 
					 | 
				
			||||||
          Prisma.PrismaClientKnownRequestError.name,
 | 
					 | 
				
			||||||
          e.code,
 | 
					 | 
				
			||||||
          e.message,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        throw new DatabaseException();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  healthCheck = async (): Promise<boolean> => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await this.prisma.$queryRaw`SELECT 1`;
 | 
					 | 
				
			||||||
      return true;
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      if (e instanceof Prisma.PrismaClientKnownRequestError) {
 | 
					 | 
				
			||||||
        throw new DatabaseException(
 | 
					 | 
				
			||||||
          Prisma.PrismaClientKnownRequestError.name,
 | 
					 | 
				
			||||||
          e.code,
 | 
					 | 
				
			||||||
          e.message,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        throw new DatabaseException();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,9 +0,0 @@
 | 
				
			||||||
import { Module } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { PrismaService } from './adapters/secondaries/prisma-service';
 | 
					 | 
				
			||||||
import { UserRepository } from './domain/user-repository';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Module({
 | 
					 | 
				
			||||||
  providers: [PrismaService, UserRepository],
 | 
					 | 
				
			||||||
  exports: [PrismaService, UserRepository],
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
export class DatabaseModule {}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,3 +0,0 @@
 | 
				
			||||||
import { PrismaRepository } from '../adapters/secondaries/prisma-repository.abstract';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class UserRepository<T> extends PrismaRepository<T> {}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,24 +0,0 @@
 | 
				
			||||||
export class DatabaseException implements Error {
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
  message: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    private _type: string = 'unknown',
 | 
					 | 
				
			||||||
    private _code: string = '',
 | 
					 | 
				
			||||||
    message?: string,
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    this.name = 'DatabaseException';
 | 
					 | 
				
			||||||
    this.message = message ?? 'An error occured with the database.';
 | 
					 | 
				
			||||||
    if (this.message.includes('Unique constraint failed')) {
 | 
					 | 
				
			||||||
      this.message = 'Already exists.';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  get type(): string {
 | 
					 | 
				
			||||||
    return this._type;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  get code(): string {
 | 
					 | 
				
			||||||
    return this._code;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,4 +0,0 @@
 | 
				
			||||||
export interface ICollection<T> {
 | 
					 | 
				
			||||||
  data: T[];
 | 
					 | 
				
			||||||
  total: number;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,18 +0,0 @@
 | 
				
			||||||
import { ICollection } from './collection.interface';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface IRepository<T> {
 | 
					 | 
				
			||||||
  findAll(
 | 
					 | 
				
			||||||
    page: number,
 | 
					 | 
				
			||||||
    perPage: number,
 | 
					 | 
				
			||||||
    params?: any,
 | 
					 | 
				
			||||||
    include?: any,
 | 
					 | 
				
			||||||
  ): Promise<ICollection<T>>;
 | 
					 | 
				
			||||||
  findOne(where: any, include?: any): Promise<T>;
 | 
					 | 
				
			||||||
  findOneByUuid(uuid: string, include?: any): Promise<T>;
 | 
					 | 
				
			||||||
  create(entity: Partial<T> | any, include?: any): Promise<T>;
 | 
					 | 
				
			||||||
  update(uuid: string, entity: Partial<T>, include?: any): Promise<T>;
 | 
					 | 
				
			||||||
  updateWhere(where: any, entity: Partial<T> | any, include?: any): Promise<T>;
 | 
					 | 
				
			||||||
  delete(uuid: string): Promise<T>;
 | 
					 | 
				
			||||||
  deleteMany(where: any): Promise<void>;
 | 
					 | 
				
			||||||
  healthCheck(): Promise<boolean>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,571 +0,0 @@
 | 
				
			||||||
import { Injectable } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					 | 
				
			||||||
import { PrismaService } from '../../adapters/secondaries/prisma-service';
 | 
					 | 
				
			||||||
import { PrismaRepository } from '../../adapters/secondaries/prisma-repository.abstract';
 | 
					 | 
				
			||||||
import { DatabaseException } from '../../exceptions/database.exception';
 | 
					 | 
				
			||||||
import { Prisma } from '@prisma/client';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class FakeEntity {
 | 
					 | 
				
			||||||
  uuid?: string;
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let entityId = 2;
 | 
					 | 
				
			||||||
const entityUuid = 'uuid-';
 | 
					 | 
				
			||||||
const entityName = 'name-';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const createRandomEntity = (): FakeEntity => {
 | 
					 | 
				
			||||||
  const entity: FakeEntity = {
 | 
					 | 
				
			||||||
    uuid: `${entityUuid}${entityId}`,
 | 
					 | 
				
			||||||
    name: `${entityName}${entityId}`,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  entityId++;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return entity;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const fakeEntityToCreate: FakeEntity = {
 | 
					 | 
				
			||||||
  name: 'test',
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const fakeEntityCreated: FakeEntity = {
 | 
					 | 
				
			||||||
  ...fakeEntityToCreate,
 | 
					 | 
				
			||||||
  uuid: 'some-uuid',
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const fakeEntities: FakeEntity[] = [];
 | 
					 | 
				
			||||||
Array.from({ length: 10 }).forEach(() => {
 | 
					 | 
				
			||||||
  fakeEntities.push(createRandomEntity());
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Injectable()
 | 
					 | 
				
			||||||
class FakePrismaRepository extends PrismaRepository<FakeEntity> {
 | 
					 | 
				
			||||||
  protected model = 'fake';
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class FakePrismaService extends PrismaService {
 | 
					 | 
				
			||||||
  fake: any;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockPrismaService = {
 | 
					 | 
				
			||||||
  $transaction: jest.fn().mockImplementation(async (data: any) => {
 | 
					 | 
				
			||||||
    const entities = await data[0];
 | 
					 | 
				
			||||||
    if (entities.length == 1) {
 | 
					 | 
				
			||||||
      return Promise.resolve([[fakeEntityCreated], 1]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return Promise.resolve([fakeEntities, fakeEntities.length]);
 | 
					 | 
				
			||||||
  }),
 | 
					 | 
				
			||||||
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
  $queryRawUnsafe: jest.fn().mockImplementation((query?: string) => {
 | 
					 | 
				
			||||||
    return Promise.resolve(fakeEntities);
 | 
					 | 
				
			||||||
  }),
 | 
					 | 
				
			||||||
  $executeRawUnsafe: jest
 | 
					 | 
				
			||||||
    .fn()
 | 
					 | 
				
			||||||
    .mockResolvedValueOnce(fakeEntityCreated)
 | 
					 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
    .mockImplementationOnce((fields: object) => {
 | 
					 | 
				
			||||||
      throw new Prisma.PrismaClientKnownRequestError('unknown request', {
 | 
					 | 
				
			||||||
        code: 'code',
 | 
					 | 
				
			||||||
        clientVersion: 'version',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
    .mockImplementationOnce((fields: object) => {
 | 
					 | 
				
			||||||
      throw new Error('an unknown error');
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .mockResolvedValueOnce(fakeEntityCreated)
 | 
					 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
    .mockImplementationOnce((fields: object) => {
 | 
					 | 
				
			||||||
      throw new Prisma.PrismaClientKnownRequestError('unknown request', {
 | 
					 | 
				
			||||||
        code: 'code',
 | 
					 | 
				
			||||||
        clientVersion: 'version',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
    .mockImplementationOnce((fields: object) => {
 | 
					 | 
				
			||||||
      throw new Error('an unknown error');
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
  $queryRaw: jest
 | 
					 | 
				
			||||||
    .fn()
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => {
 | 
					 | 
				
			||||||
      throw new Prisma.PrismaClientKnownRequestError('unknown request', {
 | 
					 | 
				
			||||||
        code: 'code',
 | 
					 | 
				
			||||||
        clientVersion: 'version',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => {
 | 
					 | 
				
			||||||
      return true;
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .mockImplementation(() => {
 | 
					 | 
				
			||||||
      throw new Prisma.PrismaClientKnownRequestError('Database unavailable', {
 | 
					 | 
				
			||||||
        code: 'code',
 | 
					 | 
				
			||||||
        clientVersion: 'version',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
  fake: {
 | 
					 | 
				
			||||||
    create: jest
 | 
					 | 
				
			||||||
      .fn()
 | 
					 | 
				
			||||||
      .mockResolvedValueOnce(fakeEntityCreated)
 | 
					 | 
				
			||||||
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
      .mockImplementationOnce((params?: any) => {
 | 
					 | 
				
			||||||
        throw new Prisma.PrismaClientKnownRequestError('unknown request', {
 | 
					 | 
				
			||||||
          code: 'code',
 | 
					 | 
				
			||||||
          clientVersion: 'version',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
      .mockImplementationOnce((params?: any) => {
 | 
					 | 
				
			||||||
        throw new Error('an unknown error');
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    findMany: jest.fn().mockImplementation((params?: any) => {
 | 
					 | 
				
			||||||
      if (params?.where?.limit == 1) {
 | 
					 | 
				
			||||||
        return Promise.resolve([fakeEntityCreated]);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return Promise.resolve(fakeEntities);
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
    count: jest.fn().mockResolvedValue(fakeEntities.length),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    findUnique: jest.fn().mockImplementation(async (params?: any) => {
 | 
					 | 
				
			||||||
      let entity;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (params?.where?.uuid) {
 | 
					 | 
				
			||||||
        entity = fakeEntities.find(
 | 
					 | 
				
			||||||
          (entity) => entity.uuid === params?.where?.uuid,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (!entity && params?.where?.uuid == 'unknown') {
 | 
					 | 
				
			||||||
        throw new Prisma.PrismaClientKnownRequestError('unknown request', {
 | 
					 | 
				
			||||||
          code: 'code',
 | 
					 | 
				
			||||||
          clientVersion: 'version',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      } else if (!entity) {
 | 
					 | 
				
			||||||
        throw new Error('no entity');
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return entity;
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    findFirst: jest
 | 
					 | 
				
			||||||
      .fn()
 | 
					 | 
				
			||||||
      .mockImplementationOnce((params?: any) => {
 | 
					 | 
				
			||||||
        if (params?.where?.name) {
 | 
					 | 
				
			||||||
          return Promise.resolve(
 | 
					 | 
				
			||||||
            fakeEntities.find((entity) => entity.name === params?.where?.name),
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
      .mockImplementationOnce((params?: any) => {
 | 
					 | 
				
			||||||
        throw new Prisma.PrismaClientKnownRequestError('unknown request', {
 | 
					 | 
				
			||||||
          code: 'code',
 | 
					 | 
				
			||||||
          clientVersion: 'version',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
      .mockImplementationOnce((params?: any) => {
 | 
					 | 
				
			||||||
        throw new Error('an unknown error');
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    update: jest
 | 
					 | 
				
			||||||
      .fn()
 | 
					 | 
				
			||||||
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
      .mockImplementationOnce((params?: any) => {
 | 
					 | 
				
			||||||
        throw new Prisma.PrismaClientKnownRequestError('unknown request', {
 | 
					 | 
				
			||||||
          code: 'code',
 | 
					 | 
				
			||||||
          clientVersion: 'version',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
      .mockImplementationOnce((params?: any) => {
 | 
					 | 
				
			||||||
        throw new Prisma.PrismaClientKnownRequestError('unknown request', {
 | 
					 | 
				
			||||||
          code: 'code',
 | 
					 | 
				
			||||||
          clientVersion: 'version',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .mockImplementationOnce((params: any) => {
 | 
					 | 
				
			||||||
        const entity = fakeEntities.find(
 | 
					 | 
				
			||||||
          (entity) => entity.name === params.where.name,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        Object.entries(params.data).map(([key, value]) => {
 | 
					 | 
				
			||||||
          entity[key] = value;
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return Promise.resolve(entity);
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .mockImplementation((params: any) => {
 | 
					 | 
				
			||||||
        const entity = fakeEntities.find(
 | 
					 | 
				
			||||||
          (entity) => entity.uuid === params.where.uuid,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        Object.entries(params.data).map(([key, value]) => {
 | 
					 | 
				
			||||||
          entity[key] = value;
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return Promise.resolve(entity);
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    delete: jest
 | 
					 | 
				
			||||||
      .fn()
 | 
					 | 
				
			||||||
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
      .mockImplementationOnce((params?: any) => {
 | 
					 | 
				
			||||||
        throw new Prisma.PrismaClientKnownRequestError('unknown request', {
 | 
					 | 
				
			||||||
          code: 'code',
 | 
					 | 
				
			||||||
          clientVersion: 'version',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .mockImplementation((params: any) => {
 | 
					 | 
				
			||||||
        let found = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fakeEntities.forEach((entity, index) => {
 | 
					 | 
				
			||||||
          if (entity.uuid === params?.where?.uuid) {
 | 
					 | 
				
			||||||
            found = true;
 | 
					 | 
				
			||||||
            fakeEntities.splice(index, 1);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!found) {
 | 
					 | 
				
			||||||
          throw new Error();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    deleteMany: jest
 | 
					 | 
				
			||||||
      .fn()
 | 
					 | 
				
			||||||
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
      .mockImplementationOnce((params?: any) => {
 | 
					 | 
				
			||||||
        throw new Prisma.PrismaClientKnownRequestError('unknown request', {
 | 
					 | 
				
			||||||
          code: 'code',
 | 
					 | 
				
			||||||
          clientVersion: 'version',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .mockImplementation((params: any) => {
 | 
					 | 
				
			||||||
        let found = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fakeEntities.forEach((entity, index) => {
 | 
					 | 
				
			||||||
          if (entity.uuid === params?.where?.uuid) {
 | 
					 | 
				
			||||||
            found = true;
 | 
					 | 
				
			||||||
            fakeEntities.splice(index, 1);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!found) {
 | 
					 | 
				
			||||||
          throw new Error();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('PrismaRepository', () => {
 | 
					 | 
				
			||||||
  let fakeRepository: FakePrismaRepository;
 | 
					 | 
				
			||||||
  let prisma: FakePrismaService;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        FakePrismaRepository,
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: PrismaService,
 | 
					 | 
				
			||||||
          useValue: mockPrismaService,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fakeRepository = module.get<FakePrismaRepository>(FakePrismaRepository);
 | 
					 | 
				
			||||||
    prisma = module.get<PrismaService>(PrismaService) as FakePrismaService;
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should be defined', () => {
 | 
					 | 
				
			||||||
    expect(fakeRepository).toBeDefined();
 | 
					 | 
				
			||||||
    expect(prisma).toBeDefined();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('findAll', () => {
 | 
					 | 
				
			||||||
    it('should return an array of entities', async () => {
 | 
					 | 
				
			||||||
      jest.spyOn(prisma.fake, 'findMany');
 | 
					 | 
				
			||||||
      jest.spyOn(prisma.fake, 'count');
 | 
					 | 
				
			||||||
      jest.spyOn(prisma, '$transaction');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const entities = await fakeRepository.findAll();
 | 
					 | 
				
			||||||
      expect(entities).toStrictEqual({
 | 
					 | 
				
			||||||
        data: fakeEntities,
 | 
					 | 
				
			||||||
        total: fakeEntities.length,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should return an array containing only one entity', async () => {
 | 
					 | 
				
			||||||
      const entities = await fakeRepository.findAll(1, 10, { limit: 1 });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      expect(prisma.fake.findMany).toHaveBeenCalledWith({
 | 
					 | 
				
			||||||
        skip: 0,
 | 
					 | 
				
			||||||
        take: 10,
 | 
					 | 
				
			||||||
        where: { limit: 1 },
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      expect(entities).toEqual({
 | 
					 | 
				
			||||||
        data: [fakeEntityCreated],
 | 
					 | 
				
			||||||
        total: 1,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('create', () => {
 | 
					 | 
				
			||||||
    it('should create an entity', async () => {
 | 
					 | 
				
			||||||
      jest.spyOn(prisma.fake, 'create');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const newEntity = await fakeRepository.create(fakeEntityToCreate);
 | 
					 | 
				
			||||||
      expect(newEntity).toBe(fakeEntityCreated);
 | 
					 | 
				
			||||||
      expect(prisma.fake.create).toHaveBeenCalledTimes(1);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should throw a DatabaseException for client error', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.create(fakeEntityToCreate),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should throw a DatabaseException if uuid is not found', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.create(fakeEntityToCreate),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('findOneByUuid', () => {
 | 
					 | 
				
			||||||
    it('should find an entity by uuid', async () => {
 | 
					 | 
				
			||||||
      const entity = await fakeRepository.findOneByUuid(fakeEntities[0].uuid);
 | 
					 | 
				
			||||||
      expect(entity).toBe(fakeEntities[0]);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should throw a DatabaseException for client error', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.findOneByUuid('unknown'),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should throw a DatabaseException if uuid is not found', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.findOneByUuid('wrong-uuid'),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('findOne', () => {
 | 
					 | 
				
			||||||
    it('should find one entity', async () => {
 | 
					 | 
				
			||||||
      const entity = await fakeRepository.findOne({
 | 
					 | 
				
			||||||
        name: fakeEntities[0].name,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      expect(entity.name).toBe(fakeEntities[0].name);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should throw a DatabaseException for client error', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.findOne({
 | 
					 | 
				
			||||||
          name: fakeEntities[0].name,
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should throw a DatabaseException for unknown error', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.findOne({
 | 
					 | 
				
			||||||
          name: fakeEntities[0].name,
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('update', () => {
 | 
					 | 
				
			||||||
    it('should throw a DatabaseException for client error', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.update('fake-uuid', { name: 'error' }),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.updateWhere({ name: 'error' }, { name: 'new error' }),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should update an entity with name', async () => {
 | 
					 | 
				
			||||||
      const newName = 'new-random-name';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await fakeRepository.updateWhere(
 | 
					 | 
				
			||||||
        { name: fakeEntities[0].name },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          name: newName,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      expect(fakeEntities[0].name).toBe(newName);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should update an entity with uuid', async () => {
 | 
					 | 
				
			||||||
      const newName = 'random-name';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await fakeRepository.update(fakeEntities[0].uuid, {
 | 
					 | 
				
			||||||
        name: newName,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      expect(fakeEntities[0].name).toBe(newName);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it("should throw an exception if an entity doesn't exist", async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.update('fake-uuid', { name: 'error' }),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.updateWhere({ name: 'error' }, { name: 'new error' }),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('delete', () => {
 | 
					 | 
				
			||||||
    it('should throw a DatabaseException for client error', async () => {
 | 
					 | 
				
			||||||
      await expect(fakeRepository.delete('fake-uuid')).rejects.toBeInstanceOf(
 | 
					 | 
				
			||||||
        DatabaseException,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should delete an entity', async () => {
 | 
					 | 
				
			||||||
      const savedUuid = fakeEntities[0].uuid;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
      const res = await fakeRepository.delete(savedUuid);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const deletedEntity = fakeEntities.find(
 | 
					 | 
				
			||||||
        (entity) => entity.uuid === savedUuid,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      expect(deletedEntity).toBeUndefined();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it("should throw an exception if an entity doesn't exist", async () => {
 | 
					 | 
				
			||||||
      await expect(fakeRepository.delete('fake-uuid')).rejects.toBeInstanceOf(
 | 
					 | 
				
			||||||
        DatabaseException,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('deleteMany', () => {
 | 
					 | 
				
			||||||
    it('should throw a DatabaseException for client error', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.deleteMany({ uuid: 'fake-uuid' }),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should delete entities based on their uuid', async () => {
 | 
					 | 
				
			||||||
      const savedUuid = fakeEntities[0].uuid;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
      const res = await fakeRepository.deleteMany({ uuid: savedUuid });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const deletedEntity = fakeEntities.find(
 | 
					 | 
				
			||||||
        (entity) => entity.uuid === savedUuid,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      expect(deletedEntity).toBeUndefined();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it("should throw an exception if an entity doesn't exist", async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.deleteMany({ uuid: 'fake-uuid' }),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('findAllByquery', () => {
 | 
					 | 
				
			||||||
    it('should return an array of entities', async () => {
 | 
					 | 
				
			||||||
      const entities = await fakeRepository.findAllByQuery(
 | 
					 | 
				
			||||||
        ['uuid', 'name'],
 | 
					 | 
				
			||||||
        ['name is not null'],
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      expect(entities).toStrictEqual({
 | 
					 | 
				
			||||||
        data: fakeEntities,
 | 
					 | 
				
			||||||
        total: fakeEntities.length,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('createWithFields', () => {
 | 
					 | 
				
			||||||
    it('should create an entity', async () => {
 | 
					 | 
				
			||||||
      jest.spyOn(prisma, '$queryRawUnsafe');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const newEntity = await fakeRepository.createWithFields({
 | 
					 | 
				
			||||||
        uuid: '804319b3-a09b-4491-9f82-7976bfce0aff',
 | 
					 | 
				
			||||||
        name: 'my-name',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      expect(newEntity).toBe(fakeEntityCreated);
 | 
					 | 
				
			||||||
      expect(prisma.$queryRawUnsafe).toHaveBeenCalledTimes(1);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should throw a DatabaseException for client error', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.createWithFields({
 | 
					 | 
				
			||||||
          uuid: '804319b3-a09b-4491-9f82-7976bfce0aff',
 | 
					 | 
				
			||||||
          name: 'my-name',
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should throw a DatabaseException if uuid is not found', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.createWithFields({
 | 
					 | 
				
			||||||
          name: 'my-name',
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('updateWithFields', () => {
 | 
					 | 
				
			||||||
    it('should update an entity', async () => {
 | 
					 | 
				
			||||||
      jest.spyOn(prisma, '$queryRawUnsafe');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const updatedEntity = await fakeRepository.updateWithFields(
 | 
					 | 
				
			||||||
        '804319b3-a09b-4491-9f82-7976bfce0aff',
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          name: 'my-name',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      expect(updatedEntity).toBe(fakeEntityCreated);
 | 
					 | 
				
			||||||
      expect(prisma.$queryRawUnsafe).toHaveBeenCalledTimes(1);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should throw a DatabaseException for client error', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.updateWithFields(
 | 
					 | 
				
			||||||
          '804319b3-a09b-4491-9f82-7976bfce0aff',
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            name: 'my-name',
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should throw a DatabaseException if uuid is not found', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        fakeRepository.updateWithFields(
 | 
					 | 
				
			||||||
          '804319b3-a09b-4491-9f82-7976bfce0aff',
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            name: 'my-name',
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('healthCheck', () => {
 | 
					 | 
				
			||||||
    it('should throw a DatabaseException for client error', async () => {
 | 
					 | 
				
			||||||
      await expect(fakeRepository.healthCheck()).rejects.toBeInstanceOf(
 | 
					 | 
				
			||||||
        DatabaseException,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should return a healthy result', async () => {
 | 
					 | 
				
			||||||
      const res = await fakeRepository.healthCheck();
 | 
					 | 
				
			||||||
      expect(res).toBeTruthy();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should throw an exception if database is not available', async () => {
 | 
					 | 
				
			||||||
      await expect(fakeRepository.healthCheck()).rejects.toBeInstanceOf(
 | 
					 | 
				
			||||||
        DatabaseException,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,42 +0,0 @@
 | 
				
			||||||
import { Controller } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { GrpcMethod } from '@nestjs/microservices';
 | 
					 | 
				
			||||||
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum ServingStatus {
 | 
					 | 
				
			||||||
  UNKNOWN = 0,
 | 
					 | 
				
			||||||
  SERVING = 1,
 | 
					 | 
				
			||||||
  NOT_SERVING = 2,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface HealthCheckRequest {
 | 
					 | 
				
			||||||
  service: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface HealthCheckResponse {
 | 
					 | 
				
			||||||
  status: ServingStatus;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Controller()
 | 
					 | 
				
			||||||
export class HealthServerController {
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    private readonly prismaHealthIndicatorUseCase: PrismaHealthIndicatorUseCase,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @GrpcMethod('Health', 'Check')
 | 
					 | 
				
			||||||
  async check(
 | 
					 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
    data: HealthCheckRequest,
 | 
					 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
    metadata: any,
 | 
					 | 
				
			||||||
  ): Promise<HealthCheckResponse> {
 | 
					 | 
				
			||||||
    const healthCheck = await this.prismaHealthIndicatorUseCase.isHealthy(
 | 
					 | 
				
			||||||
      'prisma',
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      status:
 | 
					 | 
				
			||||||
        healthCheck['prisma'].status == 'up'
 | 
					 | 
				
			||||||
          ? ServingStatus.SERVING
 | 
					 | 
				
			||||||
          : ServingStatus.NOT_SERVING,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,36 +0,0 @@
 | 
				
			||||||
import { Controller, Get, Inject } from '@nestjs/common';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  HealthCheckService,
 | 
					 | 
				
			||||||
  HealthCheck,
 | 
					 | 
				
			||||||
  HealthCheckResult,
 | 
					 | 
				
			||||||
} from '@nestjs/terminus';
 | 
					 | 
				
			||||||
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
 | 
					 | 
				
			||||||
import { MESSAGE_PUBLISHER } from 'src/app.constants';
 | 
					 | 
				
			||||||
import { IPublishMessage } from 'src/interfaces/message-publisher';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Controller('health')
 | 
					 | 
				
			||||||
export class HealthController {
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    private readonly prismaHealthIndicatorUseCase: PrismaHealthIndicatorUseCase,
 | 
					 | 
				
			||||||
    private readonly healthCheckService: HealthCheckService,
 | 
					 | 
				
			||||||
    @Inject(MESSAGE_PUBLISHER)
 | 
					 | 
				
			||||||
    private readonly messagePublisher: IPublishMessage,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Get()
 | 
					 | 
				
			||||||
  @HealthCheck()
 | 
					 | 
				
			||||||
  async check() {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      return await this.healthCheckService.check([
 | 
					 | 
				
			||||||
        async () => this.prismaHealthIndicatorUseCase.isHealthy('prisma'),
 | 
					 | 
				
			||||||
      ]);
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      const healthCheckResult: HealthCheckResult = error.response;
 | 
					 | 
				
			||||||
      this.messagePublisher.publish(
 | 
					 | 
				
			||||||
        'logging.user.health.crit',
 | 
					 | 
				
			||||||
        JSON.stringify(healthCheckResult.error),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      throw error;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,16 +0,0 @@
 | 
				
			||||||
import { Inject, Injectable } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { MESSAGE_BROKER_PUBLISHER } from '../../../../app.constants';
 | 
					 | 
				
			||||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
					 | 
				
			||||||
import { IPublishMessage } from 'src/interfaces/message-publisher';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Injectable()
 | 
					 | 
				
			||||||
export class MessagePublisher implements IPublishMessage {
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    @Inject(MESSAGE_BROKER_PUBLISHER)
 | 
					 | 
				
			||||||
    private readonly messageBrokerPublisher: MessageBrokerPublisher,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  publish = (routingKey: string, message: string): void => {
 | 
					 | 
				
			||||||
    this.messageBrokerPublisher.publish(routingKey, message);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,25 +0,0 @@
 | 
				
			||||||
import { Injectable } from '@nestjs/common';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  HealthCheckError,
 | 
					 | 
				
			||||||
  HealthIndicator,
 | 
					 | 
				
			||||||
  HealthIndicatorResult,
 | 
					 | 
				
			||||||
} from '@nestjs/terminus';
 | 
					 | 
				
			||||||
import { UsersRepository } from '../../../user/adapters/secondaries/users.repository';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Injectable()
 | 
					 | 
				
			||||||
export class PrismaHealthIndicatorUseCase extends HealthIndicator {
 | 
					 | 
				
			||||||
  constructor(private readonly repository: UsersRepository) {
 | 
					 | 
				
			||||||
    super();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  isHealthy = async (key: string): Promise<HealthIndicatorResult> => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await this.repository.healthCheck();
 | 
					 | 
				
			||||||
      return this.getStatus(key, true);
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      throw new HealthCheckError('Prisma', {
 | 
					 | 
				
			||||||
        prisma: e.message,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,28 +0,0 @@
 | 
				
			||||||
import { Module } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { HealthServerController } from './adapters/primaries/health-server.controller';
 | 
					 | 
				
			||||||
import { PrismaHealthIndicatorUseCase } from './domain/usecases/prisma.health-indicator.usecase';
 | 
					 | 
				
			||||||
import { UsersRepository } from '../user/adapters/secondaries/users.repository';
 | 
					 | 
				
			||||||
import { DatabaseModule } from '../database/database.module';
 | 
					 | 
				
			||||||
import { HealthController } from './adapters/primaries/health.controller';
 | 
					 | 
				
			||||||
import { TerminusModule } from '@nestjs/terminus';
 | 
					 | 
				
			||||||
import { MESSAGE_BROKER_PUBLISHER, MESSAGE_PUBLISHER } from 'src/app.constants';
 | 
					 | 
				
			||||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
					 | 
				
			||||||
import { MessagePublisher } from './adapters/secondaries/message-publisher';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Module({
 | 
					 | 
				
			||||||
  imports: [TerminusModule, DatabaseModule],
 | 
					 | 
				
			||||||
  controllers: [HealthServerController, HealthController],
 | 
					 | 
				
			||||||
  providers: [
 | 
					 | 
				
			||||||
    PrismaHealthIndicatorUseCase,
 | 
					 | 
				
			||||||
    UsersRepository,
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      provide: MESSAGE_BROKER_PUBLISHER,
 | 
					 | 
				
			||||||
      useClass: MessageBrokerPublisher,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      provide: MESSAGE_PUBLISHER,
 | 
					 | 
				
			||||||
      useClass: MessagePublisher,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
export class HealthModule {}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,36 +0,0 @@
 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					 | 
				
			||||||
import { MessagePublisher } from '../../adapters/secondaries/message-publisher';
 | 
					 | 
				
			||||||
import { MESSAGE_BROKER_PUBLISHER } from '../../../../app.constants';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockMessageBrokerPublisher = {
 | 
					 | 
				
			||||||
  publish: jest.fn().mockImplementation(),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('Messager', () => {
 | 
					 | 
				
			||||||
  let messagePublisher: MessagePublisher;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      imports: [],
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        MessagePublisher,
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: MESSAGE_BROKER_PUBLISHER,
 | 
					 | 
				
			||||||
          useValue: mockMessageBrokerPublisher,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    messagePublisher = module.get<MessagePublisher>(MessagePublisher);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should be defined', () => {
 | 
					 | 
				
			||||||
    expect(messagePublisher).toBeDefined();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should publish a message', async () => {
 | 
					 | 
				
			||||||
    jest.spyOn(mockMessageBrokerPublisher, 'publish');
 | 
					 | 
				
			||||||
    messagePublisher.publish('health.info', 'my-test');
 | 
					 | 
				
			||||||
    expect(mockMessageBrokerPublisher.publish).toHaveBeenCalledTimes(1);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,58 +0,0 @@
 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					 | 
				
			||||||
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
 | 
					 | 
				
			||||||
import { UsersRepository } from '../../../user/adapters/secondaries/users.repository';
 | 
					 | 
				
			||||||
import { HealthCheckError, HealthIndicatorResult } from '@nestjs/terminus';
 | 
					 | 
				
			||||||
import { Prisma } from '@prisma/client';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockUsersRepository = {
 | 
					 | 
				
			||||||
  healthCheck: jest
 | 
					 | 
				
			||||||
    .fn()
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => {
 | 
					 | 
				
			||||||
      return Promise.resolve(true);
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .mockImplementation(() => {
 | 
					 | 
				
			||||||
      throw new Prisma.PrismaClientKnownRequestError('Service unavailable', {
 | 
					 | 
				
			||||||
        code: 'code',
 | 
					 | 
				
			||||||
        clientVersion: 'version',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('PrismaHealthIndicatorUseCase', () => {
 | 
					 | 
				
			||||||
  let prismaHealthIndicatorUseCase: PrismaHealthIndicatorUseCase;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: UsersRepository,
 | 
					 | 
				
			||||||
          useValue: mockUsersRepository,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        PrismaHealthIndicatorUseCase,
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    prismaHealthIndicatorUseCase = module.get<PrismaHealthIndicatorUseCase>(
 | 
					 | 
				
			||||||
      PrismaHealthIndicatorUseCase,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should be defined', () => {
 | 
					 | 
				
			||||||
    expect(prismaHealthIndicatorUseCase).toBeDefined();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('execute', () => {
 | 
					 | 
				
			||||||
    it('should check health successfully', async () => {
 | 
					 | 
				
			||||||
      const healthIndicatorResult: HealthIndicatorResult =
 | 
					 | 
				
			||||||
        await prismaHealthIndicatorUseCase.isHealthy('prisma');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      expect(healthIndicatorResult['prisma'].status).toBe('up');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should throw an error if database is unavailable', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        prismaHealthIndicatorUseCase.isHealthy('prisma'),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(HealthCheckError);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					export const MESSAGE_PUBLISHER = Symbol('MESSAGE_PUBLISHER');
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					import { Module, Provider } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { MESSAGE_PUBLISHER } from './messager.di-tokens';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  MessageBrokerModule,
 | 
				
			||||||
 | 
					  MessageBrokerModuleOptions,
 | 
				
			||||||
 | 
					  MessageBrokerPublisher,
 | 
				
			||||||
 | 
					} from '@mobicoop/message-broker-module';
 | 
				
			||||||
 | 
					import { ConfigModule, ConfigService } from '@nestjs/config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const imports = [
 | 
				
			||||||
 | 
					  MessageBrokerModule.forRootAsync({
 | 
				
			||||||
 | 
					    imports: [ConfigModule],
 | 
				
			||||||
 | 
					    inject: [ConfigService],
 | 
				
			||||||
 | 
					    useFactory: async (
 | 
				
			||||||
 | 
					      configService: ConfigService,
 | 
				
			||||||
 | 
					    ): Promise<MessageBrokerModuleOptions> => ({
 | 
				
			||||||
 | 
					      uri: configService.get<string>('MESSAGE_BROKER_URI'),
 | 
				
			||||||
 | 
					      exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
 | 
				
			||||||
 | 
					      name: 'user',
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const providers: Provider[] = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    provide: MESSAGE_PUBLISHER,
 | 
				
			||||||
 | 
					    useClass: MessageBrokerPublisher,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Module({
 | 
				
			||||||
 | 
					  imports,
 | 
				
			||||||
 | 
					  providers,
 | 
				
			||||||
 | 
					  exports: [MESSAGE_PUBLISHER],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class MessagerModule {}
 | 
				
			||||||
| 
						 | 
					@ -1,121 +0,0 @@
 | 
				
			||||||
import { Mapper } from '@automapper/core';
 | 
					 | 
				
			||||||
import { InjectMapper } from '@automapper/nestjs';
 | 
					 | 
				
			||||||
import { Controller, UseInterceptors, UsePipes } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { CommandBus, QueryBus } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
 | 
					 | 
				
			||||||
import { DatabaseException } from '../../../database/exceptions/database.exception';
 | 
					 | 
				
			||||||
import { CreateUserCommand } from '../../commands/create-user.command';
 | 
					 | 
				
			||||||
import { DeleteUserCommand } from '../../commands/delete-user.command';
 | 
					 | 
				
			||||||
import { UpdateUserCommand } from '../../commands/update-user.command';
 | 
					 | 
				
			||||||
import { CreateUserRequest } from '../../domain/dtos/create-user.request';
 | 
					 | 
				
			||||||
import { FindAllUsersRequest } from '../../domain/dtos/find-all-users.request';
 | 
					 | 
				
			||||||
import { FindUserByUuidRequest } from '../../domain/dtos/find-user-by-uuid.request';
 | 
					 | 
				
			||||||
import { UpdateUserRequest } from '../../domain/dtos/update-user.request';
 | 
					 | 
				
			||||||
import { User } from '../../domain/entities/user';
 | 
					 | 
				
			||||||
import { FindAllUsersQuery } from '../../queries/find-all-users.query';
 | 
					 | 
				
			||||||
import { FindUserByUuidQuery } from '../../queries/find-user-by-uuid.query';
 | 
					 | 
				
			||||||
import { UserPresenter } from './user.presenter';
 | 
					 | 
				
			||||||
import { ICollection } from '../../../database/interfaces/collection.interface';
 | 
					 | 
				
			||||||
import { RpcValidationPipe } from '../../../../utils/pipes/rpc.validation-pipe';
 | 
					 | 
				
			||||||
import { CacheInterceptor, CacheKey } from '@nestjs/cache-manager';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@UsePipes(
 | 
					 | 
				
			||||||
  new RpcValidationPipe({
 | 
					 | 
				
			||||||
    whitelist: true,
 | 
					 | 
				
			||||||
    forbidUnknownValues: false,
 | 
					 | 
				
			||||||
  }),
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
@Controller()
 | 
					 | 
				
			||||||
export class UserController {
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    private readonly commandBus: CommandBus,
 | 
					 | 
				
			||||||
    private readonly queryBus: QueryBus,
 | 
					 | 
				
			||||||
    @InjectMapper() private readonly mapper: Mapper,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @GrpcMethod('UsersService', 'FindAll')
 | 
					 | 
				
			||||||
  @UseInterceptors(CacheInterceptor)
 | 
					 | 
				
			||||||
  @CacheKey('UsersServiceFindAll')
 | 
					 | 
				
			||||||
  async findAll(data: FindAllUsersRequest): Promise<ICollection<User>> {
 | 
					 | 
				
			||||||
    const userCollection = await this.queryBus.execute(
 | 
					 | 
				
			||||||
      new FindAllUsersQuery(data),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    return Promise.resolve({
 | 
					 | 
				
			||||||
      data: userCollection.data.map((user: User) =>
 | 
					 | 
				
			||||||
        this.mapper.map(user, User, UserPresenter),
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
      total: userCollection.total,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @GrpcMethod('UsersService', 'FindOneByUuid')
 | 
					 | 
				
			||||||
  @UseInterceptors(CacheInterceptor)
 | 
					 | 
				
			||||||
  @CacheKey('UsersServiceFindOneByUuid')
 | 
					 | 
				
			||||||
  async findOneByUuid(data: FindUserByUuidRequest): Promise<UserPresenter> {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const user = await this.queryBus.execute(new FindUserByUuidQuery(data));
 | 
					 | 
				
			||||||
      return this.mapper.map(user, User, UserPresenter);
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      throw new RpcException({
 | 
					 | 
				
			||||||
        code: 5,
 | 
					 | 
				
			||||||
        message: 'User not found',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @GrpcMethod('UsersService', 'Create')
 | 
					 | 
				
			||||||
  async createUser(data: CreateUserRequest): Promise<UserPresenter> {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const user = await this.commandBus.execute(new CreateUserCommand(data));
 | 
					 | 
				
			||||||
      return this.mapper.map(user, User, UserPresenter);
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      if (e instanceof DatabaseException) {
 | 
					 | 
				
			||||||
        if (e.message.includes('Already exists')) {
 | 
					 | 
				
			||||||
          throw new RpcException({
 | 
					 | 
				
			||||||
            code: 6,
 | 
					 | 
				
			||||||
            message: 'User already exists',
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      throw new RpcException({});
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @GrpcMethod('UsersService', 'Update')
 | 
					 | 
				
			||||||
  async updateUser(data: UpdateUserRequest): Promise<UserPresenter> {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const user = await this.commandBus.execute(new UpdateUserCommand(data));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return this.mapper.map(user, User, UserPresenter);
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      if (e instanceof DatabaseException) {
 | 
					 | 
				
			||||||
        if (e.message.includes('not found')) {
 | 
					 | 
				
			||||||
          throw new RpcException({
 | 
					 | 
				
			||||||
            code: 5,
 | 
					 | 
				
			||||||
            message: 'User not found',
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      throw new RpcException({});
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @GrpcMethod('UsersService', 'Delete')
 | 
					 | 
				
			||||||
  async deleteUser(data: FindUserByUuidRequest): Promise<void> {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await this.commandBus.execute(new DeleteUserCommand(data.uuid));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return Promise.resolve();
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      if (e instanceof DatabaseException) {
 | 
					 | 
				
			||||||
        if (e.message.includes('not found')) {
 | 
					 | 
				
			||||||
          throw new RpcException({
 | 
					 | 
				
			||||||
            code: 5,
 | 
					 | 
				
			||||||
            message: 'User not found',
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      throw new RpcException({});
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,18 +0,0 @@
 | 
				
			||||||
import { AutoMap } from '@automapper/classes';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class UserPresenter {
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  uuid: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  firstName: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  lastName: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  email: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  phone: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,16 +0,0 @@
 | 
				
			||||||
import { Inject, Injectable } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { IPublishMessage } from '../../../../interfaces/message-publisher';
 | 
					 | 
				
			||||||
import { MESSAGE_BROKER_PUBLISHER } from '../../../../app.constants';
 | 
					 | 
				
			||||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Injectable()
 | 
					 | 
				
			||||||
export class MessagePublisher implements IPublishMessage {
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    @Inject(MESSAGE_BROKER_PUBLISHER)
 | 
					 | 
				
			||||||
    private readonly messageBrokerPublisher: MessageBrokerPublisher,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  publish = (routingKey: string, message: string): void => {
 | 
					 | 
				
			||||||
    this.messageBrokerPublisher.publish(routingKey, message);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,8 +0,0 @@
 | 
				
			||||||
import { Injectable } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { UserRepository } from '../../../database/domain/user-repository';
 | 
					 | 
				
			||||||
import { User } from '../../domain/entities/user';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Injectable()
 | 
					 | 
				
			||||||
export class UsersRepository extends UserRepository<User> {
 | 
					 | 
				
			||||||
  protected model = 'user';
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,9 +0,0 @@
 | 
				
			||||||
import { CreateUserRequest } from '../domain/dtos/create-user.request';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class CreateUserCommand {
 | 
					 | 
				
			||||||
  readonly createUserRequest: CreateUserRequest;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(request: CreateUserRequest) {
 | 
					 | 
				
			||||||
    this.createUserRequest = request;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,7 +0,0 @@
 | 
				
			||||||
export class DeleteUserCommand {
 | 
					 | 
				
			||||||
  readonly uuid: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(uuid: string) {
 | 
					 | 
				
			||||||
    this.uuid = uuid;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,9 +0,0 @@
 | 
				
			||||||
import { UpdateUserRequest } from '../domain/dtos/update-user.request';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class UpdateUserCommand {
 | 
					 | 
				
			||||||
  readonly updateUserRequest: UpdateUserRequest;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(request: UpdateUserRequest) {
 | 
					 | 
				
			||||||
    this.updateUserRequest = request;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					import { Command, CommandProps } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class CreateUserCommand extends Command {
 | 
				
			||||||
 | 
					  readonly firstName?: string;
 | 
				
			||||||
 | 
					  readonly lastName?: string;
 | 
				
			||||||
 | 
					  readonly email?: string;
 | 
				
			||||||
 | 
					  readonly phone?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: CommandProps<CreateUserCommand>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.firstName = props.firstName;
 | 
				
			||||||
 | 
					    this.lastName = props.lastName;
 | 
				
			||||||
 | 
					    this.email = props.email;
 | 
				
			||||||
 | 
					    this.phone = props.phone;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,55 @@
 | 
				
			||||||
 | 
					import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { Inject } from '@nestjs/common';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  AggregateID,
 | 
				
			||||||
 | 
					  ConflictException,
 | 
				
			||||||
 | 
					  UniqueConstraintException,
 | 
				
			||||||
 | 
					} from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { CreateUserCommand } from './create-user.command';
 | 
				
			||||||
 | 
					import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { UserRepositoryPort } from '../../ports/user.repository.port';
 | 
				
			||||||
 | 
					import { UserEntity } from '../../../domain/user.entity';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  EmailAlreadyExistsException,
 | 
				
			||||||
 | 
					  PhoneAlreadyExistsException,
 | 
				
			||||||
 | 
					  UserAlreadyExistsException,
 | 
				
			||||||
 | 
					} from '../../../domain/user.errors';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@CommandHandler(CreateUserCommand)
 | 
				
			||||||
 | 
					export class CreateUserService implements ICommandHandler {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(USER_REPOSITORY)
 | 
				
			||||||
 | 
					    private readonly repository: UserRepositoryPort,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async execute(command: CreateUserCommand): Promise<AggregateID> {
 | 
				
			||||||
 | 
					    const user = UserEntity.create({
 | 
				
			||||||
 | 
					      firstName: command.firstName,
 | 
				
			||||||
 | 
					      lastName: command.lastName,
 | 
				
			||||||
 | 
					      email: command.email,
 | 
				
			||||||
 | 
					      phone: command.phone,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await this.repository.insert(user);
 | 
				
			||||||
 | 
					      return user.id;
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					      if (error instanceof ConflictException) {
 | 
				
			||||||
 | 
					        throw new UserAlreadyExistsException(error);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        error instanceof UniqueConstraintException &&
 | 
				
			||||||
 | 
					        error.message.includes('email')
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        throw new EmailAlreadyExistsException(error);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        error instanceof UniqueConstraintException &&
 | 
				
			||||||
 | 
					        error.message.includes('phone')
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        throw new PhoneAlreadyExistsException(error);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      throw error;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					import { Command, CommandProps } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class DeleteUserCommand extends Command {
 | 
				
			||||||
 | 
					  constructor(props: CommandProps<DeleteUserCommand>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					import { Inject } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { DeleteUserCommand } from './delete-user.command';
 | 
				
			||||||
 | 
					import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { UserRepositoryPort } from '../../ports/user.repository.port';
 | 
				
			||||||
 | 
					import { UserEntity } from '@modules/user/core/domain/user.entity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@CommandHandler(DeleteUserCommand)
 | 
				
			||||||
 | 
					export class DeleteUserService implements ICommandHandler {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(USER_REPOSITORY)
 | 
				
			||||||
 | 
					    private readonly userRepository: UserRepositoryPort,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async execute(command: DeleteUserCommand): Promise<boolean> {
 | 
				
			||||||
 | 
					    const user: UserEntity = await this.userRepository.findOneById(command.id);
 | 
				
			||||||
 | 
					    user.delete();
 | 
				
			||||||
 | 
					    const isDeleted: boolean = await this.userRepository.delete(user);
 | 
				
			||||||
 | 
					    return isDeleted;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					import { Command, CommandProps } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UpdateUserCommand extends Command {
 | 
				
			||||||
 | 
					  readonly firstName?: string;
 | 
				
			||||||
 | 
					  readonly lastName?: string;
 | 
				
			||||||
 | 
					  readonly email?: string;
 | 
				
			||||||
 | 
					  readonly phone?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: CommandProps<UpdateUserCommand>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.firstName = props.firstName;
 | 
				
			||||||
 | 
					    this.lastName = props.lastName;
 | 
				
			||||||
 | 
					    this.email = props.email;
 | 
				
			||||||
 | 
					    this.phone = props.phone;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,49 @@
 | 
				
			||||||
 | 
					import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { Inject } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { AggregateID, UniqueConstraintException } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { UserRepositoryPort } from '../../ports/user.repository.port';
 | 
				
			||||||
 | 
					import { UserEntity } from '../../../domain/user.entity';
 | 
				
			||||||
 | 
					import { UpdateUserCommand } from './update-user.command';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  EmailAlreadyExistsException,
 | 
				
			||||||
 | 
					  PhoneAlreadyExistsException,
 | 
				
			||||||
 | 
					} from '@modules/user/core/domain/user.errors';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@CommandHandler(UpdateUserCommand)
 | 
				
			||||||
 | 
					export class UpdateUserService implements ICommandHandler {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(USER_REPOSITORY)
 | 
				
			||||||
 | 
					    private readonly userRepository: UserRepositoryPort,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async execute(command: UpdateUserCommand): Promise<AggregateID> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const user: UserEntity = await this.userRepository.findOneById(
 | 
				
			||||||
 | 
					        command.id,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      user.update({
 | 
				
			||||||
 | 
					        firstName: command.firstName,
 | 
				
			||||||
 | 
					        lastName: command.lastName,
 | 
				
			||||||
 | 
					        email: command.email,
 | 
				
			||||||
 | 
					        phone: command.phone,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      await this.userRepository.update(user.id, user);
 | 
				
			||||||
 | 
					      return user.id;
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        error instanceof UniqueConstraintException &&
 | 
				
			||||||
 | 
					        error.message.includes('email')
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        throw new EmailAlreadyExistsException(error);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        error instanceof UniqueConstraintException &&
 | 
				
			||||||
 | 
					        error.message.includes('phone')
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        throw new PhoneAlreadyExistsException(error);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      throw error;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { OnEvent } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import { MessagePublisherPort } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { UserCreatedDomainEvent } from '../../domain/events/user-created.domain-event';
 | 
				
			||||||
 | 
					import { UserCreatedIntegrationEvent } from '../events/user-created.integration-event';
 | 
				
			||||||
 | 
					import { USER_CREATED_ROUTING_KEY } from '@modules/user/user.constants';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class PublishMessageWhenUserIsCreatedDomainEventHandler {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(USER_MESSAGE_PUBLISHER)
 | 
				
			||||||
 | 
					    private readonly messagePublisher: MessagePublisherPort,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @OnEvent(UserCreatedDomainEvent.name, { async: true, promisify: true })
 | 
				
			||||||
 | 
					  async handle(event: UserCreatedDomainEvent): Promise<any> {
 | 
				
			||||||
 | 
					    const userCreatedIntegrationEvent = new UserCreatedIntegrationEvent({
 | 
				
			||||||
 | 
					      id: event.aggregateId,
 | 
				
			||||||
 | 
					      firstName: event.firstName,
 | 
				
			||||||
 | 
					      lastName: event.lastName,
 | 
				
			||||||
 | 
					      email: event.email,
 | 
				
			||||||
 | 
					      phone: event.phone,
 | 
				
			||||||
 | 
					      metadata: event.metadata,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    this.messagePublisher.publish(
 | 
				
			||||||
 | 
					      USER_CREATED_ROUTING_KEY,
 | 
				
			||||||
 | 
					      JSON.stringify(userCreatedIntegrationEvent),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,27 @@
 | 
				
			||||||
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { OnEvent } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import { MessagePublisherPort } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { UserDeletedDomainEvent } from '../../domain/events/user-deleted.domain-event';
 | 
				
			||||||
 | 
					import { UserDeletedIntegrationEvent } from '../events/user-deleted.integration-event';
 | 
				
			||||||
 | 
					import { USER_DELETED_ROUTING_KEY } from '@modules/user/user.constants';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class PublishMessageWhenUserIsDeletedDomainEventHandler {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(USER_MESSAGE_PUBLISHER)
 | 
				
			||||||
 | 
					    private readonly messagePublisher: MessagePublisherPort,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @OnEvent(UserDeletedDomainEvent.name, { async: true, promisify: true })
 | 
				
			||||||
 | 
					  async handle(event: UserDeletedDomainEvent): Promise<any> {
 | 
				
			||||||
 | 
					    const userDeletedIntegrationEvent = new UserDeletedIntegrationEvent({
 | 
				
			||||||
 | 
					      id: event.aggregateId,
 | 
				
			||||||
 | 
					      metadata: event.metadata,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    this.messagePublisher.publish(
 | 
				
			||||||
 | 
					      USER_DELETED_ROUTING_KEY,
 | 
				
			||||||
 | 
					      JSON.stringify(userDeletedIntegrationEvent),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { OnEvent } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import { MessagePublisherPort } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { UserUpdatedDomainEvent } from '../../domain/events/user-updated.domain-event';
 | 
				
			||||||
 | 
					import { UserUpdatedIntegrationEvent } from '../events/user-updated.integration-event';
 | 
				
			||||||
 | 
					import { USER_UPDATED_ROUTING_KEY } from '@modules/user/user.constants';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class PublishMessageWhenUserIsUpdatedDomainEventHandler {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(USER_MESSAGE_PUBLISHER)
 | 
				
			||||||
 | 
					    private readonly messagePublisher: MessagePublisherPort,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @OnEvent(UserUpdatedDomainEvent.name, { async: true, promisify: true })
 | 
				
			||||||
 | 
					  async handle(event: UserUpdatedDomainEvent): Promise<any> {
 | 
				
			||||||
 | 
					    const userUpdatedIntegrationEvent = new UserUpdatedIntegrationEvent({
 | 
				
			||||||
 | 
					      id: event.aggregateId,
 | 
				
			||||||
 | 
					      firstName: event.firstName,
 | 
				
			||||||
 | 
					      lastName: event.lastName,
 | 
				
			||||||
 | 
					      email: event.email,
 | 
				
			||||||
 | 
					      phone: event.phone,
 | 
				
			||||||
 | 
					      metadata: event.metadata,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    this.messagePublisher.publish(
 | 
				
			||||||
 | 
					      USER_UPDATED_ROUTING_KEY,
 | 
				
			||||||
 | 
					      JSON.stringify(userUpdatedIntegrationEvent),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					import { IntegrationEvent, IntegrationEventProps } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UserCreatedIntegrationEvent extends IntegrationEvent {
 | 
				
			||||||
 | 
					  readonly firstName: string;
 | 
				
			||||||
 | 
					  readonly lastName: string;
 | 
				
			||||||
 | 
					  readonly email: string;
 | 
				
			||||||
 | 
					  readonly phone: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: IntegrationEventProps<UserCreatedIntegrationEvent>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.firstName = props.firstName;
 | 
				
			||||||
 | 
					    this.lastName = props.lastName;
 | 
				
			||||||
 | 
					    this.email = props.email;
 | 
				
			||||||
 | 
					    this.phone = props.phone;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					import { IntegrationEvent, IntegrationEventProps } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UserDeletedIntegrationEvent extends IntegrationEvent {
 | 
				
			||||||
 | 
					  constructor(props: IntegrationEventProps<UserDeletedIntegrationEvent>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					import { IntegrationEvent, IntegrationEventProps } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UserUpdatedIntegrationEvent extends IntegrationEvent {
 | 
				
			||||||
 | 
					  readonly firstName: string;
 | 
				
			||||||
 | 
					  readonly lastName: string;
 | 
				
			||||||
 | 
					  readonly email: string;
 | 
				
			||||||
 | 
					  readonly phone: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: IntegrationEventProps<UserUpdatedIntegrationEvent>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.firstName = props.firstName;
 | 
				
			||||||
 | 
					    this.lastName = props.lastName;
 | 
				
			||||||
 | 
					    this.email = props.email;
 | 
				
			||||||
 | 
					    this.phone = props.phone;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					import { RepositoryPort } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { UserEntity } from '../../domain/user.entity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type UserRepositoryPort = RepositoryPort<UserEntity>;
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { FindUserByIdQuery } from './find-user-by-id.query';
 | 
				
			||||||
 | 
					import { Inject } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { UserRepositoryPort } from '../../ports/user.repository.port';
 | 
				
			||||||
 | 
					import { UserEntity } from '@modules/user/core/domain/user.entity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@QueryHandler(FindUserByIdQuery)
 | 
				
			||||||
 | 
					export class FindUserByIdQueryHandler implements IQueryHandler {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(USER_REPOSITORY)
 | 
				
			||||||
 | 
					    private readonly userRepository: UserRepositoryPort,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					  async execute(query: FindUserByIdQuery): Promise<UserEntity> {
 | 
				
			||||||
 | 
					    return await this.userRepository.findOneById(query.id);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					import { QueryBase } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class FindUserByIdQuery extends QueryBase {
 | 
				
			||||||
 | 
					  readonly id: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(id: string) {
 | 
				
			||||||
 | 
					    super();
 | 
				
			||||||
 | 
					    this.id = id;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UserCreatedDomainEvent extends DomainEvent {
 | 
				
			||||||
 | 
					  readonly firstName: string;
 | 
				
			||||||
 | 
					  readonly lastName: string;
 | 
				
			||||||
 | 
					  readonly email: string;
 | 
				
			||||||
 | 
					  readonly phone: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: DomainEventProps<UserCreatedDomainEvent>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.firstName = props.firstName;
 | 
				
			||||||
 | 
					    this.lastName = props.lastName;
 | 
				
			||||||
 | 
					    this.email = props.email;
 | 
				
			||||||
 | 
					    this.phone = props.phone;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UserDeletedDomainEvent extends DomainEvent {
 | 
				
			||||||
 | 
					  constructor(props: DomainEventProps<UserDeletedDomainEvent>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UserUpdatedDomainEvent extends DomainEvent {
 | 
				
			||||||
 | 
					  readonly firstName: string;
 | 
				
			||||||
 | 
					  readonly lastName: string;
 | 
				
			||||||
 | 
					  readonly email: string;
 | 
				
			||||||
 | 
					  readonly phone: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: DomainEventProps<UserUpdatedDomainEvent>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.firstName = props.firstName;
 | 
				
			||||||
 | 
					    this.lastName = props.lastName;
 | 
				
			||||||
 | 
					    this.email = props.email;
 | 
				
			||||||
 | 
					    this.phone = props.phone;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,55 @@
 | 
				
			||||||
 | 
					import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { v4 } from 'uuid';
 | 
				
			||||||
 | 
					import { CreateUserProps, UpdateUserProps, UserProps } from './user.types';
 | 
				
			||||||
 | 
					import { UserCreatedDomainEvent } from './events/user-created.domain-event';
 | 
				
			||||||
 | 
					import { UserUpdatedDomainEvent } from './events/user-updated.domain-event';
 | 
				
			||||||
 | 
					import { UserDeletedDomainEvent } from './events/user-deleted.domain-event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UserEntity extends AggregateRoot<UserProps> {
 | 
				
			||||||
 | 
					  protected readonly _id: AggregateID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static create = (create: CreateUserProps): UserEntity => {
 | 
				
			||||||
 | 
					    const id = v4();
 | 
				
			||||||
 | 
					    const props: UserProps = { ...create };
 | 
				
			||||||
 | 
					    const user = new UserEntity({ id, props });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    user.addEvent(
 | 
				
			||||||
 | 
					      new UserCreatedDomainEvent({
 | 
				
			||||||
 | 
					        aggregateId: id,
 | 
				
			||||||
 | 
					        firstName: props.firstName,
 | 
				
			||||||
 | 
					        lastName: props.lastName,
 | 
				
			||||||
 | 
					        email: props.email,
 | 
				
			||||||
 | 
					        phone: props.phone,
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    return user;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  update(props: UpdateUserProps): void {
 | 
				
			||||||
 | 
					    this.props.firstName = props.firstName ?? this.props.firstName;
 | 
				
			||||||
 | 
					    this.props.lastName = props.lastName ?? this.props.lastName;
 | 
				
			||||||
 | 
					    this.props.email = props.email ?? this.props.email;
 | 
				
			||||||
 | 
					    this.props.phone = props.phone ?? this.props.phone;
 | 
				
			||||||
 | 
					    this.addEvent(
 | 
				
			||||||
 | 
					      new UserUpdatedDomainEvent({
 | 
				
			||||||
 | 
					        aggregateId: this._id,
 | 
				
			||||||
 | 
					        firstName: props.firstName,
 | 
				
			||||||
 | 
					        lastName: props.lastName,
 | 
				
			||||||
 | 
					        email: props.email,
 | 
				
			||||||
 | 
					        phone: props.phone,
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  delete(): void {
 | 
				
			||||||
 | 
					    this.addEvent(
 | 
				
			||||||
 | 
					      new UserDeletedDomainEvent({
 | 
				
			||||||
 | 
					        aggregateId: this.id,
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validate(): void {
 | 
				
			||||||
 | 
					    // entity business rules validation to protect it's invariant before saving entity to a database
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					import { ExceptionBase } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UserAlreadyExistsException extends ExceptionBase {
 | 
				
			||||||
 | 
					  static readonly message = 'User already exists';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public readonly code = 'USER.ALREADY_EXISTS';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(cause?: Error, metadata?: unknown) {
 | 
				
			||||||
 | 
					    super(UserAlreadyExistsException.message, cause, metadata);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class EmailAlreadyExistsException extends ExceptionBase {
 | 
				
			||||||
 | 
					  static readonly message = 'Email already exists';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public readonly code = 'EMAIL.ALREADY_EXISTS';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(cause?: Error, metadata?: unknown) {
 | 
				
			||||||
 | 
					    super(EmailAlreadyExistsException.message, cause, metadata);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class PhoneAlreadyExistsException extends ExceptionBase {
 | 
				
			||||||
 | 
					  static readonly message = 'Phone already exists';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public readonly code = 'PHONE.ALREADY_EXISTS';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(cause?: Error, metadata?: unknown) {
 | 
				
			||||||
 | 
					    super(PhoneAlreadyExistsException.message, cause, metadata);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					// All properties that a User has
 | 
				
			||||||
 | 
					export interface UserProps {
 | 
				
			||||||
 | 
					  firstName?: string;
 | 
				
			||||||
 | 
					  lastName?: string;
 | 
				
			||||||
 | 
					  email?: string;
 | 
				
			||||||
 | 
					  phone?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Properties that are needed for a User creation
 | 
				
			||||||
 | 
					export interface CreateUserProps {
 | 
				
			||||||
 | 
					  firstName?: string;
 | 
				
			||||||
 | 
					  lastName?: string;
 | 
				
			||||||
 | 
					  email?: string;
 | 
				
			||||||
 | 
					  phone?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface UpdateUserProps {
 | 
				
			||||||
 | 
					  firstName?: string;
 | 
				
			||||||
 | 
					  lastName?: string;
 | 
				
			||||||
 | 
					  email?: string;
 | 
				
			||||||
 | 
					  phone?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,11 +0,0 @@
 | 
				
			||||||
import { IsInt, IsOptional } from 'class-validator';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class FindAllUsersRequest {
 | 
					 | 
				
			||||||
  @IsInt()
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  page?: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsInt()
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  perPage?: number;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,35 +0,0 @@
 | 
				
			||||||
import { AutoMap } from '@automapper/classes';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  IsEmail,
 | 
					 | 
				
			||||||
  IsNotEmpty,
 | 
					 | 
				
			||||||
  IsOptional,
 | 
					 | 
				
			||||||
  IsPhoneNumber,
 | 
					 | 
				
			||||||
  IsString,
 | 
					 | 
				
			||||||
} from 'class-validator';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class UpdateUserRequest {
 | 
					 | 
				
			||||||
  @IsString()
 | 
					 | 
				
			||||||
  @IsNotEmpty()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  uuid: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsString()
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  firstName?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsString()
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  lastName?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsEmail()
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  email?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsPhoneNumber()
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  phone?: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,18 +0,0 @@
 | 
				
			||||||
import { AutoMap } from '@automapper/classes';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class User {
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  uuid: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  firstName?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  lastName?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  email?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  phone?: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,51 +0,0 @@
 | 
				
			||||||
import { Mapper } from '@automapper/core';
 | 
					 | 
				
			||||||
import { InjectMapper } from '@automapper/nestjs';
 | 
					 | 
				
			||||||
import { CommandHandler } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
 | 
					 | 
				
			||||||
import { CreateUserCommand } from '../../commands/create-user.command';
 | 
					 | 
				
			||||||
import { CreateUserRequest } from '../dtos/create-user.request';
 | 
					 | 
				
			||||||
import { User } from '../entities/user';
 | 
					 | 
				
			||||||
import { Inject } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
 | 
					 | 
				
			||||||
import { IPublishMessage } from '../../../../interfaces/message-publisher';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@CommandHandler(CreateUserCommand)
 | 
					 | 
				
			||||||
export class CreateUserUseCase {
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    private readonly repository: UsersRepository,
 | 
					 | 
				
			||||||
    @Inject(MESSAGE_PUBLISHER)
 | 
					 | 
				
			||||||
    private readonly messagePublisher: IPublishMessage,
 | 
					 | 
				
			||||||
    @InjectMapper() private readonly mapper: Mapper,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  execute = async (command: CreateUserCommand): Promise<User> => {
 | 
					 | 
				
			||||||
    const entity = this.mapper.map(
 | 
					 | 
				
			||||||
      command.createUserRequest,
 | 
					 | 
				
			||||||
      CreateUserRequest,
 | 
					 | 
				
			||||||
      User,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const user = await this.repository.create(entity);
 | 
					 | 
				
			||||||
      this.messagePublisher.publish('user.create', JSON.stringify(user));
 | 
					 | 
				
			||||||
      this.messagePublisher.publish(
 | 
					 | 
				
			||||||
        'logging.user.create.info',
 | 
					 | 
				
			||||||
        JSON.stringify(user),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      return user;
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      let key = 'logging.user.create.crit';
 | 
					 | 
				
			||||||
      if (error.message.includes('Already exists')) {
 | 
					 | 
				
			||||||
        key = 'logging.user.create.warning';
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      this.messagePublisher.publish(
 | 
					 | 
				
			||||||
        key,
 | 
					 | 
				
			||||||
        JSON.stringify({
 | 
					 | 
				
			||||||
          command,
 | 
					 | 
				
			||||||
          error,
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      throw error;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,40 +0,0 @@
 | 
				
			||||||
import { CommandHandler } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
 | 
					 | 
				
			||||||
import { DeleteUserCommand } from '../../commands/delete-user.command';
 | 
					 | 
				
			||||||
import { User } from '../entities/user';
 | 
					 | 
				
			||||||
import { IPublishMessage } from '../../../../interfaces/message-publisher';
 | 
					 | 
				
			||||||
import { Inject } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@CommandHandler(DeleteUserCommand)
 | 
					 | 
				
			||||||
export class DeleteUserUseCase {
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    private readonly repository: UsersRepository,
 | 
					 | 
				
			||||||
    @Inject(MESSAGE_PUBLISHER)
 | 
					 | 
				
			||||||
    private readonly messagePublisher: IPublishMessage,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  execute = async (command: DeleteUserCommand): Promise<User> => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const user = await this.repository.delete(command.uuid);
 | 
					 | 
				
			||||||
      this.messagePublisher.publish(
 | 
					 | 
				
			||||||
        'user.delete',
 | 
					 | 
				
			||||||
        JSON.stringify({ uuid: user.uuid }),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      this.messagePublisher.publish(
 | 
					 | 
				
			||||||
        'logging.user.delete.info',
 | 
					 | 
				
			||||||
        JSON.stringify({ uuid: user.uuid }),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      return user;
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      this.messagePublisher.publish(
 | 
					 | 
				
			||||||
        'logging.user.delete.crit',
 | 
					 | 
				
			||||||
        JSON.stringify({
 | 
					 | 
				
			||||||
          command,
 | 
					 | 
				
			||||||
          error,
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      throw error;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,15 +0,0 @@
 | 
				
			||||||
import { QueryHandler } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import { ICollection } from 'src/modules/database/interfaces/collection.interface';
 | 
					 | 
				
			||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
 | 
					 | 
				
			||||||
import { FindAllUsersQuery } from '../../queries/find-all-users.query';
 | 
					 | 
				
			||||||
import { User } from '../entities/user';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@QueryHandler(FindAllUsersQuery)
 | 
					 | 
				
			||||||
export class FindAllUsersUseCase {
 | 
					 | 
				
			||||||
  constructor(private readonly repository: UsersRepository) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  execute = async (
 | 
					 | 
				
			||||||
    findAllUsersQuery: FindAllUsersQuery,
 | 
					 | 
				
			||||||
  ): Promise<ICollection<User>> =>
 | 
					 | 
				
			||||||
    this.repository.findAll(findAllUsersQuery.page, findAllUsersQuery.perPage);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,33 +0,0 @@
 | 
				
			||||||
import { Inject, NotFoundException } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { QueryHandler } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
 | 
					 | 
				
			||||||
import { FindUserByUuidQuery } from '../../queries/find-user-by-uuid.query';
 | 
					 | 
				
			||||||
import { User } from '../entities/user';
 | 
					 | 
				
			||||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
 | 
					 | 
				
			||||||
import { IPublishMessage } from '../../../../interfaces/message-publisher';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@QueryHandler(FindUserByUuidQuery)
 | 
					 | 
				
			||||||
export class FindUserByUuidUseCase {
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    private readonly repository: UsersRepository,
 | 
					 | 
				
			||||||
    @Inject(MESSAGE_PUBLISHER)
 | 
					 | 
				
			||||||
    private readonly messagePublisher: IPublishMessage,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  execute = async (findUserByUuid: FindUserByUuidQuery): Promise<User> => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const user = await this.repository.findOneByUuid(findUserByUuid.uuid);
 | 
					 | 
				
			||||||
      if (!user) throw new NotFoundException();
 | 
					 | 
				
			||||||
      return user;
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      this.messagePublisher.publish(
 | 
					 | 
				
			||||||
        'logging.user.read.warning',
 | 
					 | 
				
			||||||
        JSON.stringify({
 | 
					 | 
				
			||||||
          query: findUserByUuid,
 | 
					 | 
				
			||||||
          error,
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      throw error;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,53 +0,0 @@
 | 
				
			||||||
import { Mapper } from '@automapper/core';
 | 
					 | 
				
			||||||
import { InjectMapper } from '@automapper/nestjs';
 | 
					 | 
				
			||||||
import { CommandHandler } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
 | 
					 | 
				
			||||||
import { UpdateUserCommand } from '../../commands/update-user.command';
 | 
					 | 
				
			||||||
import { UpdateUserRequest } from '../dtos/update-user.request';
 | 
					 | 
				
			||||||
import { User } from '../entities/user';
 | 
					 | 
				
			||||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
 | 
					 | 
				
			||||||
import { Inject } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { IPublishMessage } from '../../../../interfaces/message-publisher';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@CommandHandler(UpdateUserCommand)
 | 
					 | 
				
			||||||
export class UpdateUserUseCase {
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    private readonly repository: UsersRepository,
 | 
					 | 
				
			||||||
    @Inject(MESSAGE_PUBLISHER)
 | 
					 | 
				
			||||||
    private readonly messagePublisher: IPublishMessage,
 | 
					 | 
				
			||||||
    @InjectMapper() private readonly mapper: Mapper,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  execute = async (command: UpdateUserCommand): Promise<User> => {
 | 
					 | 
				
			||||||
    const entity = this.mapper.map(
 | 
					 | 
				
			||||||
      command.updateUserRequest,
 | 
					 | 
				
			||||||
      UpdateUserRequest,
 | 
					 | 
				
			||||||
      User,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const user = await this.repository.update(
 | 
					 | 
				
			||||||
        command.updateUserRequest.uuid,
 | 
					 | 
				
			||||||
        entity,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      this.messagePublisher.publish(
 | 
					 | 
				
			||||||
        'user.update',
 | 
					 | 
				
			||||||
        JSON.stringify(command.updateUserRequest),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      this.messagePublisher.publish(
 | 
					 | 
				
			||||||
        'logging.user.update.info',
 | 
					 | 
				
			||||||
        JSON.stringify(command.updateUserRequest),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      return user;
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      this.messagePublisher.publish(
 | 
					 | 
				
			||||||
        'logging.user.update.crit',
 | 
					 | 
				
			||||||
        JSON.stringify({
 | 
					 | 
				
			||||||
          command,
 | 
					 | 
				
			||||||
          error,
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      throw error;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,56 @@
 | 
				
			||||||
 | 
					import { Inject, Injectable, Logger } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { EventEmitter2 } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  LoggerBase,
 | 
				
			||||||
 | 
					  MessagePublisherPort,
 | 
				
			||||||
 | 
					  PrismaRepositoryBase,
 | 
				
			||||||
 | 
					} from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { PrismaService } from './prisma.service';
 | 
				
			||||||
 | 
					import { UserEntity } from '../core/domain/user.entity';
 | 
				
			||||||
 | 
					import { UserRepositoryPort } from '../core/application/ports/user.repository.port';
 | 
				
			||||||
 | 
					import { UserMapper } from '../user.mapper';
 | 
				
			||||||
 | 
					import { USER_MESSAGE_PUBLISHER } from '../user.di-tokens';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type UserBaseModel = {
 | 
				
			||||||
 | 
					  uuid: string;
 | 
				
			||||||
 | 
					  firstName: string;
 | 
				
			||||||
 | 
					  lastName: string;
 | 
				
			||||||
 | 
					  email: string;
 | 
				
			||||||
 | 
					  phone: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type UserReadModel = UserBaseModel & {
 | 
				
			||||||
 | 
					  createdAt?: Date;
 | 
				
			||||||
 | 
					  updatedAt?: Date;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type UserWriteModel = UserBaseModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 *  Repository is used for retrieving/saving domain entities
 | 
				
			||||||
 | 
					 * */
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class UserRepository
 | 
				
			||||||
 | 
					  extends PrismaRepositoryBase<UserEntity, UserReadModel, UserWriteModel>
 | 
				
			||||||
 | 
					  implements UserRepositoryPort
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    prisma: PrismaService,
 | 
				
			||||||
 | 
					    mapper: UserMapper,
 | 
				
			||||||
 | 
					    eventEmitter: EventEmitter2,
 | 
				
			||||||
 | 
					    @Inject(USER_MESSAGE_PUBLISHER)
 | 
				
			||||||
 | 
					    protected readonly messagePublisher: MessagePublisherPort,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    super(
 | 
				
			||||||
 | 
					      prisma.user,
 | 
				
			||||||
 | 
					      prisma,
 | 
				
			||||||
 | 
					      mapper,
 | 
				
			||||||
 | 
					      eventEmitter,
 | 
				
			||||||
 | 
					      new LoggerBase({
 | 
				
			||||||
 | 
					        logger: new Logger(UserRepository.name),
 | 
				
			||||||
 | 
					        domain: 'user',
 | 
				
			||||||
 | 
					        messagePublisher,
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					import { PaginatedResponseDto } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { UserResponseDto } from './user.response.dto';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UserPaginatedResponseDto extends PaginatedResponseDto<UserResponseDto> {
 | 
				
			||||||
 | 
					  readonly data: readonly UserResponseDto[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					import { ResponseBase } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UserResponseDto extends ResponseBase {
 | 
				
			||||||
 | 
					  firstName: string;
 | 
				
			||||||
 | 
					  lastName: string;
 | 
				
			||||||
 | 
					  email: string;
 | 
				
			||||||
 | 
					  phone: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,49 @@
 | 
				
			||||||
 | 
					import { Controller, UsePipes } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { CommandBus } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { GrpcMethod, RpcException } from '@nestjs/microservices';
 | 
				
			||||||
 | 
					import { AggregateID } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { IdResponse } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { RpcValidationPipe } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { CreateUserRequestDto } from './dtos/create-user.request.dto';
 | 
				
			||||||
 | 
					import { CreateUserCommand } from '@modules/user/core/application/commands/create-user/create-user.command';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  EmailAlreadyExistsException,
 | 
				
			||||||
 | 
					  PhoneAlreadyExistsException,
 | 
				
			||||||
 | 
					  UserAlreadyExistsException,
 | 
				
			||||||
 | 
					} from '@modules/user/core/domain/user.errors';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@UsePipes(
 | 
				
			||||||
 | 
					  new RpcValidationPipe({
 | 
				
			||||||
 | 
					    whitelist: false,
 | 
				
			||||||
 | 
					    forbidUnknownValues: false,
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					@Controller()
 | 
				
			||||||
 | 
					export class CreateUserGrpcController {
 | 
				
			||||||
 | 
					  constructor(private readonly commandBus: CommandBus) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @GrpcMethod('UserService', 'Create')
 | 
				
			||||||
 | 
					  async create(data: CreateUserRequestDto): Promise<IdResponse> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const aggregateID: AggregateID = await this.commandBus.execute(
 | 
				
			||||||
 | 
					        new CreateUserCommand(data),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return new IdResponse(aggregateID);
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        error instanceof UserAlreadyExistsException ||
 | 
				
			||||||
 | 
					        error instanceof EmailAlreadyExistsException ||
 | 
				
			||||||
 | 
					        error instanceof PhoneAlreadyExistsException
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					        throw new RpcException({
 | 
				
			||||||
 | 
					          code: RpcExceptionCode.ALREADY_EXISTS,
 | 
				
			||||||
 | 
					          message: error.message,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      throw new RpcException({
 | 
				
			||||||
 | 
					        code: RpcExceptionCode.UNKNOWN,
 | 
				
			||||||
 | 
					        message: error.message,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  DatabaseErrorException,
 | 
				
			||||||
 | 
					  NotFoundException,
 | 
				
			||||||
 | 
					  RpcExceptionCode,
 | 
				
			||||||
 | 
					  RpcValidationPipe,
 | 
				
			||||||
 | 
					} from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { Controller, UsePipes } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { CommandBus } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { GrpcMethod, RpcException } from '@nestjs/microservices';
 | 
				
			||||||
 | 
					import { DeleteUserRequestDto } from './dtos/delete-user.request.dto';
 | 
				
			||||||
 | 
					import { DeleteUserCommand } from '@modules/user/core/application/commands/delete-user/delete-user.command';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@UsePipes(
 | 
				
			||||||
 | 
					  new RpcValidationPipe({
 | 
				
			||||||
 | 
					    whitelist: true,
 | 
				
			||||||
 | 
					    forbidUnknownValues: false,
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					@Controller()
 | 
				
			||||||
 | 
					export class DeleteUserGrpcController {
 | 
				
			||||||
 | 
					  constructor(private readonly commandBus: CommandBus) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @GrpcMethod('UserService', 'Delete')
 | 
				
			||||||
 | 
					  async delete(data: DeleteUserRequestDto): Promise<void> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await this.commandBus.execute(new DeleteUserCommand(data));
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					      if (error instanceof NotFoundException)
 | 
				
			||||||
 | 
					        throw new RpcException({
 | 
				
			||||||
 | 
					          code: RpcExceptionCode.NOT_FOUND,
 | 
				
			||||||
 | 
					          message: error.message,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      if (error instanceof DatabaseErrorException)
 | 
				
			||||||
 | 
					        throw new RpcException({
 | 
				
			||||||
 | 
					          code: RpcExceptionCode.INTERNAL,
 | 
				
			||||||
 | 
					          message: error.message,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      throw new RpcException({
 | 
				
			||||||
 | 
					        code: RpcExceptionCode.UNKNOWN,
 | 
				
			||||||
 | 
					        message: error.message,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,19 @@
 | 
				
			||||||
 | 
					import { IsEmail, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class CreateUserRequestDto {
 | 
				
			||||||
 | 
					  @IsString()
 | 
				
			||||||
 | 
					  @IsOptional()
 | 
				
			||||||
 | 
					  firstName: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @IsString()
 | 
				
			||||||
 | 
					  @IsOptional()
 | 
				
			||||||
 | 
					  lastName: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @IsEmail()
 | 
				
			||||||
 | 
					  @IsOptional()
 | 
				
			||||||
 | 
					  email?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @IsPhoneNumber()
 | 
				
			||||||
 | 
					  @IsOptional()
 | 
				
			||||||
 | 
					  phone?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import { IsNotEmpty, IsString } from 'class-validator';
 | 
					import { IsNotEmpty, IsString } from 'class-validator';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class FindUserByUuidRequest {
 | 
					export class DeleteUserRequestDto {
 | 
				
			||||||
  @IsString()
 | 
					  @IsString()
 | 
				
			||||||
  @IsNotEmpty()
 | 
					  @IsNotEmpty()
 | 
				
			||||||
  uuid: string;
 | 
					  id: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					import { IsNotEmpty, IsString } from 'class-validator';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class FindUserByIdRequestDto {
 | 
				
			||||||
 | 
					  @IsString()
 | 
				
			||||||
 | 
					  @IsNotEmpty()
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,29 +1,22 @@
 | 
				
			||||||
import { AutoMap } from '@automapper/classes';
 | 
					 | 
				
			||||||
import { IsEmail, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
 | 
					import { IsEmail, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class CreateUserRequest {
 | 
					export class UpdateUserRequestDto {
 | 
				
			||||||
  @IsString()
 | 
					  @IsString()
 | 
				
			||||||
  @IsOptional()
 | 
					  id: string;
 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  uuid?: string;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @IsString()
 | 
					  @IsString()
 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  firstName?: string;
 | 
					  firstName?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @IsString()
 | 
					  @IsString()
 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  lastName?: string;
 | 
					  lastName?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @IsEmail()
 | 
					  @IsEmail()
 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  email?: string;
 | 
					  email?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @IsPhoneNumber()
 | 
					  @IsPhoneNumber()
 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  phone?: string;
 | 
					  phone?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					import { Controller, UsePipes } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { QueryBus } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { GrpcMethod, RpcException } from '@nestjs/microservices';
 | 
				
			||||||
 | 
					import { NotFoundException } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { RpcValidationPipe } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { UserMapper } from '@modules/user/user.mapper';
 | 
				
			||||||
 | 
					import { FindUserByIdRequestDto } from './dtos/find-user-by-id.request.dto';
 | 
				
			||||||
 | 
					import { UserResponseDto } from '../dtos/user.response.dto';
 | 
				
			||||||
 | 
					import { UserEntity } from '@modules/user/core/domain/user.entity';
 | 
				
			||||||
 | 
					import { FindUserByIdQuery } from '@modules/user/core/application/queries/find-user-by-id/find-user-by-id.query';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@UsePipes(
 | 
				
			||||||
 | 
					  new RpcValidationPipe({
 | 
				
			||||||
 | 
					    whitelist: false,
 | 
				
			||||||
 | 
					    forbidUnknownValues: false,
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					@Controller()
 | 
				
			||||||
 | 
					export class FindUserByIdGrpcController {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    protected readonly mapper: UserMapper,
 | 
				
			||||||
 | 
					    private readonly queryBus: QueryBus,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @GrpcMethod('UserService', 'FindOneById')
 | 
				
			||||||
 | 
					  async findOnebyId(data: FindUserByIdRequestDto): Promise<UserResponseDto> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const user: UserEntity = await this.queryBus.execute(
 | 
				
			||||||
 | 
					        new FindUserByIdQuery(data.id),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return this.mapper.toResponse(user);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      if (e instanceof NotFoundException) {
 | 
				
			||||||
 | 
					        throw new RpcException({
 | 
				
			||||||
 | 
					          code: RpcExceptionCode.NOT_FOUND,
 | 
				
			||||||
 | 
					          message: e.message,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      throw new RpcException({
 | 
				
			||||||
 | 
					        code: RpcExceptionCode.UNKNOWN,
 | 
				
			||||||
 | 
					        message: e.message,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,57 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  AggregateID,
 | 
				
			||||||
 | 
					  IdResponse,
 | 
				
			||||||
 | 
					  RpcExceptionCode,
 | 
				
			||||||
 | 
					  RpcValidationPipe,
 | 
				
			||||||
 | 
					} from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { Controller, UsePipes } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { CommandBus } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { GrpcMethod, RpcException } from '@nestjs/microservices';
 | 
				
			||||||
 | 
					import { UpdateUserRequestDto } from './dtos/update-user.request.dto';
 | 
				
			||||||
 | 
					import { UpdateUserCommand } from '@modules/user/core/application/commands/update-user/update-user.command';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  EmailAlreadyExistsException,
 | 
				
			||||||
 | 
					  PhoneAlreadyExistsException,
 | 
				
			||||||
 | 
					  UserAlreadyExistsException,
 | 
				
			||||||
 | 
					} from '@modules/user/core/domain/user.errors';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@UsePipes(
 | 
				
			||||||
 | 
					  new RpcValidationPipe({
 | 
				
			||||||
 | 
					    whitelist: true,
 | 
				
			||||||
 | 
					    forbidUnknownValues: false,
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					@Controller()
 | 
				
			||||||
 | 
					export class UpdateUserGrpcController {
 | 
				
			||||||
 | 
					  constructor(private readonly commandBus: CommandBus) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @GrpcMethod('UserService', 'Update')
 | 
				
			||||||
 | 
					  async updateUser(data: UpdateUserRequestDto): Promise<IdResponse> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const aggregateID: AggregateID = await this.commandBus.execute(
 | 
				
			||||||
 | 
					        new UpdateUserCommand({
 | 
				
			||||||
 | 
					          id: data.id,
 | 
				
			||||||
 | 
					          firstName: data.firstName,
 | 
				
			||||||
 | 
					          lastName: data.lastName,
 | 
				
			||||||
 | 
					          email: data.email,
 | 
				
			||||||
 | 
					          phone: data.phone,
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return new IdResponse(aggregateID);
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        error instanceof UserAlreadyExistsException ||
 | 
				
			||||||
 | 
					        error instanceof EmailAlreadyExistsException ||
 | 
				
			||||||
 | 
					        error instanceof PhoneAlreadyExistsException
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					        throw new RpcException({
 | 
				
			||||||
 | 
					          code: RpcExceptionCode.ALREADY_EXISTS,
 | 
				
			||||||
 | 
					          message: error.message,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      throw new RpcException({
 | 
				
			||||||
 | 
					        code: RpcExceptionCode.UNKNOWN,
 | 
				
			||||||
 | 
					        message: error.message,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -2,20 +2,20 @@ syntax = "proto3";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package user;
 | 
					package user;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
service UsersService {
 | 
					service UserService {
 | 
				
			||||||
  rpc FindOneByUuid(UserByUuid) returns (User);
 | 
					  rpc FindOneById(UserById) returns (User);
 | 
				
			||||||
  rpc FindAll(UserFilter) returns (Users);
 | 
					  rpc FindAll(UserFilter) returns (Users);
 | 
				
			||||||
  rpc Create(User) returns (User);
 | 
					  rpc Create(User) returns (UserById);
 | 
				
			||||||
  rpc Update(User) returns (User);
 | 
					  rpc Update(User) returns (UserById);
 | 
				
			||||||
  rpc Delete(UserByUuid) returns (Empty);
 | 
					  rpc Delete(UserById) returns (Empty);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message UserByUuid {
 | 
					message UserById {
 | 
				
			||||||
  string uuid = 1;
 | 
					  string id = 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message User {
 | 
					message User {
 | 
				
			||||||
  string uuid = 1;
 | 
					  string id = 1;
 | 
				
			||||||
  string firstName = 2;
 | 
					  string firstName = 2;
 | 
				
			||||||
  string lastName = 3;
 | 
					  string lastName = 3;
 | 
				
			||||||
  string email = 4;
 | 
					  string email = 4;
 | 
				
			||||||
| 
						 | 
					@ -1,29 +0,0 @@
 | 
				
			||||||
import { createMap, forMember, ignore, Mapper } from '@automapper/core';
 | 
					 | 
				
			||||||
import { AutomapperProfile, InjectMapper } from '@automapper/nestjs';
 | 
					 | 
				
			||||||
import { Injectable } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { UserPresenter } from '../adapters/primaries/user.presenter';
 | 
					 | 
				
			||||||
import { CreateUserRequest } from '../domain/dtos/create-user.request';
 | 
					 | 
				
			||||||
import { UpdateUserRequest } from '../domain/dtos/update-user.request';
 | 
					 | 
				
			||||||
import { User } from '../domain/entities/user';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Injectable()
 | 
					 | 
				
			||||||
export class UserProfile extends AutomapperProfile {
 | 
					 | 
				
			||||||
  constructor(@InjectMapper() mapper: Mapper) {
 | 
					 | 
				
			||||||
    super(mapper);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  override get profile() {
 | 
					 | 
				
			||||||
    return (mapper) => {
 | 
					 | 
				
			||||||
      createMap(mapper, User, UserPresenter);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      createMap(mapper, CreateUserRequest, User);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      createMap(
 | 
					 | 
				
			||||||
        mapper,
 | 
					 | 
				
			||||||
        UpdateUserRequest,
 | 
					 | 
				
			||||||
        User,
 | 
					 | 
				
			||||||
        forMember((dest) => dest.uuid, ignore()),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,11 +0,0 @@
 | 
				
			||||||
import { FindAllUsersRequest } from '../domain/dtos/find-all-users.request';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class FindAllUsersQuery {
 | 
					 | 
				
			||||||
  page: number;
 | 
					 | 
				
			||||||
  perPage: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(findAllUsersRequest?: FindAllUsersRequest) {
 | 
					 | 
				
			||||||
    this.page = findAllUsersRequest?.page ?? 1;
 | 
					 | 
				
			||||||
    this.perPage = findAllUsersRequest?.perPage ?? 10;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,9 +0,0 @@
 | 
				
			||||||
import { FindUserByUuidRequest } from '../domain/dtos/find-user-by-uuid.request';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class FindUserByUuidQuery {
 | 
					 | 
				
			||||||
  readonly uuid: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(findUserByUuidRequest: FindUserByUuidRequest) {
 | 
					 | 
				
			||||||
    this.uuid = findUserByUuidRequest.uuid;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,120 @@
 | 
				
			||||||
 | 
					import { UserEntity } from '@modules/user/core/domain/user.entity';
 | 
				
			||||||
 | 
					import { CreateUserProps } from '@modules/user/core/domain/user.types';
 | 
				
			||||||
 | 
					import { PrismaService } from '@modules/user/infrastructure/prisma.service';
 | 
				
			||||||
 | 
					import { UserRepository } from '@modules/user/infrastructure/user.repository';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  USER_MESSAGE_PUBLISHER,
 | 
				
			||||||
 | 
					  USER_REPOSITORY,
 | 
				
			||||||
 | 
					} from '@modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { UserMapper } from '@modules/user/user.mapper';
 | 
				
			||||||
 | 
					import { ConfigModule } from '@nestjs/config';
 | 
				
			||||||
 | 
					import { EventEmitterModule } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import { Test } from '@nestjs/testing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('User Repository', () => {
 | 
				
			||||||
 | 
					  let prismaService: PrismaService;
 | 
				
			||||||
 | 
					  let userRepository: UserRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const executeInsertCommand = async (table: string, object: any) => {
 | 
				
			||||||
 | 
					    const command = `INSERT INTO "${table}" ("${Object.keys(object).join(
 | 
				
			||||||
 | 
					      '","',
 | 
				
			||||||
 | 
					    )}") VALUES ('${Object.values(object).join("','")}')`;
 | 
				
			||||||
 | 
					    await prismaService.$executeRawUnsafe(command);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  const getSeed = (index: number, uuid: string): string => {
 | 
				
			||||||
 | 
					    return `${uuid.slice(0, -2)}${index.toString(16).padStart(2, '0')}`;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const baseUuid = {
 | 
				
			||||||
 | 
					    uuid: 'be459a29-7a41-4c0b-b371-abe90bfb6f00',
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const createUsers = async (nbToCreate = 10) => {
 | 
				
			||||||
 | 
					    for (let i = 0; i < nbToCreate; i++) {
 | 
				
			||||||
 | 
					      const userToCreate = {
 | 
				
			||||||
 | 
					        uuid: getSeed(i, baseUuid.uuid),
 | 
				
			||||||
 | 
					        firstName: `John${i}`,
 | 
				
			||||||
 | 
					        lastName: `Doe${i}`,
 | 
				
			||||||
 | 
					        email: `john.doe${i}@email.com`,
 | 
				
			||||||
 | 
					        phone: `+33611223344${i}`,
 | 
				
			||||||
 | 
					        createdAt: '2023-07-24 13:07:05.000',
 | 
				
			||||||
 | 
					        updatedAt: '2023-07-24 13:07:05.000',
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      userToCreate.uuid = getSeed(i, baseUuid.uuid);
 | 
				
			||||||
 | 
					      await executeInsertCommand('user', userToCreate);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const mockMessagePublisher = {
 | 
				
			||||||
 | 
					    publish: jest.fn().mockImplementation(),
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const mockLogger = {
 | 
				
			||||||
 | 
					    log: jest.fn(),
 | 
				
			||||||
 | 
					    warn: jest.fn(),
 | 
				
			||||||
 | 
					    error: jest.fn(),
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      imports: [
 | 
				
			||||||
 | 
					        EventEmitterModule.forRoot(),
 | 
				
			||||||
 | 
					        ConfigModule.forRoot({ isGlobal: true }),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        PrismaService,
 | 
				
			||||||
 | 
					        UserMapper,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: USER_REPOSITORY,
 | 
				
			||||||
 | 
					          useClass: UserRepository,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: USER_MESSAGE_PUBLISHER,
 | 
				
			||||||
 | 
					          useValue: mockMessagePublisher,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					      // disable logging
 | 
				
			||||||
 | 
					      .setLogger(mockLogger)
 | 
				
			||||||
 | 
					      .compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    prismaService = module.get<PrismaService>(PrismaService);
 | 
				
			||||||
 | 
					    userRepository = module.get<UserRepository>(USER_REPOSITORY);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterAll(async () => {
 | 
				
			||||||
 | 
					    await prismaService.$disconnect();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(async () => {
 | 
				
			||||||
 | 
					    await prismaService.user.deleteMany();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('findOneById', () => {
 | 
				
			||||||
 | 
					    it('should return a user', async () => {
 | 
				
			||||||
 | 
					      await createUsers(1);
 | 
				
			||||||
 | 
					      const result = await userRepository.findOneById(baseUuid.uuid);
 | 
				
			||||||
 | 
					      expect(result.id).toBe(baseUuid.uuid);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('create', () => {
 | 
				
			||||||
 | 
					    it('should create a user', async () => {
 | 
				
			||||||
 | 
					      const beforeCount = await prismaService.user.count();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const createUserProps: CreateUserProps = {
 | 
				
			||||||
 | 
					        firstName: 'Jane',
 | 
				
			||||||
 | 
					        lastName: 'Doe',
 | 
				
			||||||
 | 
					        email: 'jane.doe@email.com',
 | 
				
			||||||
 | 
					        phone: '+33622334455',
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const userToCreate: UserEntity = UserEntity.create(createUserProps);
 | 
				
			||||||
 | 
					      await userRepository.insert(userToCreate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const afterCount = await prismaService.user.count();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(afterCount - beforeCount).toBe(1);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -1,174 +0,0 @@
 | 
				
			||||||
import { TestingModule, Test } from '@nestjs/testing';
 | 
					 | 
				
			||||||
import { DatabaseModule } from '../../../database/database.module';
 | 
					 | 
				
			||||||
import { PrismaService } from '../../../database/adapters/secondaries/prisma-service';
 | 
					 | 
				
			||||||
import { DatabaseException } from '../../../database/exceptions/database.exception';
 | 
					 | 
				
			||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
 | 
					 | 
				
			||||||
import { User } from '../../domain/entities/user';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('UsersRepository', () => {
 | 
					 | 
				
			||||||
  let prismaService: PrismaService;
 | 
					 | 
				
			||||||
  let usersRepository: UsersRepository;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const createUsers = async (nbToCreate = 10) => {
 | 
					 | 
				
			||||||
    for (let i = 0; i < nbToCreate; i++) {
 | 
					 | 
				
			||||||
      await prismaService.user.create({
 | 
					 | 
				
			||||||
        data: {
 | 
					 | 
				
			||||||
          firstName: `firstName-${i}`,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      imports: [DatabaseModule],
 | 
					 | 
				
			||||||
      providers: [UsersRepository, PrismaService],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    prismaService = module.get<PrismaService>(PrismaService);
 | 
					 | 
				
			||||||
    usersRepository = module.get<UsersRepository>(UsersRepository);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  afterAll(async () => {
 | 
					 | 
				
			||||||
    await prismaService.$disconnect();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeEach(async () => {
 | 
					 | 
				
			||||||
    await prismaService.user.deleteMany();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('findAll', () => {
 | 
					 | 
				
			||||||
    it('should return an empty data array', async () => {
 | 
					 | 
				
			||||||
      const res = await usersRepository.findAll();
 | 
					 | 
				
			||||||
      expect(res).toEqual({
 | 
					 | 
				
			||||||
        data: [],
 | 
					 | 
				
			||||||
        total: 0,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should return a data array with 8 users', async () => {
 | 
					 | 
				
			||||||
      await createUsers(8);
 | 
					 | 
				
			||||||
      const users = await usersRepository.findAll();
 | 
					 | 
				
			||||||
      expect(users.data.length).toBe(8);
 | 
					 | 
				
			||||||
      expect(users.total).toBe(8);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should return a data array limited to 10 users', async () => {
 | 
					 | 
				
			||||||
      await createUsers(20);
 | 
					 | 
				
			||||||
      const users = await usersRepository.findAll();
 | 
					 | 
				
			||||||
      expect(users.data.length).toBe(10);
 | 
					 | 
				
			||||||
      expect(users.total).toBe(20);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('findOneByUuid', () => {
 | 
					 | 
				
			||||||
    it('should return a user', async () => {
 | 
					 | 
				
			||||||
      const userToFind = await prismaService.user.create({
 | 
					 | 
				
			||||||
        data: {
 | 
					 | 
				
			||||||
          firstName: 'test',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const user = await usersRepository.findOneByUuid(userToFind.uuid);
 | 
					 | 
				
			||||||
      expect(user.uuid).toBe(userToFind.uuid);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should return null', async () => {
 | 
					 | 
				
			||||||
      const user = await usersRepository.findOneByUuid(
 | 
					 | 
				
			||||||
        '544572be-11fb-4244-8235-587221fc9104',
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      expect(user).toBeNull();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('findOne', () => {
 | 
					 | 
				
			||||||
    it('should return a user according to its email', async () => {
 | 
					 | 
				
			||||||
      const userToFind = await prismaService.user.create({
 | 
					 | 
				
			||||||
        data: {
 | 
					 | 
				
			||||||
          email: 'test@test.com',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const user = await usersRepository.findOne({
 | 
					 | 
				
			||||||
        email: 'test@test.com',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      expect(user.uuid).toBe(userToFind.uuid);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should return null with unknown email', async () => {
 | 
					 | 
				
			||||||
      const user = await usersRepository.findOne({
 | 
					 | 
				
			||||||
        email: 'wrong@email.com',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      expect(user).toBeNull();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('create', () => {
 | 
					 | 
				
			||||||
    it('should create a user', async () => {
 | 
					 | 
				
			||||||
      const beforeCount = await prismaService.user.count();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const userToCreate: User = new User();
 | 
					 | 
				
			||||||
      userToCreate.firstName = 'test';
 | 
					 | 
				
			||||||
      const user = await usersRepository.create(userToCreate);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const afterCount = await prismaService.user.count();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      expect(afterCount - beforeCount).toBe(1);
 | 
					 | 
				
			||||||
      expect(user.uuid).toBeDefined();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('update', () => {
 | 
					 | 
				
			||||||
    it('should update user firstName', async () => {
 | 
					 | 
				
			||||||
      const userToUpdate = await prismaService.user.create({
 | 
					 | 
				
			||||||
        data: {
 | 
					 | 
				
			||||||
          firstName: 'test',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const toUpdate: User = new User();
 | 
					 | 
				
			||||||
      toUpdate.firstName = 'updated';
 | 
					 | 
				
			||||||
      const updateduser = await usersRepository.update(
 | 
					 | 
				
			||||||
        userToUpdate.uuid,
 | 
					 | 
				
			||||||
        toUpdate,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      expect(updateduser.uuid).toBe(userToUpdate.uuid);
 | 
					 | 
				
			||||||
      expect(updateduser.firstName).toBe('updated');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should throw DatabaseException', async () => {
 | 
					 | 
				
			||||||
      const toUpdate: User = new User();
 | 
					 | 
				
			||||||
      toUpdate.firstName = 'updated';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        usersRepository.update(
 | 
					 | 
				
			||||||
          '544572be-11fb-4244-8235-587221fc9104',
 | 
					 | 
				
			||||||
          toUpdate,
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('delete', () => {
 | 
					 | 
				
			||||||
    it('should delete a user', async () => {
 | 
					 | 
				
			||||||
      const userToRemove = await prismaService.user.create({
 | 
					 | 
				
			||||||
        data: {
 | 
					 | 
				
			||||||
          firstName: 'test',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await usersRepository.delete(userToRemove.uuid);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const count = await prismaService.user.count();
 | 
					 | 
				
			||||||
      expect(count).toBe(0);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should throw DatabaseException', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        usersRepository.delete('544572be-11fb-4244-8235-587221fc9104'),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(DatabaseException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,105 @@
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					import { AggregateID, UniqueConstraintException } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { ConflictException } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { CreateUserRequestDto } from '@modules/user/interface/grpc-controllers/dtos/create-user.request.dto';
 | 
				
			||||||
 | 
					import { CreateUserService } from '@modules/user/core/application/commands/create-user/create-user.service';
 | 
				
			||||||
 | 
					import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { UserEntity } from '@modules/user/core/domain/user.entity';
 | 
				
			||||||
 | 
					import { CreateUserCommand } from '@modules/user/core/application/commands/create-user/create-user.command';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  EmailAlreadyExistsException,
 | 
				
			||||||
 | 
					  PhoneAlreadyExistsException,
 | 
				
			||||||
 | 
					  UserAlreadyExistsException,
 | 
				
			||||||
 | 
					} from '@modules/user/core/domain/user.errors';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const createUserRequest: CreateUserRequestDto = {
 | 
				
			||||||
 | 
					  firstName: 'John',
 | 
				
			||||||
 | 
					  lastName: 'Doe',
 | 
				
			||||||
 | 
					  email: 'john.doe@email.com',
 | 
				
			||||||
 | 
					  phone: '+33611223344',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockUserRepository = {
 | 
				
			||||||
 | 
					  insert: jest
 | 
				
			||||||
 | 
					    .fn()
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => ({}))
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new Error();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new ConflictException('User already exists');
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new UniqueConstraintException('email already exists');
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new UniqueConstraintException('phone already exists');
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('create-user.service', () => {
 | 
				
			||||||
 | 
					  let createUserService: CreateUserService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: USER_REPOSITORY,
 | 
				
			||||||
 | 
					          useValue: mockUserRepository,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        CreateUserService,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    createUserService = module.get<CreateUserService>(CreateUserService);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(createUserService).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('execution', () => {
 | 
				
			||||||
 | 
					    const createUserCommand = new CreateUserCommand(createUserRequest);
 | 
				
			||||||
 | 
					    it('should create a new user', async () => {
 | 
				
			||||||
 | 
					      UserEntity.create = jest.fn().mockReturnValue({
 | 
				
			||||||
 | 
					        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      const result: AggregateID = await createUserService.execute(
 | 
				
			||||||
 | 
					        createUserCommand,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(result).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should throw an error if something bad happens', async () => {
 | 
				
			||||||
 | 
					      UserEntity.create = jest.fn().mockReturnValue({
 | 
				
			||||||
 | 
					        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        createUserService.execute(createUserCommand),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(Error);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should throw an exception if User already exists', async () => {
 | 
				
			||||||
 | 
					      UserEntity.create = jest.fn().mockReturnValue({
 | 
				
			||||||
 | 
					        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        createUserService.execute(createUserCommand),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(UserAlreadyExistsException);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should throw an exception if Email already exists', async () => {
 | 
				
			||||||
 | 
					      UserEntity.create = jest.fn().mockReturnValue({
 | 
				
			||||||
 | 
					        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        createUserService.execute(createUserCommand),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(EmailAlreadyExistsException);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should throw an exception if Phone already exists', async () => {
 | 
				
			||||||
 | 
					      UserEntity.create = jest.fn().mockReturnValue({
 | 
				
			||||||
 | 
					        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        createUserService.execute(createUserCommand),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(PhoneAlreadyExistsException);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,50 @@
 | 
				
			||||||
 | 
					import { DeleteUserCommand } from '@modules/user/core/application/commands/delete-user/delete-user.command';
 | 
				
			||||||
 | 
					import { DeleteUserService } from '@modules/user/core/application/commands/delete-user/delete-user.service';
 | 
				
			||||||
 | 
					import { DeleteUserRequestDto } from '@modules/user/interface/grpc-controllers/dtos/delete-user.request.dto';
 | 
				
			||||||
 | 
					import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const deleteUserRequest: DeleteUserRequestDto = {
 | 
				
			||||||
 | 
					  id: '165192d4-398a-4469-a16b-98c02cc6f531',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockUserEntity = {
 | 
				
			||||||
 | 
					  delete: jest.fn(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockUserRepository = {
 | 
				
			||||||
 | 
					  findOneById: jest.fn().mockImplementation(() => mockUserEntity),
 | 
				
			||||||
 | 
					  delete: jest.fn().mockImplementationOnce(() => true),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Delete User Service', () => {
 | 
				
			||||||
 | 
					  let deleteUserService: DeleteUserService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: USER_REPOSITORY,
 | 
				
			||||||
 | 
					          useValue: mockUserRepository,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        DeleteUserService,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    deleteUserService = module.get<DeleteUserService>(DeleteUserService);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(deleteUserService).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('execution', () => {
 | 
				
			||||||
 | 
					    const deleteUserCommand = new DeleteUserCommand(deleteUserRequest);
 | 
				
			||||||
 | 
					    it('should delete a user', async () => {
 | 
				
			||||||
 | 
					      const result: boolean = await deleteUserService.execute(
 | 
				
			||||||
 | 
					        deleteUserCommand,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(result).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,58 @@
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					import { UserEntity } from '@modules/user/core/domain/user.entity';
 | 
				
			||||||
 | 
					import { FindUserByIdQueryHandler } from '@modules/user/core/application/queries/find-user-by-id/find-user-by-id.query-handler';
 | 
				
			||||||
 | 
					import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { FindUserByIdQuery } from '@modules/user/core/application/queries/find-user-by-id/find-user-by-id.query';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const now = new Date('2023-06-21 06:00:00');
 | 
				
			||||||
 | 
					const user: UserEntity = new UserEntity({
 | 
				
			||||||
 | 
					  id: 'c160cf8c-f057-4962-841f-3ad68346df44',
 | 
				
			||||||
 | 
					  props: {
 | 
				
			||||||
 | 
					    firstName: 'John',
 | 
				
			||||||
 | 
					    lastName: 'Doe',
 | 
				
			||||||
 | 
					    email: 'john.doe@email.com',
 | 
				
			||||||
 | 
					    phone: '+33611223344',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  createdAt: now,
 | 
				
			||||||
 | 
					  updatedAt: now,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockUserRepository = {
 | 
				
			||||||
 | 
					  findOneById: jest.fn().mockImplementation(() => user),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('find-user-by-id.query-handler', () => {
 | 
				
			||||||
 | 
					  let findUserByIdQueryHandler: FindUserByIdQueryHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: USER_REPOSITORY,
 | 
				
			||||||
 | 
					          useValue: mockUserRepository,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        FindUserByIdQueryHandler,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    findUserByIdQueryHandler = module.get<FindUserByIdQueryHandler>(
 | 
				
			||||||
 | 
					      FindUserByIdQueryHandler,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(findUserByIdQueryHandler).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('execution', () => {
 | 
				
			||||||
 | 
					    it('should return a user', async () => {
 | 
				
			||||||
 | 
					      const findUserbyIdQuery = new FindUserByIdQuery(
 | 
				
			||||||
 | 
					        'dd264806-13b4-4226-9b18-87adf0ad5dd1',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      const user: UserEntity = await findUserByIdQueryHandler.execute(
 | 
				
			||||||
 | 
					        findUserbyIdQuery,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(user.getProps().lastName).toBe('Doe');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					import { PublishMessageWhenUserIsCreatedDomainEventHandler } from '@modules/user/core/application/event-handlers/publish-message-when-user-is-created.domain-event-handler';
 | 
				
			||||||
 | 
					import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { UserCreatedDomainEvent } from '@modules/user/core/domain/events/user-created.domain-event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockMessagePublisher = {
 | 
				
			||||||
 | 
					  publish: jest.fn().mockImplementation(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Publish message when user is created domain event handler', () => {
 | 
				
			||||||
 | 
					  let publishMessageWhenUserIsCreatedDomainEventHandler: PublishMessageWhenUserIsCreatedDomainEventHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: USER_MESSAGE_PUBLISHER,
 | 
				
			||||||
 | 
					          useValue: mockMessagePublisher,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        PublishMessageWhenUserIsCreatedDomainEventHandler,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    publishMessageWhenUserIsCreatedDomainEventHandler =
 | 
				
			||||||
 | 
					      module.get<PublishMessageWhenUserIsCreatedDomainEventHandler>(
 | 
				
			||||||
 | 
					        PublishMessageWhenUserIsCreatedDomainEventHandler,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should publish a message', () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockMessagePublisher, 'publish');
 | 
				
			||||||
 | 
					    const userCreatedDomainEvent: UserCreatedDomainEvent = {
 | 
				
			||||||
 | 
					      id: 'some-domain-event-id',
 | 
				
			||||||
 | 
					      aggregateId: 'some-aggregate-id',
 | 
				
			||||||
 | 
					      firstName: 'John',
 | 
				
			||||||
 | 
					      lastName: 'Doe',
 | 
				
			||||||
 | 
					      email: 'john.doe@email.com',
 | 
				
			||||||
 | 
					      phone: '+33611223344',
 | 
				
			||||||
 | 
					      metadata: {
 | 
				
			||||||
 | 
					        timestamp: new Date('2023-06-28T05:00:00Z').getTime(),
 | 
				
			||||||
 | 
					        correlationId: 'some-correlation-id',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    publishMessageWhenUserIsCreatedDomainEventHandler.handle(
 | 
				
			||||||
 | 
					      userCreatedDomainEvent,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    expect(publishMessageWhenUserIsCreatedDomainEventHandler).toBeDefined();
 | 
				
			||||||
 | 
					    expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					    expect(mockMessagePublisher.publish).toHaveBeenCalledWith(
 | 
				
			||||||
 | 
					      'user.created',
 | 
				
			||||||
 | 
					      '{"id":"some-aggregate-id","metadata":{"correlationId":"some-correlation-id","timestamp":1687928400000},"firstName":"John","lastName":"Doe","email":"john.doe@email.com","phone":"+33611223344"}',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { UserCreatedDomainEvent } from '@modules/user/core/domain/events/user-created.domain-event';
 | 
				
			||||||
 | 
					import { PublishMessageWhenUserIsDeletedDomainEventHandler } from '@modules/user/core/application/event-handlers/publish-message-when-user-is-deleted.domain-event-handler';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockMessagePublisher = {
 | 
				
			||||||
 | 
					  publish: jest.fn().mockImplementation(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Publish message when user is deleted domain event handler', () => {
 | 
				
			||||||
 | 
					  let publishMessageWhenUserIsDeletedDomainEventHandler: PublishMessageWhenUserIsDeletedDomainEventHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: USER_MESSAGE_PUBLISHER,
 | 
				
			||||||
 | 
					          useValue: mockMessagePublisher,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        PublishMessageWhenUserIsDeletedDomainEventHandler,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    publishMessageWhenUserIsDeletedDomainEventHandler =
 | 
				
			||||||
 | 
					      module.get<PublishMessageWhenUserIsDeletedDomainEventHandler>(
 | 
				
			||||||
 | 
					        PublishMessageWhenUserIsDeletedDomainEventHandler,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should publish a message', () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockMessagePublisher, 'publish');
 | 
				
			||||||
 | 
					    const userDeletedDomainEvent: UserCreatedDomainEvent = {
 | 
				
			||||||
 | 
					      id: 'some-domain-event-id',
 | 
				
			||||||
 | 
					      aggregateId: 'some-aggregate-id',
 | 
				
			||||||
 | 
					      firstName: 'John',
 | 
				
			||||||
 | 
					      lastName: 'Doe',
 | 
				
			||||||
 | 
					      email: 'john.doe@email.com',
 | 
				
			||||||
 | 
					      phone: '+33611223344',
 | 
				
			||||||
 | 
					      metadata: {
 | 
				
			||||||
 | 
					        timestamp: new Date('2023-06-28T05:00:00Z').getTime(),
 | 
				
			||||||
 | 
					        correlationId: 'some-correlation-id',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    publishMessageWhenUserIsDeletedDomainEventHandler.handle(
 | 
				
			||||||
 | 
					      userDeletedDomainEvent,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    expect(publishMessageWhenUserIsDeletedDomainEventHandler).toBeDefined();
 | 
				
			||||||
 | 
					    expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					    expect(mockMessagePublisher.publish).toHaveBeenCalledWith(
 | 
				
			||||||
 | 
					      'user.deleted',
 | 
				
			||||||
 | 
					      '{"id":"some-aggregate-id","metadata":{"correlationId":"some-correlation-id","timestamp":1687928400000}}',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { PublishMessageWhenUserIsUpdatedDomainEventHandler } from '@modules/user/core/application/event-handlers/publish-message-when-user-is-updated.domain-event-handler';
 | 
				
			||||||
 | 
					import { UserUpdatedDomainEvent } from '@modules/user/core/domain/events/user-updated.domain-event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockMessagePublisher = {
 | 
				
			||||||
 | 
					  publish: jest.fn().mockImplementation(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Publish message when user is updated domain event handler', () => {
 | 
				
			||||||
 | 
					  let publishMessageWhenUserIsUpdatedDomainEventHandler: PublishMessageWhenUserIsUpdatedDomainEventHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: USER_MESSAGE_PUBLISHER,
 | 
				
			||||||
 | 
					          useValue: mockMessagePublisher,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        PublishMessageWhenUserIsUpdatedDomainEventHandler,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    publishMessageWhenUserIsUpdatedDomainEventHandler =
 | 
				
			||||||
 | 
					      module.get<PublishMessageWhenUserIsUpdatedDomainEventHandler>(
 | 
				
			||||||
 | 
					        PublishMessageWhenUserIsUpdatedDomainEventHandler,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should publish a message', () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockMessagePublisher, 'publish');
 | 
				
			||||||
 | 
					    const userUpdatedDomainEvent: UserUpdatedDomainEvent = {
 | 
				
			||||||
 | 
					      id: 'some-domain-event-id',
 | 
				
			||||||
 | 
					      aggregateId: 'some-aggregate-id',
 | 
				
			||||||
 | 
					      firstName: 'Jane',
 | 
				
			||||||
 | 
					      lastName: 'Doe',
 | 
				
			||||||
 | 
					      email: 'jane.doe@email.com',
 | 
				
			||||||
 | 
					      phone: '+33611223344',
 | 
				
			||||||
 | 
					      metadata: {
 | 
				
			||||||
 | 
					        timestamp: new Date('2023-06-28T05:00:00Z').getTime(),
 | 
				
			||||||
 | 
					        correlationId: 'some-correlation-id',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    publishMessageWhenUserIsUpdatedDomainEventHandler.handle(
 | 
				
			||||||
 | 
					      userUpdatedDomainEvent,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    expect(publishMessageWhenUserIsUpdatedDomainEventHandler).toBeDefined();
 | 
				
			||||||
 | 
					    expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					    expect(mockMessagePublisher.publish).toHaveBeenCalledWith(
 | 
				
			||||||
 | 
					      'user.updated',
 | 
				
			||||||
 | 
					      '{"id":"some-aggregate-id","metadata":{"correlationId":"some-correlation-id","timestamp":1687928400000},"firstName":"Jane","lastName":"Doe","email":"jane.doe@email.com","phone":"+33611223344"}',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,126 @@
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  AggregateID,
 | 
				
			||||||
 | 
					  NotFoundException,
 | 
				
			||||||
 | 
					  UniqueConstraintException,
 | 
				
			||||||
 | 
					} from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
 | 
				
			||||||
 | 
					import { UserEntity } from '@modules/user/core/domain/user.entity';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  EmailAlreadyExistsException,
 | 
				
			||||||
 | 
					  PhoneAlreadyExistsException,
 | 
				
			||||||
 | 
					} from '@modules/user/core/domain/user.errors';
 | 
				
			||||||
 | 
					import { UpdateUserRequestDto } from '@modules/user/interface/grpc-controllers/dtos/update-user.request.dto';
 | 
				
			||||||
 | 
					import { UpdateUserService } from '@modules/user/core/application/commands/update-user/update-user.service';
 | 
				
			||||||
 | 
					import { UpdateUserCommand } from '@modules/user/core/application/commands/update-user/update-user.command';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updateFirstNameUserRequest: UpdateUserRequestDto = {
 | 
				
			||||||
 | 
					  id: 'c97b1783-76cf-4840-b298-b90b13c58894',
 | 
				
			||||||
 | 
					  firstName: 'Johnny',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updateEmailUserRequest: UpdateUserRequestDto = {
 | 
				
			||||||
 | 
					  id: 'c97b1783-76cf-4840-b298-b90b13c58894',
 | 
				
			||||||
 | 
					  email: 'john.doe@already.exists.email.com',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updatePhoneUserRequest: UpdateUserRequestDto = {
 | 
				
			||||||
 | 
					  id: 'c97b1783-76cf-4840-b298-b90b13c58894',
 | 
				
			||||||
 | 
					  phone: '+33611223344',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const now = new Date();
 | 
				
			||||||
 | 
					const userToUpdate: UserEntity = new UserEntity({
 | 
				
			||||||
 | 
					  id: 'c97b1783-76cf-4840-b298-b90b13c58894',
 | 
				
			||||||
 | 
					  createdAt: now,
 | 
				
			||||||
 | 
					  updatedAt: now,
 | 
				
			||||||
 | 
					  props: {
 | 
				
			||||||
 | 
					    firstName: 'John',
 | 
				
			||||||
 | 
					    lastName: 'Doe',
 | 
				
			||||||
 | 
					    email: 'john.doe@email.com',
 | 
				
			||||||
 | 
					    phone: '+33611223344',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockUserRepository = {
 | 
				
			||||||
 | 
					  findOneById: jest
 | 
				
			||||||
 | 
					    .fn()
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new NotFoundException('Record not found');
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementation(() => userToUpdate),
 | 
				
			||||||
 | 
					  update: jest
 | 
				
			||||||
 | 
					    .fn()
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => 'c97b1783-76cf-4840-b298-b90b13c58894')
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new UniqueConstraintException('email already exists');
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new UniqueConstraintException('phone already exists');
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new Error();
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('update-user.service', () => {
 | 
				
			||||||
 | 
					  let updateUserService: UpdateUserService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: USER_REPOSITORY,
 | 
				
			||||||
 | 
					          useValue: mockUserRepository,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        UpdateUserService,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updateUserService = module.get<UpdateUserService>(UpdateUserService);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(updateUserService).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('execution', () => {
 | 
				
			||||||
 | 
					    it('should throw an exception if user is not found', async () => {
 | 
				
			||||||
 | 
					      const updateUserCommand = new UpdateUserCommand(
 | 
				
			||||||
 | 
					        updateFirstNameUserRequest,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        updateUserService.execute(updateUserCommand),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(NotFoundException);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should update a user firstName', async () => {
 | 
				
			||||||
 | 
					      jest.spyOn(userToUpdate, 'update');
 | 
				
			||||||
 | 
					      const updateUserCommand = new UpdateUserCommand(
 | 
				
			||||||
 | 
					        updateFirstNameUserRequest,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      const result: AggregateID = await updateUserService.execute(
 | 
				
			||||||
 | 
					        updateUserCommand,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(result).toBe('c97b1783-76cf-4840-b298-b90b13c58894');
 | 
				
			||||||
 | 
					      expect(userToUpdate.update).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should throw an exception if Email already exists', async () => {
 | 
				
			||||||
 | 
					      const updateUserCommand = new UpdateUserCommand(updateEmailUserRequest);
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        updateUserService.execute(updateUserCommand),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(EmailAlreadyExistsException);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should throw an exception if Phone already exists', async () => {
 | 
				
			||||||
 | 
					      const updateUserCommand = new UpdateUserCommand(updatePhoneUserRequest);
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        updateUserService.execute(updateUserCommand),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(PhoneAlreadyExistsException);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should throw an error if something bad happens', async () => {
 | 
				
			||||||
 | 
					      const updateUserCommand = new UpdateUserCommand(updatePhoneUserRequest);
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        updateUserService.execute(updateUserCommand),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(Error);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					import { UserCreatedDomainEvent } from '@modules/user/core/domain/events/user-created.domain-event';
 | 
				
			||||||
 | 
					import { UserDeletedDomainEvent } from '@modules/user/core/domain/events/user-deleted.domain-event';
 | 
				
			||||||
 | 
					import { UserUpdatedDomainEvent } from '@modules/user/core/domain/events/user-updated.domain-event';
 | 
				
			||||||
 | 
					import { UserEntity } from '@modules/user/core/domain/user.entity';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  CreateUserProps,
 | 
				
			||||||
 | 
					  UpdateUserProps,
 | 
				
			||||||
 | 
					} from '@modules/user/core/domain/user.types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const createUserProps: CreateUserProps = {
 | 
				
			||||||
 | 
					  firstName: 'John',
 | 
				
			||||||
 | 
					  lastName: 'Doe',
 | 
				
			||||||
 | 
					  email: 'john.doe@email.com',
 | 
				
			||||||
 | 
					  phone: '+33611223344',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updateUserProps: UpdateUserProps = {
 | 
				
			||||||
 | 
					  firstName: 'Jane',
 | 
				
			||||||
 | 
					  lastName: 'Dane',
 | 
				
			||||||
 | 
					  email: 'jane.dane@email.com',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('User entity create', () => {
 | 
				
			||||||
 | 
					  it('should create a new user entity', async () => {
 | 
				
			||||||
 | 
					    const userEntity: UserEntity = UserEntity.create(createUserProps);
 | 
				
			||||||
 | 
					    expect(userEntity.id.length).toBe(36);
 | 
				
			||||||
 | 
					    expect(userEntity.getProps().email).toBe('john.doe@email.com');
 | 
				
			||||||
 | 
					    expect(userEntity.domainEvents.length).toBe(1);
 | 
				
			||||||
 | 
					    expect(userEntity.domainEvents[0]).toBeInstanceOf(UserCreatedDomainEvent);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('User entity update', () => {
 | 
				
			||||||
 | 
					  it('should update a user entity', async () => {
 | 
				
			||||||
 | 
					    const userEntity: UserEntity = UserEntity.create(createUserProps);
 | 
				
			||||||
 | 
					    userEntity.update(updateUserProps);
 | 
				
			||||||
 | 
					    expect(userEntity.getProps().firstName).toBe('Jane');
 | 
				
			||||||
 | 
					    expect(userEntity.getProps().lastName).toBe('Dane');
 | 
				
			||||||
 | 
					    expect(userEntity.getProps().email).toBe('jane.dane@email.com');
 | 
				
			||||||
 | 
					    // 2 events because UserEntity.create sends a UserCreatedDomainEvent
 | 
				
			||||||
 | 
					    expect(userEntity.domainEvents.length).toBe(2);
 | 
				
			||||||
 | 
					    expect(userEntity.domainEvents[1]).toBeInstanceOf(UserUpdatedDomainEvent);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('User entity delete', () => {
 | 
				
			||||||
 | 
					  it('should delete a user entity', async () => {
 | 
				
			||||||
 | 
					    const userEntity: UserEntity = UserEntity.create(createUserProps);
 | 
				
			||||||
 | 
					    userEntity.delete();
 | 
				
			||||||
 | 
					    // 2 events because UserEntity.create sends a UserCreatedDomainEvent
 | 
				
			||||||
 | 
					    expect(userEntity.domainEvents.length).toBe(2);
 | 
				
			||||||
 | 
					    expect(userEntity.domainEvents[1]).toBeInstanceOf(UserDeletedDomainEvent);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -1,79 +0,0 @@
 | 
				
			||||||
import { classes } from '@automapper/classes';
 | 
					 | 
				
			||||||
import { AutomapperModule } from '@automapper/nestjs';
 | 
					 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					 | 
				
			||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
 | 
					 | 
				
			||||||
import { CreateUserCommand } from '../../commands/create-user.command';
 | 
					 | 
				
			||||||
import { CreateUserRequest } from '../../domain/dtos/create-user.request';
 | 
					 | 
				
			||||||
import { User } from '../../domain/entities/user';
 | 
					 | 
				
			||||||
import { CreateUserUseCase } from '../../domain/usecases/create-user.usecase';
 | 
					 | 
				
			||||||
import { UserProfile } from '../../mappers/user.profile';
 | 
					 | 
				
			||||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const newUserRequest: CreateUserRequest = {
 | 
					 | 
				
			||||||
  firstName: 'John',
 | 
					 | 
				
			||||||
  lastName: 'Doe',
 | 
					 | 
				
			||||||
  email: 'john.doe@email.com',
 | 
					 | 
				
			||||||
  phone: '0601020304',
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
const newUserCommand: CreateUserCommand = new CreateUserCommand(newUserRequest);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockUsersRepository = {
 | 
					 | 
				
			||||||
  create: jest
 | 
					 | 
				
			||||||
    .fn()
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => {
 | 
					 | 
				
			||||||
      return Promise.resolve({
 | 
					 | 
				
			||||||
        ...newUserRequest,
 | 
					 | 
				
			||||||
        uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .mockImplementation(() => {
 | 
					 | 
				
			||||||
      throw new Error('Already exists');
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockMessagePublisher = {
 | 
					 | 
				
			||||||
  publish: jest.fn().mockImplementation(),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('CreateUserUseCase', () => {
 | 
					 | 
				
			||||||
  let createUserUseCase: CreateUserUseCase;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })],
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: UsersRepository,
 | 
					 | 
				
			||||||
          useValue: mockUsersRepository,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        CreateUserUseCase,
 | 
					 | 
				
			||||||
        UserProfile,
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: MESSAGE_PUBLISHER,
 | 
					 | 
				
			||||||
          useValue: mockMessagePublisher,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    createUserUseCase = module.get<CreateUserUseCase>(CreateUserUseCase);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should be defined', () => {
 | 
					 | 
				
			||||||
    expect(createUserUseCase).toBeDefined();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('execute', () => {
 | 
					 | 
				
			||||||
    it('should create and return a new user', async () => {
 | 
					 | 
				
			||||||
      const newUser: User = await createUserUseCase.execute(newUserCommand);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      expect(newUser.lastName).toBe(newUserRequest.lastName);
 | 
					 | 
				
			||||||
      expect(newUser.uuid).toBeDefined();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should throw an error if user already exists', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        createUserUseCase.execute(newUserCommand),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(Error);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,93 +0,0 @@
 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					 | 
				
			||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
 | 
					 | 
				
			||||||
import { DeleteUserCommand } from '../../commands/delete-user.command';
 | 
					 | 
				
			||||||
import { DeleteUserUseCase } from '../../domain/usecases/delete-user.usecase';
 | 
					 | 
				
			||||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockUsers = [
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
 | 
					 | 
				
			||||||
    firstName: 'John',
 | 
					 | 
				
			||||||
    lastName: 'Doe',
 | 
					 | 
				
			||||||
    email: 'john.doe@email.com',
 | 
					 | 
				
			||||||
    phone: '0601020304',
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    uuid: 'bb281075-1b98-4456-89d6-c643d3044a92',
 | 
					 | 
				
			||||||
    firstName: 'Jane',
 | 
					 | 
				
			||||||
    lastName: 'Doe',
 | 
					 | 
				
			||||||
    email: 'jane.doe@email.com',
 | 
					 | 
				
			||||||
    phone: '0602030405',
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    uuid: 'bb281075-1b98-4456-89d6-c643d3044a93',
 | 
					 | 
				
			||||||
    firstName: 'Jimmy',
 | 
					 | 
				
			||||||
    lastName: 'Doe',
 | 
					 | 
				
			||||||
    email: 'jimmy.doe@email.com',
 | 
					 | 
				
			||||||
    phone: '0603040506',
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockUsersRepository = {
 | 
					 | 
				
			||||||
  delete: jest
 | 
					 | 
				
			||||||
    .fn()
 | 
					 | 
				
			||||||
    .mockImplementationOnce((uuid: string) => {
 | 
					 | 
				
			||||||
      let savedUser = {};
 | 
					 | 
				
			||||||
      mockUsers.forEach((user, index) => {
 | 
					 | 
				
			||||||
        if (user.uuid === uuid) {
 | 
					 | 
				
			||||||
          savedUser = { ...user };
 | 
					 | 
				
			||||||
          mockUsers.splice(index, 1);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      return savedUser;
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .mockImplementation(() => {
 | 
					 | 
				
			||||||
      throw new Error('Error');
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockMessagePublisher = {
 | 
					 | 
				
			||||||
  publish: jest.fn().mockImplementation(),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('DeleteUserUseCase', () => {
 | 
					 | 
				
			||||||
  let deleteUserUseCase: DeleteUserUseCase;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: UsersRepository,
 | 
					 | 
				
			||||||
          useValue: mockUsersRepository,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        DeleteUserUseCase,
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: MESSAGE_PUBLISHER,
 | 
					 | 
				
			||||||
          useValue: mockMessagePublisher,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    deleteUserUseCase = module.get<DeleteUserUseCase>(DeleteUserUseCase);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should be defined', () => {
 | 
					 | 
				
			||||||
    expect(deleteUserUseCase).toBeDefined();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('execute', () => {
 | 
					 | 
				
			||||||
    it('should delete a user', async () => {
 | 
					 | 
				
			||||||
      const savedUuid = mockUsers[0].uuid;
 | 
					 | 
				
			||||||
      const deleteUserCommand = new DeleteUserCommand(savedUuid);
 | 
					 | 
				
			||||||
      await deleteUserUseCase.execute(deleteUserCommand);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const deletedUser = mockUsers.find((user) => user.uuid === savedUuid);
 | 
					 | 
				
			||||||
      expect(deletedUser).toBeUndefined();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    it('should throw an error if user does not exist', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        deleteUserUseCase.execute(new DeleteUserCommand('wrong uuid')),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(Error);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,74 +0,0 @@
 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					 | 
				
			||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
 | 
					 | 
				
			||||||
import { FindAllUsersRequest } from '../../domain/dtos/find-all-users.request';
 | 
					 | 
				
			||||||
import { FindAllUsersUseCase } from '../../domain/usecases/find-all-users.usecase';
 | 
					 | 
				
			||||||
import { FindAllUsersQuery } from '../../queries/find-all-users.query';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const findAllUsersRequest: FindAllUsersRequest = new FindAllUsersRequest();
 | 
					 | 
				
			||||||
findAllUsersRequest.page = 1;
 | 
					 | 
				
			||||||
findAllUsersRequest.perPage = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const findAllUsersQuery: FindAllUsersQuery = new FindAllUsersQuery(
 | 
					 | 
				
			||||||
  findAllUsersRequest,
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockUsers = [
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
 | 
					 | 
				
			||||||
    firstName: 'John',
 | 
					 | 
				
			||||||
    lastName: 'Doe',
 | 
					 | 
				
			||||||
    email: 'john.doe@email.com',
 | 
					 | 
				
			||||||
    phone: '0601020304',
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    uuid: 'bb281075-1b98-4456-89d6-c643d3044a92',
 | 
					 | 
				
			||||||
    firstName: 'Jane',
 | 
					 | 
				
			||||||
    lastName: 'Doe',
 | 
					 | 
				
			||||||
    email: 'jane.doe@email.com',
 | 
					 | 
				
			||||||
    phone: '0602030405',
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    uuid: 'bb281075-1b98-4456-89d6-c643d3044a93',
 | 
					 | 
				
			||||||
    firstName: 'Jimmy',
 | 
					 | 
				
			||||||
    lastName: 'Doe',
 | 
					 | 
				
			||||||
    email: 'jimmy.doe@email.com',
 | 
					 | 
				
			||||||
    phone: '0603040506',
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockUsersRepository = {
 | 
					 | 
				
			||||||
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
  findAll: jest.fn().mockImplementation((query?: FindAllUsersQuery) => {
 | 
					 | 
				
			||||||
    return Promise.resolve(mockUsers);
 | 
					 | 
				
			||||||
  }),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('FindAllUsersUseCase', () => {
 | 
					 | 
				
			||||||
  let findAllUsersUseCase: FindAllUsersUseCase;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: UsersRepository,
 | 
					 | 
				
			||||||
          useValue: mockUsersRepository,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        FindAllUsersUseCase,
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    findAllUsersUseCase = module.get<FindAllUsersUseCase>(FindAllUsersUseCase);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should be defined', () => {
 | 
					 | 
				
			||||||
    expect(findAllUsersUseCase).toBeDefined();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('execute', () => {
 | 
					 | 
				
			||||||
    it('should return an array filled with users', async () => {
 | 
					 | 
				
			||||||
      const users = await findAllUsersUseCase.execute(findAllUsersQuery);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      expect(users).toBe(mockUsers);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,82 +0,0 @@
 | 
				
			||||||
import { NotFoundException } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					 | 
				
			||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
 | 
					 | 
				
			||||||
import { FindUserByUuidRequest } from '../../domain/dtos/find-user-by-uuid.request';
 | 
					 | 
				
			||||||
import { FindUserByUuidUseCase } from '../../domain/usecases/find-user-by-uuid.usecase';
 | 
					 | 
				
			||||||
import { FindUserByUuidQuery } from '../../queries/find-user-by-uuid.query';
 | 
					 | 
				
			||||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockUser = {
 | 
					 | 
				
			||||||
  uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
 | 
					 | 
				
			||||||
  firstName: 'John',
 | 
					 | 
				
			||||||
  lastName: 'Doe',
 | 
					 | 
				
			||||||
  email: 'john.doe@email.com',
 | 
					 | 
				
			||||||
  phone: '0601020304',
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockUserRepository = {
 | 
					 | 
				
			||||||
  findOneByUuid: jest
 | 
					 | 
				
			||||||
    .fn()
 | 
					 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
    .mockImplementationOnce((query?: FindUserByUuidQuery) => {
 | 
					 | 
				
			||||||
      return Promise.resolve(mockUser);
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .mockImplementation(() => {
 | 
					 | 
				
			||||||
      return Promise.resolve(undefined);
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockMessagePublisher = {
 | 
					 | 
				
			||||||
  publish: jest.fn().mockImplementation(),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('FindUserByUuidUseCase', () => {
 | 
					 | 
				
			||||||
  let findUserByUuidUseCase: FindUserByUuidUseCase;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      imports: [],
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: UsersRepository,
 | 
					 | 
				
			||||||
          useValue: mockUserRepository,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: MESSAGE_PUBLISHER,
 | 
					 | 
				
			||||||
          useValue: mockMessagePublisher,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        FindUserByUuidUseCase,
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    findUserByUuidUseCase = module.get<FindUserByUuidUseCase>(
 | 
					 | 
				
			||||||
      FindUserByUuidUseCase,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should be defined', () => {
 | 
					 | 
				
			||||||
    expect(findUserByUuidUseCase).toBeDefined();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('execute', () => {
 | 
					 | 
				
			||||||
    it('should return a user', async () => {
 | 
					 | 
				
			||||||
      const findUserByUuidRequest: FindUserByUuidRequest =
 | 
					 | 
				
			||||||
        new FindUserByUuidRequest();
 | 
					 | 
				
			||||||
      findUserByUuidRequest.uuid = 'bb281075-1b98-4456-89d6-c643d3044a91';
 | 
					 | 
				
			||||||
      const user = await findUserByUuidUseCase.execute(
 | 
					 | 
				
			||||||
        new FindUserByUuidQuery(findUserByUuidRequest),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      expect(user).toBe(mockUser);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    it('should throw an error if user does not exist', async () => {
 | 
					 | 
				
			||||||
      const findUserByUuidRequest: FindUserByUuidRequest =
 | 
					 | 
				
			||||||
        new FindUserByUuidRequest();
 | 
					 | 
				
			||||||
      findUserByUuidRequest.uuid = 'bb281075-1b98-4456-89d6-c643d3044a90';
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        findUserByUuidUseCase.execute(
 | 
					 | 
				
			||||||
          new FindUserByUuidQuery(findUserByUuidRequest),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(NotFoundException);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					import { PrismaService } from '@modules/user/infrastructure/prisma.service';
 | 
				
			||||||
 | 
					import { UserRepository } from '@modules/user/infrastructure/user.repository';
 | 
				
			||||||
 | 
					import { UserMapper } from '@modules/user/user.mapper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockMessagePublisher = {
 | 
				
			||||||
 | 
					  publish: jest.fn().mockImplementation(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('User repository', () => {
 | 
				
			||||||
 | 
					  let prismaService: PrismaService;
 | 
				
			||||||
 | 
					  let userMapper: UserMapper;
 | 
				
			||||||
 | 
					  let eventEmitter: EventEmitter2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      imports: [EventEmitterModule.forRoot()],
 | 
				
			||||||
 | 
					      providers: [PrismaService, UserMapper],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    prismaService = module.get<PrismaService>(PrismaService);
 | 
				
			||||||
 | 
					    userMapper = module.get<UserMapper>(UserMapper);
 | 
				
			||||||
 | 
					    eventEmitter = module.get<EventEmitter2>(EventEmitter2);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      new UserRepository(
 | 
				
			||||||
 | 
					        prismaService,
 | 
				
			||||||
 | 
					        userMapper,
 | 
				
			||||||
 | 
					        eventEmitter,
 | 
				
			||||||
 | 
					        mockMessagePublisher,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    ).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,123 @@
 | 
				
			||||||
 | 
					import { IdResponse } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { CommandBus } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { RpcException } from '@nestjs/microservices';
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  EmailAlreadyExistsException,
 | 
				
			||||||
 | 
					  PhoneAlreadyExistsException,
 | 
				
			||||||
 | 
					  UserAlreadyExistsException,
 | 
				
			||||||
 | 
					} from '@modules/user/core/domain/user.errors';
 | 
				
			||||||
 | 
					import { CreateUserGrpcController } from '@modules/user/interface/grpc-controllers/create-user.grpc.controller';
 | 
				
			||||||
 | 
					import { CreateUserRequestDto } from '@modules/user/interface/grpc-controllers/dtos/create-user.request.dto';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const createUserRequest: CreateUserRequestDto = {
 | 
				
			||||||
 | 
					  firstName: 'John',
 | 
				
			||||||
 | 
					  lastName: 'Doe',
 | 
				
			||||||
 | 
					  email: 'john.doe@email.com',
 | 
				
			||||||
 | 
					  phone: '+33611223344',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockCommandBus = {
 | 
				
			||||||
 | 
					  execute: jest
 | 
				
			||||||
 | 
					    .fn()
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => '200d61a8-d878-4378-a609-c19ea71633d2')
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new UserAlreadyExistsException();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new EmailAlreadyExistsException();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new PhoneAlreadyExistsException();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new Error();
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Create User Grpc Controller', () => {
 | 
				
			||||||
 | 
					  let createUserGrpcController: CreateUserGrpcController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: CommandBus,
 | 
				
			||||||
 | 
					          useValue: mockCommandBus,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        CreateUserGrpcController,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    createUserGrpcController = module.get<CreateUserGrpcController>(
 | 
				
			||||||
 | 
					      CreateUserGrpcController,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterEach(async () => {
 | 
				
			||||||
 | 
					    jest.clearAllMocks();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(createUserGrpcController).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should create a new user', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockCommandBus, 'execute');
 | 
				
			||||||
 | 
					    const result: IdResponse = await createUserGrpcController.create(
 | 
				
			||||||
 | 
					      createUserRequest,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    expect(result).toBeInstanceOf(IdResponse);
 | 
				
			||||||
 | 
					    expect(result.id).toBe('200d61a8-d878-4378-a609-c19ea71633d2');
 | 
				
			||||||
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should throw a dedicated RpcException if user already exists', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockCommandBus, 'execute');
 | 
				
			||||||
 | 
					    expect.assertions(3);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await createUserGrpcController.create(createUserRequest);
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(RpcException);
 | 
				
			||||||
 | 
					      expect(e.error.code).toBe(RpcExceptionCode.ALREADY_EXISTS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should throw a dedicated RpcException if email already exists', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockCommandBus, 'execute');
 | 
				
			||||||
 | 
					    expect.assertions(3);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await createUserGrpcController.create(createUserRequest);
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(RpcException);
 | 
				
			||||||
 | 
					      expect(e.error.code).toBe(RpcExceptionCode.ALREADY_EXISTS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should throw a dedicated RpcException if phone already exists', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockCommandBus, 'execute');
 | 
				
			||||||
 | 
					    expect.assertions(3);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await createUserGrpcController.create(createUserRequest);
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(RpcException);
 | 
				
			||||||
 | 
					      expect(e.error.code).toBe(RpcExceptionCode.ALREADY_EXISTS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should throw a generic RpcException', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockCommandBus, 'execute');
 | 
				
			||||||
 | 
					    expect.assertions(3);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await createUserGrpcController.create(createUserRequest);
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(RpcException);
 | 
				
			||||||
 | 
					      expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,99 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  DatabaseErrorException,
 | 
				
			||||||
 | 
					  NotFoundException,
 | 
				
			||||||
 | 
					} from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { DeleteUserGrpcController } from '@modules/user/interface/grpc-controllers/delete-user.grpc.controller';
 | 
				
			||||||
 | 
					import { DeleteUserRequestDto } from '@modules/user/interface/grpc-controllers/dtos/delete-user.request.dto';
 | 
				
			||||||
 | 
					import { CommandBus } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { RpcException } from '@nestjs/microservices';
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const deleteUserRequest: DeleteUserRequestDto = {
 | 
				
			||||||
 | 
					  id: '78153e03-4861-4f58-a705-88526efee53b',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockCommandBus = {
 | 
				
			||||||
 | 
					  execute: jest
 | 
				
			||||||
 | 
					    .fn()
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => ({}))
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new NotFoundException();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new DatabaseErrorException();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new Error();
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Delete User Grpc Controller', () => {
 | 
				
			||||||
 | 
					  let deleteUserGrpcController: DeleteUserGrpcController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: CommandBus,
 | 
				
			||||||
 | 
					          useValue: mockCommandBus,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        DeleteUserGrpcController,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    deleteUserGrpcController = module.get<DeleteUserGrpcController>(
 | 
				
			||||||
 | 
					      DeleteUserGrpcController,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterEach(async () => {
 | 
				
			||||||
 | 
					    jest.clearAllMocks();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(deleteUserGrpcController).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should delete a user', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockCommandBus, 'execute');
 | 
				
			||||||
 | 
					    await deleteUserGrpcController.delete(deleteUserRequest);
 | 
				
			||||||
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should throw a dedicated RpcException if user does not exist', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockCommandBus, 'execute');
 | 
				
			||||||
 | 
					    expect.assertions(3);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await deleteUserGrpcController.delete(deleteUserRequest);
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(RpcException);
 | 
				
			||||||
 | 
					      expect(e.error.code).toBe(RpcExceptionCode.NOT_FOUND);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should throw a dedicated RpcException if a database error occurs', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockCommandBus, 'execute');
 | 
				
			||||||
 | 
					    expect.assertions(3);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await deleteUserGrpcController.delete(deleteUserRequest);
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(RpcException);
 | 
				
			||||||
 | 
					      expect(e.error.code).toBe(RpcExceptionCode.INTERNAL);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should throw a generic RpcException', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockCommandBus, 'execute');
 | 
				
			||||||
 | 
					    expect.assertions(3);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await deleteUserGrpcController.delete(deleteUserRequest);
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(RpcException);
 | 
				
			||||||
 | 
					      expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,103 @@
 | 
				
			||||||
 | 
					import { NotFoundException } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { FindUserByIdGrpcController } from '@modules/user/interface/grpc-controllers/find-user-by-id.grpc.controller';
 | 
				
			||||||
 | 
					import { UserMapper } from '@modules/user/user.mapper';
 | 
				
			||||||
 | 
					import { QueryBus } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { RpcException } from '@nestjs/microservices';
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockQueryBus = {
 | 
				
			||||||
 | 
					  execute: jest
 | 
				
			||||||
 | 
					    .fn()
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => '200d61a8-d878-4378-a609-c19ea71633d2')
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new NotFoundException();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new Error();
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockUserMapper = {
 | 
				
			||||||
 | 
					  toResponse: jest.fn().mockImplementationOnce(() => ({
 | 
				
			||||||
 | 
					    firstName: 'John',
 | 
				
			||||||
 | 
					    lastName: 'Doe',
 | 
				
			||||||
 | 
					    email: 'john.doe@email.com',
 | 
				
			||||||
 | 
					    phone: '+33611223344',
 | 
				
			||||||
 | 
					  })),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Find User By Id Grpc Controller', () => {
 | 
				
			||||||
 | 
					  let findUserbyIdGrpcController: FindUserByIdGrpcController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: QueryBus,
 | 
				
			||||||
 | 
					          useValue: mockQueryBus,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: UserMapper,
 | 
				
			||||||
 | 
					          useValue: mockUserMapper,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        FindUserByIdGrpcController,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    findUserbyIdGrpcController = module.get<FindUserByIdGrpcController>(
 | 
				
			||||||
 | 
					      FindUserByIdGrpcController,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterEach(async () => {
 | 
				
			||||||
 | 
					    jest.clearAllMocks();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(findUserbyIdGrpcController).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should return a user', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockQueryBus, 'execute');
 | 
				
			||||||
 | 
					    jest.spyOn(mockUserMapper, 'toResponse');
 | 
				
			||||||
 | 
					    const response = await findUserbyIdGrpcController.findOnebyId({
 | 
				
			||||||
 | 
					      id: '6dcf093c-c7db-4dae-8e9c-c715cebf83c7',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(response.firstName).toBe('John');
 | 
				
			||||||
 | 
					    expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					    expect(mockUserMapper.toResponse).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should throw a dedicated RpcException if user is not found', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockQueryBus, 'execute');
 | 
				
			||||||
 | 
					    jest.spyOn(mockUserMapper, 'toResponse');
 | 
				
			||||||
 | 
					    expect.assertions(4);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await findUserbyIdGrpcController.findOnebyId({
 | 
				
			||||||
 | 
					        id: 'ac85f5f4-41cd-4c5d-9aee-0a1acb176fb8',
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(RpcException);
 | 
				
			||||||
 | 
					      expect(e.error.code).toBe(RpcExceptionCode.NOT_FOUND);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					    expect(mockUserMapper.toResponse).toHaveBeenCalledTimes(0);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should throw a generic RpcException', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockQueryBus, 'execute');
 | 
				
			||||||
 | 
					    jest.spyOn(mockUserMapper, 'toResponse');
 | 
				
			||||||
 | 
					    expect.assertions(4);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await findUserbyIdGrpcController.findOnebyId({
 | 
				
			||||||
 | 
					        id: '53c8e7ec-ef68-42bc-ba4c-5ef3effa60a6',
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(RpcException);
 | 
				
			||||||
 | 
					      expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					    expect(mockUserMapper.toResponse).toHaveBeenCalledTimes(0);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,116 @@
 | 
				
			||||||
 | 
					import { IdResponse } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  EmailAlreadyExistsException,
 | 
				
			||||||
 | 
					  PhoneAlreadyExistsException,
 | 
				
			||||||
 | 
					} from '@modules/user/core/domain/user.errors';
 | 
				
			||||||
 | 
					import { UpdateUserRequestDto } from '@modules/user/interface/grpc-controllers/dtos/update-user.request.dto';
 | 
				
			||||||
 | 
					import { UpdateUserGrpcController } from '@modules/user/interface/grpc-controllers/update-user.grpc.controller';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { CommandBus } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { RpcException } from '@nestjs/microservices';
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updateFirstNameUserRequest: UpdateUserRequestDto = {
 | 
				
			||||||
 | 
					  id: 'c97b1783-76cf-4840-b298-b90b13c58894',
 | 
				
			||||||
 | 
					  firstName: 'Johnny',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updateEmailUserRequest: UpdateUserRequestDto = {
 | 
				
			||||||
 | 
					  id: 'c97b1783-76cf-4840-b298-b90b13c58894',
 | 
				
			||||||
 | 
					  email: 'john.doe@already.exists.email.com',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updatePhoneUserRequest: UpdateUserRequestDto = {
 | 
				
			||||||
 | 
					  id: 'c97b1783-76cf-4840-b298-b90b13c58894',
 | 
				
			||||||
 | 
					  phone: '+33611223344',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockCommandBus = {
 | 
				
			||||||
 | 
					  execute: jest
 | 
				
			||||||
 | 
					    .fn()
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => 'c97b1783-76cf-4840-b298-b90b13c58894')
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new EmailAlreadyExistsException();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new PhoneAlreadyExistsException();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new Error();
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Update User Grpc Controller', () => {
 | 
				
			||||||
 | 
					  let updateUserGrpcController: UpdateUserGrpcController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: CommandBus,
 | 
				
			||||||
 | 
					          useValue: mockCommandBus,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        UpdateUserGrpcController,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updateUserGrpcController = module.get<UpdateUserGrpcController>(
 | 
				
			||||||
 | 
					      UpdateUserGrpcController,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterEach(async () => {
 | 
				
			||||||
 | 
					    jest.clearAllMocks();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(updateUserGrpcController).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should update a user', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockCommandBus, 'execute');
 | 
				
			||||||
 | 
					    const result: IdResponse = await updateUserGrpcController.updateUser(
 | 
				
			||||||
 | 
					      updateFirstNameUserRequest,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    expect(result).toBeInstanceOf(IdResponse);
 | 
				
			||||||
 | 
					    expect(result.id).toBe('c97b1783-76cf-4840-b298-b90b13c58894');
 | 
				
			||||||
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should throw a dedicated RpcException if email already exists', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockCommandBus, 'execute');
 | 
				
			||||||
 | 
					    expect.assertions(3);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await updateUserGrpcController.updateUser(updateEmailUserRequest);
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(RpcException);
 | 
				
			||||||
 | 
					      expect(e.error.code).toBe(RpcExceptionCode.ALREADY_EXISTS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should throw a dedicated RpcException if phone already exists', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockCommandBus, 'execute');
 | 
				
			||||||
 | 
					    expect.assertions(3);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await updateUserGrpcController.updateUser(updatePhoneUserRequest);
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(RpcException);
 | 
				
			||||||
 | 
					      expect(e.error.code).toBe(RpcExceptionCode.ALREADY_EXISTS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should throw a generic RpcException', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockCommandBus, 'execute');
 | 
				
			||||||
 | 
					    expect.assertions(3);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await updateUserGrpcController.updateUser(updateFirstNameUserRequest);
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      expect(e).toBeInstanceOf(RpcException);
 | 
				
			||||||
 | 
					      expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -1,36 +0,0 @@
 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					 | 
				
			||||||
import { MessagePublisher } from '../../adapters/secondaries/message-publisher';
 | 
					 | 
				
			||||||
import { MESSAGE_BROKER_PUBLISHER } from '../../../../app.constants';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockMessageBrokerPublisher = {
 | 
					 | 
				
			||||||
  publish: jest.fn().mockImplementation(),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('Messager', () => {
 | 
					 | 
				
			||||||
  let messagePublisher: MessagePublisher;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      imports: [],
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        MessagePublisher,
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: MESSAGE_BROKER_PUBLISHER,
 | 
					 | 
				
			||||||
          useValue: mockMessageBrokerPublisher,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    messagePublisher = module.get<MessagePublisher>(MessagePublisher);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should be defined', () => {
 | 
					 | 
				
			||||||
    expect(messagePublisher).toBeDefined();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should publish a message', async () => {
 | 
					 | 
				
			||||||
    jest.spyOn(mockMessageBrokerPublisher, 'publish');
 | 
					 | 
				
			||||||
    messagePublisher.publish('user.create.info', 'my-test');
 | 
					 | 
				
			||||||
    expect(mockMessageBrokerPublisher.publish).toHaveBeenCalledTimes(1);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,84 +0,0 @@
 | 
				
			||||||
import { classes } from '@automapper/classes';
 | 
					 | 
				
			||||||
import { AutomapperModule } from '@automapper/nestjs';
 | 
					 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					 | 
				
			||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
 | 
					 | 
				
			||||||
import { UpdateUserCommand } from '../../commands/update-user.command';
 | 
					 | 
				
			||||||
import { UpdateUserRequest } from '../../domain/dtos/update-user.request';
 | 
					 | 
				
			||||||
import { User } from '../../domain/entities/user';
 | 
					 | 
				
			||||||
import { UpdateUserUseCase } from '../../domain/usecases/update-user.usecase';
 | 
					 | 
				
			||||||
import { UserProfile } from '../../mappers/user.profile';
 | 
					 | 
				
			||||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const originalUser: User = new User();
 | 
					 | 
				
			||||||
originalUser.uuid = 'bb281075-1b98-4456-89d6-c643d3044a91';
 | 
					 | 
				
			||||||
originalUser.lastName = 'Doe';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const updateUserRequest: UpdateUserRequest = {
 | 
					 | 
				
			||||||
  uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
 | 
					 | 
				
			||||||
  lastName: 'Dane',
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const updateUserCommand: UpdateUserCommand = new UpdateUserCommand(
 | 
					 | 
				
			||||||
  updateUserRequest,
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockUsersRepository = {
 | 
					 | 
				
			||||||
  update: jest
 | 
					 | 
				
			||||||
    .fn()
 | 
					 | 
				
			||||||
    .mockImplementationOnce((uuid: string, params: any) => {
 | 
					 | 
				
			||||||
      originalUser.lastName = params.lastName;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return Promise.resolve(originalUser);
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .mockImplementation(() => {
 | 
					 | 
				
			||||||
      throw new Error('Error');
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockMessager = {
 | 
					 | 
				
			||||||
  publish: jest.fn().mockImplementation(),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('UpdateUserUseCase', () => {
 | 
					 | 
				
			||||||
  let updateUserUseCase: UpdateUserUseCase;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: UsersRepository,
 | 
					 | 
				
			||||||
          useValue: mockUsersRepository,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        UpdateUserUseCase,
 | 
					 | 
				
			||||||
        UserProfile,
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: MESSAGE_PUBLISHER,
 | 
					 | 
				
			||||||
          useValue: mockMessager,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    updateUserUseCase = module.get<UpdateUserUseCase>(UpdateUserUseCase);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should be defined', () => {
 | 
					 | 
				
			||||||
    expect(updateUserUseCase).toBeDefined();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('execute', () => {
 | 
					 | 
				
			||||||
    it('should update a user', async () => {
 | 
					 | 
				
			||||||
      const updatedUser: User = await updateUserUseCase.execute(
 | 
					 | 
				
			||||||
        updateUserCommand,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      expect(updatedUser.lastName).toBe(updateUserRequest.lastName);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    it('should throw an error if user does not exist', async () => {
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        updateUserUseCase.execute(updateUserCommand),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(Error);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,60 @@
 | 
				
			||||||
 | 
					import { UserEntity } from '@modules/user/core/domain/user.entity';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  UserReadModel,
 | 
				
			||||||
 | 
					  UserWriteModel,
 | 
				
			||||||
 | 
					} from '@modules/user/infrastructure/user.repository';
 | 
				
			||||||
 | 
					import { UserResponseDto } from '@modules/user/interface/dtos/user.response.dto';
 | 
				
			||||||
 | 
					import { UserMapper } from '@modules/user/user.mapper';
 | 
				
			||||||
 | 
					import { Test } from '@nestjs/testing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const now = new Date('2023-06-21 06:00:00');
 | 
				
			||||||
 | 
					const userEntity: UserEntity = new UserEntity({
 | 
				
			||||||
 | 
					  id: 'c160cf8c-f057-4962-841f-3ad68346df44',
 | 
				
			||||||
 | 
					  props: {
 | 
				
			||||||
 | 
					    firstName: 'John',
 | 
				
			||||||
 | 
					    lastName: 'Doe',
 | 
				
			||||||
 | 
					    email: 'john.doe@email.com',
 | 
				
			||||||
 | 
					    phone: '+33611223344',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  createdAt: now,
 | 
				
			||||||
 | 
					  updatedAt: now,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					const userReadModel: UserReadModel = {
 | 
				
			||||||
 | 
					  uuid: 'c160cf8c-f057-4962-841f-3ad68346df44',
 | 
				
			||||||
 | 
					  firstName: 'John',
 | 
				
			||||||
 | 
					  lastName: 'Doe',
 | 
				
			||||||
 | 
					  email: 'john.doe@email.com',
 | 
				
			||||||
 | 
					  phone: '+33611223344',
 | 
				
			||||||
 | 
					  createdAt: now,
 | 
				
			||||||
 | 
					  updatedAt: now,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('User Mapper', () => {
 | 
				
			||||||
 | 
					  let userMapper: UserMapper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [UserMapper],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					    userMapper = module.get<UserMapper>(UserMapper);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(userMapper).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should map domain entity to persistence data', async () => {
 | 
				
			||||||
 | 
					    const mapped: UserWriteModel = userMapper.toPersistence(userEntity);
 | 
				
			||||||
 | 
					    expect(mapped.lastName).toBe('Doe');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should map persisted data to domain entity', async () => {
 | 
				
			||||||
 | 
					    const mapped: UserEntity = userMapper.toDomain(userReadModel);
 | 
				
			||||||
 | 
					    expect(mapped.getProps().firstName).toBe('John');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should map domain entity to response', async () => {
 | 
				
			||||||
 | 
					    const mapped: UserResponseDto = userMapper.toResponse(userEntity);
 | 
				
			||||||
 | 
					    expect(mapped.id).toBe('c160cf8c-f057-4962-841f-3ad68346df44');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					export const USER_CREATED_ROUTING_KEY = 'user.created';
 | 
				
			||||||
 | 
					export const USER_UPDATED_ROUTING_KEY = 'user.updated';
 | 
				
			||||||
 | 
					export const USER_DELETED_ROUTING_KEY = 'user.deleted';
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue