Merge branch 'refactorToBetterHexagon' into 'main'
Refactor to better hexagon See merge request v3/service/ad!10
This commit is contained in:
		
						commit
						c03edba904
					
				| 
						 | 
					@ -22,9 +22,12 @@ DEPARTURE_MARGIN=900
 | 
				
			||||||
# DEFAULT ROLE
 | 
					# DEFAULT ROLE
 | 
				
			||||||
ROLE=passenger
 | 
					ROLE=passenger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# SEATS PROVIDED AS DRIVER / REQUESTED AS PASSENGER
 | 
					# SEATS PROPOSED AS DRIVER / REQUESTED AS PASSENGER
 | 
				
			||||||
SEATS_PROVIDED=3
 | 
					SEATS_PROPOSED=3
 | 
				
			||||||
SEATS_REQUESTED=1
 | 
					SEATS_REQUESTED=1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# ACCEPT ONLY SAME FREQUENCY REQUESTS
 | 
					# ACCEPT ONLY SAME FREQUENCY REQUESTS
 | 
				
			||||||
STRICT_FREQUENCY=false
 | 
					STRICT_FREQUENCY=false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# default timezone
 | 
				
			||||||
 | 
					DEFAULT_TIMEZONE=Europe/Paris
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										65
									
								
								README.md
								
								
								
								
							
							
						
						
									
										65
									
								
								README.md
								
								
								
								
							| 
						 | 
					@ -48,30 +48,34 @@ 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 an ad by its uuid
 | 
					-   **FindById** : find an ad by its id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ```json
 | 
					    ```json
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        "uuid": "80126a61-d128-4f96-afdb-92e33c75a3e1"
 | 
					        "id": "80126a61-d128-4f96-afdb-92e33c75a3e1"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    ```
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-   **Create** : create an ad (note that uuid is optional, a uuid will be automatically attributed if it is not provided)
 | 
					-   **Create** : create an ad (note that id is optional, an id (as a uuid) will be automatically attributed if it is not provided)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Punctual driver ad :
 | 
					    Punctual driver ad :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ```json
 | 
					    ```json
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        "userUuid": "80c9bb02-0931-4a1d-bea6-22d358992245",
 | 
					        "userId": "80c9bb02-0931-4a1d-bea6-22d358992245",
 | 
				
			||||||
        "driver": true,
 | 
					        "driver": true,
 | 
				
			||||||
        "seatsDriver": 3,
 | 
					        "seatsProposed": 3,
 | 
				
			||||||
        "frequency": "PUNCTUAL",
 | 
					        "frequency": "PUNCTUAL",
 | 
				
			||||||
        "departureDateTime": "2023-01-15 09:00",
 | 
					        "fromDate": "2023-01-15",
 | 
				
			||||||
        "addresses": [
 | 
					        "toDate": "2023-01-15",
 | 
				
			||||||
 | 
					        "schedule": {
 | 
				
			||||||
 | 
					            "thu": "09:00"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "waypoints": [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                "position": 0,
 | 
					                "position": 0,
 | 
				
			||||||
                "lon": 48.68944505415954,
 | 
					                "lon": 48.689445,
 | 
				
			||||||
                "lat": 6.176510296462267,
 | 
					                "lat": 6.17651,
 | 
				
			||||||
                "houseNumber": "5",
 | 
					                "houseNumber": "5",
 | 
				
			||||||
                "street": "Avenue Foch",
 | 
					                "street": "Avenue Foch",
 | 
				
			||||||
                "locality": "Nancy",
 | 
					                "locality": "Nancy",
 | 
				
			||||||
| 
						 | 
					@ -94,18 +98,22 @@ The app exposes the following [gRPC](https://grpc.io/) services :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ```json
 | 
					    ```json
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        "userUuid": "80c9bb02-0931-4a1d-bea6-22d358992245",
 | 
					        "userId": "80c9bb02-0931-4a1d-bea6-22d358992245",
 | 
				
			||||||
        "driver": true,
 | 
					        "driver": true,
 | 
				
			||||||
        "pasenger": true,
 | 
					        "pasenger": true,
 | 
				
			||||||
        "seatsDriver": 3,
 | 
					        "seatsProposed": 3,
 | 
				
			||||||
        "seatsPassenger": 1,
 | 
					        "seatsRequested": 1,
 | 
				
			||||||
        "frequency": "PUNCTUAL",
 | 
					        "frequency": "PUNCTUAL",
 | 
				
			||||||
        "departureDateTime": "2023-01-15 09:00",
 | 
					        "fromDate": "2023-01-15",
 | 
				
			||||||
        "addresses": [
 | 
					        "toDate": "2023-01-15",
 | 
				
			||||||
 | 
					        "schedule": {
 | 
				
			||||||
 | 
					            "thu": "09:00"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "waypoints": [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                "position": 0,
 | 
					                "position": 0,
 | 
				
			||||||
                "lon": 48.68944505415954,
 | 
					                "lon": 48.689445,
 | 
				
			||||||
                "lat": 6.176510296462267,
 | 
					                "lat": 6.17651,
 | 
				
			||||||
                "houseNumber": "5",
 | 
					                "houseNumber": "5",
 | 
				
			||||||
                "street": "Avenue Foch",
 | 
					                "street": "Avenue Foch",
 | 
				
			||||||
                "locality": "Nancy",
 | 
					                "locality": "Nancy",
 | 
				
			||||||
| 
						 | 
					@ -128,7 +136,7 @@ The app exposes the following [gRPC](https://grpc.io/) services :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ```json
 | 
					    ```json
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        "userUuid": "80c9bb02-0931-4a1d-bea6-22d358992245",
 | 
					        "userId": "80c9bb02-0931-4a1d-bea6-22d358992245",
 | 
				
			||||||
        "passenger": true,
 | 
					        "passenger": true,
 | 
				
			||||||
        "seatsPassenger": 1,
 | 
					        "seatsPassenger": 1,
 | 
				
			||||||
        "frequency": "RECURRRENT",
 | 
					        "frequency": "RECURRRENT",
 | 
				
			||||||
| 
						 | 
					@ -139,11 +147,11 @@ The app exposes the following [gRPC](https://grpc.io/) services :
 | 
				
			||||||
            "tue": "07:05",
 | 
					            "tue": "07:05",
 | 
				
			||||||
            "fri": "07:10"
 | 
					            "fri": "07:10"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "addresses": [
 | 
					        "waypoints": [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                "position": 0,
 | 
					                "position": 0,
 | 
				
			||||||
                "lon": 48.68944505415954,
 | 
					                "lon": 48.689445,
 | 
				
			||||||
                "lat": 6.176510296462267,
 | 
					                "lat": 6.17651,
 | 
				
			||||||
                "houseNumber": "5",
 | 
					                "houseNumber": "5",
 | 
				
			||||||
                "street": "Avenue Foch",
 | 
					                "street": "Avenue Foch",
 | 
				
			||||||
                "locality": "Nancy",
 | 
					                "locality": "Nancy",
 | 
				
			||||||
| 
						 | 
					@ -164,15 +172,14 @@ The app exposes the following [gRPC](https://grpc.io/) services :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    The list of possible options when creating an ad :
 | 
					    The list of possible options when creating an ad :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    -   uuid (optional): the uuid of the ad
 | 
					    -   id (optional): the id of the ad (as a uuid)
 | 
				
			||||||
    -   userUuid: the user uuid
 | 
					    -   userId: the user id (as a uuid)
 | 
				
			||||||
    -   driver (boolean, optional): if the ad is a driver ad
 | 
					    -   driver (boolean, optional): if the ad is a driver ad
 | 
				
			||||||
    -   passenger (boolean, optional): if the ad is a passenger ad
 | 
					    -   passenger (boolean, optional): if the ad is a passenger ad
 | 
				
			||||||
    -   frequency: `PUNCTUAL` or `RECURRENT`
 | 
					    -   frequency: `PUNCTUAL` or `RECURRENT`
 | 
				
			||||||
    -   departureDateTime (required if punctual): departure date and hour/minute for a punctual ad
 | 
					    -   fromDate: start date for recurrent ad, carpool date for punctual ad
 | 
				
			||||||
    -   fromDate (required if recurrent): start date for recurrent ad
 | 
					    -   toDate: end date for recurrent ad, same as fromDate for punctual ad
 | 
				
			||||||
    -   toDate (required if recurrent): end date for recurrent ad
 | 
					    -   schedule: an object with the departure time for each carpooled day in the week (only the carpooled day for punctual ad)
 | 
				
			||||||
    -   schedule (required if recurrent): an object with the departure time for each carpooled day in the week
 | 
					 | 
				
			||||||
    -   marginDurations (optional): an object with the margin duration (in seconds) for each carpooled day in the week, eg:
 | 
					    -   marginDurations (optional): an object with the margin duration (in seconds) for each carpooled day in the week, eg:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
| 
						 | 
					@ -181,10 +188,10 @@ The app exposes the following [gRPC](https://grpc.io/) services :
 | 
				
			||||||
                "fri": 950
 | 
					                "fri": 950
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    -   seatsDriver (optional): number of seats proposed as driver;
 | 
					    -   seatsProposed (optional): number of seats proposed as driver
 | 
				
			||||||
    -   seatsPassenger (optional): number of seats requested as passenger;
 | 
					    -   seatsRequested (optional): number of seats requested as passenger
 | 
				
			||||||
    -   strict (boolean, optional): if set to true, allow matching only with similar frequency ads
 | 
					    -   strict (boolean, optional): if set to true, allow matching only with similar frequency ads
 | 
				
			||||||
    -   addresses: an array of adresses that represent the waypoints of the journey (only first and last waypoints are used for passenger ads)
 | 
					    -   waypoints: an array of addresses that represent the waypoints of the journey (only first and last waypoints are used for passenger ads). Note that positions are **required** and **must** be consecutives
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Default values must be set in `.env` file.
 | 
					    Default values must be set in `.env` file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										45
									
								
								package.json
								
								
								
								
							
							
						
						
									
										45
									
								
								package.json
								
								
								
								
							| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "name": "@mobicoop/ad",
 | 
					  "name": "@mobicoop/ad",
 | 
				
			||||||
  "version": "0.0.1",
 | 
					  "version": "1.0.0",
 | 
				
			||||||
  "description": "Mobicoop V3 Ad",
 | 
					  "description": "Mobicoop V3 Ad",
 | 
				
			||||||
  "author": "sbriat",
 | 
					  "author": "sbriat",
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
| 
						 | 
					@ -34,9 +34,6 @@
 | 
				
			||||||
    "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.14",
 | 
					    "@grpc/grpc-js": "^1.8.14",
 | 
				
			||||||
    "@grpc/proto-loader": "^0.7.6",
 | 
					    "@grpc/proto-loader": "^0.7.6",
 | 
				
			||||||
    "@liaoliaots/nestjs-redis": "^9.0.5",
 | 
					    "@liaoliaots/nestjs-redis": "^9.0.5",
 | 
				
			||||||
| 
						 | 
					@ -46,15 +43,19 @@
 | 
				
			||||||
    "@nestjs/config": "^2.3.1",
 | 
					    "@nestjs/config": "^2.3.1",
 | 
				
			||||||
    "@nestjs/core": "^9.0.0",
 | 
					    "@nestjs/core": "^9.0.0",
 | 
				
			||||||
    "@nestjs/cqrs": "^9.0.3",
 | 
					    "@nestjs/cqrs": "^9.0.3",
 | 
				
			||||||
 | 
					    "@nestjs/event-emitter": "^1.4.2",
 | 
				
			||||||
    "@nestjs/microservices": "^9.4.0",
 | 
					    "@nestjs/microservices": "^9.4.0",
 | 
				
			||||||
    "@nestjs/platform-express": "^9.0.0",
 | 
					    "@nestjs/platform-express": "^9.0.0",
 | 
				
			||||||
    "@nestjs/terminus": "^9.2.2",
 | 
					    "@nestjs/terminus": "^9.2.2",
 | 
				
			||||||
    "@prisma/client": "^4.13.0",
 | 
					    "@prisma/client": "^4.13.0",
 | 
				
			||||||
    "class-transformer": "^0.5.1",
 | 
					    "class-transformer": "^0.5.1",
 | 
				
			||||||
    "class-validator": "^0.14.0",
 | 
					    "class-validator": "^0.14.0",
 | 
				
			||||||
 | 
					    "geo-tz": "^7.0.7",
 | 
				
			||||||
    "ioredis": "^5.3.2",
 | 
					    "ioredis": "^5.3.2",
 | 
				
			||||||
 | 
					    "nestjs-request-context": "^2.1.0",
 | 
				
			||||||
    "reflect-metadata": "^0.1.13",
 | 
					    "reflect-metadata": "^0.1.13",
 | 
				
			||||||
    "rxjs": "^7.2.0"
 | 
					    "rxjs": "^7.2.0",
 | 
				
			||||||
 | 
					    "timezonecomplete": "^5.12.4"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@nestjs/cli": "^9.0.0",
 | 
					    "@nestjs/cli": "^9.0.0",
 | 
				
			||||||
| 
						 | 
					@ -64,6 +65,7 @@
 | 
				
			||||||
    "@types/jest": "29.5.0",
 | 
					    "@types/jest": "29.5.0",
 | 
				
			||||||
    "@types/node": "18.15.11",
 | 
					    "@types/node": "18.15.11",
 | 
				
			||||||
    "@types/supertest": "^2.0.11",
 | 
					    "@types/supertest": "^2.0.11",
 | 
				
			||||||
 | 
					    "@types/uuid": "^9.0.2",
 | 
				
			||||||
    "@typescript-eslint/eslint-plugin": "^5.0.0",
 | 
					    "@typescript-eslint/eslint-plugin": "^5.0.0",
 | 
				
			||||||
    "@typescript-eslint/parser": "^5.0.0",
 | 
					    "@typescript-eslint/parser": "^5.0.0",
 | 
				
			||||||
    "dotenv-cli": "^7.2.1",
 | 
					    "dotenv-cli": "^7.2.1",
 | 
				
			||||||
| 
						 | 
					@ -88,13 +90,16 @@
 | 
				
			||||||
      "ts"
 | 
					      "ts"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "modulePathIgnorePatterns": [
 | 
					    "modulePathIgnorePatterns": [
 | 
				
			||||||
      ".controller.ts",
 | 
					 | 
				
			||||||
      ".module.ts",
 | 
					      ".module.ts",
 | 
				
			||||||
      ".request.ts",
 | 
					      ".dto.ts",
 | 
				
			||||||
      ".presenter.ts",
 | 
					 | 
				
			||||||
      ".profile.ts",
 | 
					 | 
				
			||||||
      ".exception.ts",
 | 
					 | 
				
			||||||
      ".constants.ts",
 | 
					      ".constants.ts",
 | 
				
			||||||
 | 
					      ".response.ts",
 | 
				
			||||||
 | 
					      ".response.base.ts",
 | 
				
			||||||
 | 
					      ".port.ts",
 | 
				
			||||||
 | 
					      "libs/exceptions",
 | 
				
			||||||
 | 
					      "libs/types",
 | 
				
			||||||
 | 
					      "prisma.service.ts",
 | 
				
			||||||
 | 
					      "convert-props-to-object.util.ts",
 | 
				
			||||||
      "main.ts"
 | 
					      "main.ts"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "rootDir": "src",
 | 
					    "rootDir": "src",
 | 
				
			||||||
| 
						 | 
					@ -106,18 +111,24 @@
 | 
				
			||||||
      "**/*.(t|j)s"
 | 
					      "**/*.(t|j)s"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "coveragePathIgnorePatterns": [
 | 
					    "coveragePathIgnorePatterns": [
 | 
				
			||||||
      ".validator.ts",
 | 
					 | 
				
			||||||
      ".controller.ts",
 | 
					 | 
				
			||||||
      ".module.ts",
 | 
					      ".module.ts",
 | 
				
			||||||
      ".request.ts",
 | 
					      ".dto.ts",
 | 
				
			||||||
      ".presenter.ts",
 | 
					 | 
				
			||||||
      ".profile.ts",
 | 
					 | 
				
			||||||
      ".exception.ts",
 | 
					 | 
				
			||||||
      ".constants.ts",
 | 
					      ".constants.ts",
 | 
				
			||||||
      ".interfaces.ts",
 | 
					      ".response.ts",
 | 
				
			||||||
 | 
					      ".response.base.ts",
 | 
				
			||||||
 | 
					      ".port.ts",
 | 
				
			||||||
 | 
					      "libs/exceptions",
 | 
				
			||||||
 | 
					      "libs/types",
 | 
				
			||||||
 | 
					      "prisma.service.ts",
 | 
				
			||||||
 | 
					      "convert-props-to-object.util.ts",
 | 
				
			||||||
      "main.ts"
 | 
					      "main.ts"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "coverageDirectory": "../coverage",
 | 
					    "coverageDirectory": "../coverage",
 | 
				
			||||||
 | 
					    "moduleNameMapper": {
 | 
				
			||||||
 | 
					      "^@libs(.*)": "<rootDir>/libs/$1",
 | 
				
			||||||
 | 
					      "^@modules(.*)": "<rootDir>/modules/$1",
 | 
				
			||||||
 | 
					      "^@src(.*)": "<rootDir>$1"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "testEnvironment": "node"
 | 
					    "testEnvironment": "node"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,13 +10,13 @@ CREATE TABLE "ad" (
 | 
				
			||||||
    "frequency" "Frequency" NOT NULL,
 | 
					    "frequency" "Frequency" NOT NULL,
 | 
				
			||||||
    "fromDate" DATE NOT NULL,
 | 
					    "fromDate" DATE NOT NULL,
 | 
				
			||||||
    "toDate" DATE NOT NULL,
 | 
					    "toDate" DATE NOT NULL,
 | 
				
			||||||
    "monTime" TEXT,
 | 
					    "monTime" TIMESTAMPTZ,
 | 
				
			||||||
    "tueTime" TEXT,
 | 
					    "tueTime" TIMESTAMPTZ,
 | 
				
			||||||
    "wedTime" TEXT,
 | 
					    "wedTime" TIMESTAMPTZ,
 | 
				
			||||||
    "thuTime" TEXT,
 | 
					    "thuTime" TIMESTAMPTZ,
 | 
				
			||||||
    "friTime" TEXT,
 | 
					    "friTime" TIMESTAMPTZ,
 | 
				
			||||||
    "satTime" TEXT,
 | 
					    "satTime" TIMESTAMPTZ,
 | 
				
			||||||
    "sunTime" TEXT,
 | 
					    "sunTime" TIMESTAMPTZ,
 | 
				
			||||||
    "monMargin" INTEGER NOT NULL,
 | 
					    "monMargin" INTEGER NOT NULL,
 | 
				
			||||||
    "tueMargin" INTEGER NOT NULL,
 | 
					    "tueMargin" INTEGER NOT NULL,
 | 
				
			||||||
    "wedMargin" INTEGER NOT NULL,
 | 
					    "wedMargin" INTEGER NOT NULL,
 | 
				
			||||||
| 
						 | 
					@ -24,8 +24,8 @@ CREATE TABLE "ad" (
 | 
				
			||||||
    "friMargin" INTEGER NOT NULL,
 | 
					    "friMargin" INTEGER NOT NULL,
 | 
				
			||||||
    "satMargin" INTEGER NOT NULL,
 | 
					    "satMargin" INTEGER NOT NULL,
 | 
				
			||||||
    "sunMargin" INTEGER NOT NULL,
 | 
					    "sunMargin" INTEGER NOT NULL,
 | 
				
			||||||
    "seatsDriver" SMALLINT NOT NULL,
 | 
					    "seatsProposed" SMALLINT NOT NULL,
 | 
				
			||||||
    "seatsPassenger" SMALLINT NOT NULL,
 | 
					    "seatsRequested" SMALLINT NOT NULL,
 | 
				
			||||||
    "strict" BOOLEAN NOT NULL,
 | 
					    "strict" BOOLEAN NOT NULL,
 | 
				
			||||||
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
					    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
				
			||||||
    "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
					    "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
				
			||||||
| 
						 | 
					@ -34,12 +34,12 @@ CREATE TABLE "ad" (
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- CreateTable
 | 
					-- CreateTable
 | 
				
			||||||
CREATE TABLE "address" (
 | 
					CREATE TABLE "waypoint" (
 | 
				
			||||||
    "uuid" UUID NOT NULL,
 | 
					    "uuid" UUID NOT NULL,
 | 
				
			||||||
    "adUuid" UUID NOT NULL,
 | 
					    "adUuid" UUID NOT NULL,
 | 
				
			||||||
    "position" SMALLINT NOT NULL,
 | 
					    "position" SMALLINT NOT NULL,
 | 
				
			||||||
    "lon" DOUBLE PRECISION NOT NULL,
 | 
					    "lon" DECIMAL(9,6) NOT NULL,
 | 
				
			||||||
    "lat" DOUBLE PRECISION NOT NULL,
 | 
					    "lat" DECIMAL(8,6) NOT NULL,
 | 
				
			||||||
    "name" TEXT,
 | 
					    "name" TEXT,
 | 
				
			||||||
    "houseNumber" TEXT,
 | 
					    "houseNumber" TEXT,
 | 
				
			||||||
    "street" TEXT,
 | 
					    "street" TEXT,
 | 
				
			||||||
| 
						 | 
					@ -49,8 +49,8 @@ CREATE TABLE "address" (
 | 
				
			||||||
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
					    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
				
			||||||
    "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
					    "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    CONSTRAINT "address_pkey" PRIMARY KEY ("uuid")
 | 
					    CONSTRAINT "waypoint_pkey" PRIMARY KEY ("uuid")
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- AddForeignKey
 | 
					-- AddForeignKey
 | 
				
			||||||
ALTER TABLE "address" ADD CONSTRAINT "address_adUuid_fkey" FOREIGN KEY ("adUuid") REFERENCES "ad"("uuid") ON DELETE CASCADE ON UPDATE CASCADE;
 | 
					ALTER TABLE "waypoint" ADD CONSTRAINT "waypoint_adUuid_fkey" FOREIGN KEY ("adUuid") REFERENCES "ad"("uuid") ON DELETE CASCADE ON UPDATE CASCADE;
 | 
				
			||||||
| 
						 | 
					@ -12,20 +12,20 @@ datasource db {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
model Ad {
 | 
					model Ad {
 | 
				
			||||||
  uuid           String    @id @default(uuid()) @db.Uuid
 | 
					  uuid           String     @id @default(uuid()) @db.Uuid
 | 
				
			||||||
  userUuid       String    @db.Uuid
 | 
					  userUuid       String     @db.Uuid
 | 
				
			||||||
  driver         Boolean
 | 
					  driver         Boolean
 | 
				
			||||||
  passenger      Boolean
 | 
					  passenger      Boolean
 | 
				
			||||||
  frequency      Frequency
 | 
					  frequency      Frequency
 | 
				
			||||||
  fromDate       DateTime  @db.Date
 | 
					  fromDate       DateTime   @db.Date
 | 
				
			||||||
  toDate         DateTime  @db.Date
 | 
					  toDate         DateTime   @db.Date
 | 
				
			||||||
  monTime        String?
 | 
					  monTime        DateTime?  @db.Timestamptz()
 | 
				
			||||||
  tueTime        String?
 | 
					  tueTime        DateTime?  @db.Timestamptz()
 | 
				
			||||||
  wedTime        String?
 | 
					  wedTime        DateTime?  @db.Timestamptz()
 | 
				
			||||||
  thuTime        String?
 | 
					  thuTime        DateTime?  @db.Timestamptz()
 | 
				
			||||||
  friTime        String?
 | 
					  friTime        DateTime?  @db.Timestamptz()
 | 
				
			||||||
  satTime        String?
 | 
					  satTime        DateTime?  @db.Timestamptz()
 | 
				
			||||||
  sunTime        String?
 | 
					  sunTime        DateTime?  @db.Timestamptz()
 | 
				
			||||||
  monMargin      Int
 | 
					  monMargin      Int
 | 
				
			||||||
  tueMargin      Int
 | 
					  tueMargin      Int
 | 
				
			||||||
  wedMargin      Int
 | 
					  wedMargin      Int
 | 
				
			||||||
| 
						 | 
					@ -33,22 +33,22 @@ model Ad {
 | 
				
			||||||
  friMargin      Int
 | 
					  friMargin      Int
 | 
				
			||||||
  satMargin      Int
 | 
					  satMargin      Int
 | 
				
			||||||
  sunMargin      Int
 | 
					  sunMargin      Int
 | 
				
			||||||
  seatsDriver    Int       @db.SmallInt
 | 
					  seatsProposed  Int        @db.SmallInt
 | 
				
			||||||
  seatsPassenger Int       @db.SmallInt
 | 
					  seatsRequested Int        @db.SmallInt
 | 
				
			||||||
  strict         Boolean
 | 
					  strict         Boolean
 | 
				
			||||||
  createdAt      DateTime  @default(now())
 | 
					  createdAt      DateTime   @default(now())
 | 
				
			||||||
  updatedAt      DateTime  @default(now()) @updatedAt
 | 
					  updatedAt      DateTime   @default(now()) @updatedAt
 | 
				
			||||||
  addresses      Address[]
 | 
					  waypoints      Waypoint[]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @@map("ad")
 | 
					  @@map("ad")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
model Address {
 | 
					model Waypoint {
 | 
				
			||||||
  uuid        String   @id @default(uuid()) @db.Uuid
 | 
					  uuid        String   @id @default(uuid()) @db.Uuid
 | 
				
			||||||
  adUuid      String   @db.Uuid
 | 
					  adUuid      String   @db.Uuid
 | 
				
			||||||
  position    Int      @db.SmallInt
 | 
					  position    Int      @db.SmallInt
 | 
				
			||||||
  lon         Float
 | 
					  lon         Decimal  @db.Decimal(9, 6)
 | 
				
			||||||
  lat         Float
 | 
					  lat         Decimal  @db.Decimal(8, 6)
 | 
				
			||||||
  name        String?
 | 
					  name        String?
 | 
				
			||||||
  houseNumber String?
 | 
					  houseNumber String?
 | 
				
			||||||
  street      String?
 | 
					  street      String?
 | 
				
			||||||
| 
						 | 
					@ -59,7 +59,7 @@ model Address {
 | 
				
			||||||
  updatedAt   DateTime @default(now()) @updatedAt
 | 
					  updatedAt   DateTime @default(now()) @updatedAt
 | 
				
			||||||
  Ad          Ad       @relation(fields: [adUuid], references: [uuid], onDelete: Cascade)
 | 
					  Ad          Ad       @relation(fields: [adUuid], references: [uuid], onDelete: Cascade)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @@map("address")
 | 
					  @@map("waypoint")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum Frequency {
 | 
					enum Frequency {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,6 @@
 | 
				
			||||||
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 { AdModule } from './modules/ad/ad.module';
 | 
					import { AdModule } from './modules/ad/ad.module';
 | 
				
			||||||
import { AutomapperModule } from '@automapper/nestjs';
 | 
					 | 
				
			||||||
import { classes } from '@automapper/classes';
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  MessageBrokerModule,
 | 
					  MessageBrokerModule,
 | 
				
			||||||
  MessageBrokerModuleOptions,
 | 
					  MessageBrokerModuleOptions,
 | 
				
			||||||
| 
						 | 
					@ -12,11 +9,15 @@ import {
 | 
				
			||||||
  ConfigurationModule,
 | 
					  ConfigurationModule,
 | 
				
			||||||
  ConfigurationModuleOptions,
 | 
					  ConfigurationModuleOptions,
 | 
				
			||||||
} from '@mobicoop/configuration-module';
 | 
					} from '@mobicoop/configuration-module';
 | 
				
			||||||
 | 
					import { EventEmitterModule } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import { RequestContextModule } from 'nestjs-request-context';
 | 
				
			||||||
 | 
					import { HealthModule } from '@modules/health/health.module';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Module({
 | 
					@Module({
 | 
				
			||||||
  imports: [
 | 
					  imports: [
 | 
				
			||||||
    ConfigModule.forRoot({ isGlobal: true }),
 | 
					    ConfigModule.forRoot({ isGlobal: true }),
 | 
				
			||||||
    AutomapperModule.forRoot({ strategyInitializer: classes() }),
 | 
					    EventEmitterModule.forRoot(),
 | 
				
			||||||
 | 
					    RequestContextModule,
 | 
				
			||||||
    MessageBrokerModule.forRootAsync({
 | 
					    MessageBrokerModule.forRootAsync({
 | 
				
			||||||
      imports: [ConfigModule],
 | 
					      imports: [ConfigModule],
 | 
				
			||||||
      inject: [ConfigService],
 | 
					      inject: [ConfigService],
 | 
				
			||||||
| 
						 | 
					@ -44,8 +45,6 @@ import {
 | 
				
			||||||
          password: configService.get<string>('REDIS_PASSWORD'),
 | 
					          password: configService.get<string>('REDIS_PASSWORD'),
 | 
				
			||||||
          port: configService.get<number>('REDIS_PORT'),
 | 
					          port: configService.get<number>('REDIS_PORT'),
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					 | 
				
			||||||
        propagateConfigurationRoutingKey: 'configuration.propagate',
 | 
					 | 
				
			||||||
        setConfigurationBrokerQueue: 'ad-configuration-create-update',
 | 
					        setConfigurationBrokerQueue: 'ad-configuration-create-update',
 | 
				
			||||||
        deleteConfigurationQueue: 'ad-configuration-delete',
 | 
					        deleteConfigurationQueue: 'ad-configuration-delete',
 | 
				
			||||||
        propagateConfigurationQueue: 'ad-configuration-propagate',
 | 
					        propagateConfigurationQueue: 'ad-configuration-propagate',
 | 
				
			||||||
| 
						 | 
					@ -54,7 +53,5 @@ import {
 | 
				
			||||||
    HealthModule,
 | 
					    HealthModule,
 | 
				
			||||||
    AdModule,
 | 
					    AdModule,
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  controllers: [],
 | 
					 | 
				
			||||||
  providers: [],
 | 
					 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class AppModule {}
 | 
					export class AppModule {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,19 @@
 | 
				
			||||||
 | 
					export class ApiErrorResponse {
 | 
				
			||||||
 | 
					  readonly statusCode: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  readonly message: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  readonly error: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  readonly correlationId: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  readonly subErrors?: string[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(body: ApiErrorResponse) {
 | 
				
			||||||
 | 
					    this.statusCode = body.statusCode;
 | 
				
			||||||
 | 
					    this.message = body.message;
 | 
				
			||||||
 | 
					    this.error = body.error;
 | 
				
			||||||
 | 
					    this.correlationId = body.correlationId;
 | 
				
			||||||
 | 
					    this.subErrors = body.subErrors;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					export class IdResponse {
 | 
				
			||||||
 | 
					  constructor(id: string) {
 | 
				
			||||||
 | 
					    this.id = id;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  readonly id: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					import { Paginated } from '../ddd';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export abstract class PaginatedResponseDto<T> extends Paginated<T> {
 | 
				
			||||||
 | 
					  readonly total: number;
 | 
				
			||||||
 | 
					  readonly perPage: number;
 | 
				
			||||||
 | 
					  readonly page: number;
 | 
				
			||||||
 | 
					  abstract readonly data: readonly T[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					import { IdResponse } from './id.response.dto';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface BaseResponseProps {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					  createdAt: Date;
 | 
				
			||||||
 | 
					  updatedAt: Date;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Most of our response objects will have properties like
 | 
				
			||||||
 | 
					 * id, createdAt and updatedAt so we can move them to a
 | 
				
			||||||
 | 
					 * separate class and extend it to avoid duplication.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class ResponseBase extends IdResponse {
 | 
				
			||||||
 | 
					  constructor(props: BaseResponseProps) {
 | 
				
			||||||
 | 
					    super(props.id);
 | 
				
			||||||
 | 
					    this.createdAt = new Date(props.createdAt).toISOString();
 | 
				
			||||||
 | 
					    this.updatedAt = new Date(props.updatedAt).toISOString();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  readonly createdAt: string;
 | 
				
			||||||
 | 
					  readonly updatedAt: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,66 @@
 | 
				
			||||||
 | 
					import { EventEmitter2 } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import { AggregateRoot, Mapper, RepositoryPort } from '../ddd';
 | 
				
			||||||
 | 
					import { ObjectLiteral } from '../types';
 | 
				
			||||||
 | 
					import { LoggerPort } from '../ports/logger.port';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  PrismaRawRepositoryPort,
 | 
				
			||||||
 | 
					  PrismaRepositoryPort,
 | 
				
			||||||
 | 
					} from '../ports/prisma-repository.port';
 | 
				
			||||||
 | 
					import { Prisma } from '@prisma/client';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ConflictException,
 | 
				
			||||||
 | 
					  DatabaseErrorException,
 | 
				
			||||||
 | 
					  NotFoundException,
 | 
				
			||||||
 | 
					} from '@libs/exceptions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export abstract class PrismaRepositoryBase<
 | 
				
			||||||
 | 
					  Aggregate extends AggregateRoot<any>,
 | 
				
			||||||
 | 
					  DbReadModel extends ObjectLiteral,
 | 
				
			||||||
 | 
					  DbWriteModel extends ObjectLiteral,
 | 
				
			||||||
 | 
					> implements RepositoryPort<Aggregate>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  protected constructor(
 | 
				
			||||||
 | 
					    protected readonly prisma: PrismaRepositoryPort<Aggregate> | any,
 | 
				
			||||||
 | 
					    protected readonly prismaRaw: PrismaRawRepositoryPort,
 | 
				
			||||||
 | 
					    protected readonly mapper: Mapper<Aggregate, DbReadModel, DbWriteModel>,
 | 
				
			||||||
 | 
					    protected readonly eventEmitter: EventEmitter2,
 | 
				
			||||||
 | 
					    protected readonly logger: LoggerPort,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async findOneById(id: string, include?: any): Promise<Aggregate> {
 | 
				
			||||||
 | 
					    const entity = await this.prisma.findUnique({
 | 
				
			||||||
 | 
					      where: { uuid: id },
 | 
				
			||||||
 | 
					      include,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (entity) return this.mapper.toDomain(entity);
 | 
				
			||||||
 | 
					    throw new NotFoundException('Record not found');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async insert(entity: Aggregate): Promise<void> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await this.prisma.create({
 | 
				
			||||||
 | 
					        data: this.mapper.toPersistence(entity),
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      entity.publishEvents(this.logger, this.eventEmitter);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      if (e instanceof Prisma.PrismaClientKnownRequestError) {
 | 
				
			||||||
 | 
					        if (e.message.includes('Already exists')) {
 | 
				
			||||||
 | 
					          throw new ConflictException('Record already exists', e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      throw e;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async healthCheck(): Promise<boolean> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await this.prismaRaw.$queryRaw`SELECT 1`;
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      if (e instanceof Prisma.PrismaClientKnownRequestError) {
 | 
				
			||||||
 | 
					        throw new DatabaseErrorException(e.message);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      throw new DatabaseErrorException();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,35 @@
 | 
				
			||||||
 | 
					import { Entity } from './entity.base';
 | 
				
			||||||
 | 
					import { EventEmitter2 } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import { LoggerPort } from '@libs/ports/logger.port';
 | 
				
			||||||
 | 
					import { DomainEvent } from './domain-event.base';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export abstract class AggregateRoot<EntityProps> extends Entity<EntityProps> {
 | 
				
			||||||
 | 
					  private _domainEvents: DomainEvent[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get domainEvents(): DomainEvent[] {
 | 
				
			||||||
 | 
					    return this._domainEvents;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  protected addEvent(domainEvent: DomainEvent): void {
 | 
				
			||||||
 | 
					    this._domainEvents.push(domainEvent);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public clearEvents(): void {
 | 
				
			||||||
 | 
					    this._domainEvents = [];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public async publishEvents(
 | 
				
			||||||
 | 
					    logger: LoggerPort,
 | 
				
			||||||
 | 
					    eventEmitter: EventEmitter2,
 | 
				
			||||||
 | 
					  ): Promise<void> {
 | 
				
			||||||
 | 
					    await Promise.all(
 | 
				
			||||||
 | 
					      this.domainEvents.map(async (event) => {
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					          `"${event.constructor.name}" event published for aggregate ${this.constructor.name} : ${this.id}`,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return eventEmitter.emitAsync(event.constructor.name, event);
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    this.clearEvents();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,52 @@
 | 
				
			||||||
 | 
					import { v4 } from 'uuid';
 | 
				
			||||||
 | 
					import { ArgumentNotProvidedException } from '../exceptions';
 | 
				
			||||||
 | 
					import { Guard } from '../guard';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type CommandProps<T> = Omit<T, 'id' | 'metadata'> & Partial<Command>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type CommandMetadata = {
 | 
				
			||||||
 | 
					  /** ID for correlation purposes (for commands that
 | 
				
			||||||
 | 
					   *  arrive from other microservices,logs correlation, etc). */
 | 
				
			||||||
 | 
					  readonly correlationId: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Causation id to reconstruct execution order if needed
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  readonly causationId?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * ID of a user who invoker the command. Can be useful for
 | 
				
			||||||
 | 
					   * logging and tracking execution of commands and events
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  readonly userId?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Time when the command occurred. Mostly for tracing purposes
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  readonly timestamp: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Command {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Command id, in case if we want to save it
 | 
				
			||||||
 | 
					   * for auditing purposes and create a correlation/causation chain
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  readonly id: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  readonly metadata: CommandMetadata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: CommandProps<unknown>) {
 | 
				
			||||||
 | 
					    if (Guard.isEmpty(props)) {
 | 
				
			||||||
 | 
					      throw new ArgumentNotProvidedException(
 | 
				
			||||||
 | 
					        'Command props should not be empty',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.id = props.id || v4();
 | 
				
			||||||
 | 
					    this.metadata = {
 | 
				
			||||||
 | 
					      correlationId: props?.metadata?.correlationId,
 | 
				
			||||||
 | 
					      causationId: props?.metadata?.causationId,
 | 
				
			||||||
 | 
					      timestamp: props?.metadata?.timestamp || Date.now(),
 | 
				
			||||||
 | 
					      userId: props?.metadata?.userId,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,52 @@
 | 
				
			||||||
 | 
					import { ArgumentNotProvidedException } from '../exceptions';
 | 
				
			||||||
 | 
					import { Guard } from '../guard';
 | 
				
			||||||
 | 
					import { v4 } from 'uuid';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DomainEventMetadata = {
 | 
				
			||||||
 | 
					  /** Timestamp when this domain event occurred */
 | 
				
			||||||
 | 
					  readonly timestamp: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** ID for correlation purposes (for Integration Events,logs correlation, etc).
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  readonly correlationId: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Causation id used to reconstruct execution order if needed
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  readonly causationId?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * User ID for debugging and logging purposes
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  readonly userId?: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type DomainEventProps<T> = Omit<T, 'id' | 'metadata'> & {
 | 
				
			||||||
 | 
					  aggregateId: string;
 | 
				
			||||||
 | 
					  metadata?: DomainEventMetadata;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export abstract class DomainEvent {
 | 
				
			||||||
 | 
					  public readonly id: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Aggregate ID where domain event occurred */
 | 
				
			||||||
 | 
					  public readonly aggregateId: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public readonly metadata: DomainEventMetadata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: DomainEventProps<unknown>) {
 | 
				
			||||||
 | 
					    if (Guard.isEmpty(props)) {
 | 
				
			||||||
 | 
					      throw new ArgumentNotProvidedException(
 | 
				
			||||||
 | 
					        'DomainEvent props should not be empty',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.id = v4();
 | 
				
			||||||
 | 
					    this.aggregateId = props.aggregateId;
 | 
				
			||||||
 | 
					    this.metadata = {
 | 
				
			||||||
 | 
					      correlationId: props?.metadata?.correlationId,
 | 
				
			||||||
 | 
					      causationId: props?.metadata?.causationId,
 | 
				
			||||||
 | 
					      timestamp: props?.metadata?.timestamp || Date.now(),
 | 
				
			||||||
 | 
					      userId: props?.metadata?.userId,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,150 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ArgumentNotProvidedException,
 | 
				
			||||||
 | 
					  ArgumentInvalidException,
 | 
				
			||||||
 | 
					  ArgumentOutOfRangeException,
 | 
				
			||||||
 | 
					} from '../exceptions';
 | 
				
			||||||
 | 
					import { Guard } from '../guard';
 | 
				
			||||||
 | 
					import { convertPropsToObject } from '../utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type AggregateID = string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface BaseEntityProps {
 | 
				
			||||||
 | 
					  id: AggregateID;
 | 
				
			||||||
 | 
					  createdAt: Date;
 | 
				
			||||||
 | 
					  updatedAt: Date;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface CreateEntityProps<T> {
 | 
				
			||||||
 | 
					  id: AggregateID;
 | 
				
			||||||
 | 
					  props: T;
 | 
				
			||||||
 | 
					  createdAt?: Date;
 | 
				
			||||||
 | 
					  updatedAt?: Date;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export abstract class Entity<EntityProps> {
 | 
				
			||||||
 | 
					  constructor({
 | 
				
			||||||
 | 
					    id,
 | 
				
			||||||
 | 
					    createdAt,
 | 
				
			||||||
 | 
					    updatedAt,
 | 
				
			||||||
 | 
					    props,
 | 
				
			||||||
 | 
					  }: CreateEntityProps<EntityProps>) {
 | 
				
			||||||
 | 
					    this.setId(id);
 | 
				
			||||||
 | 
					    this.validateProps(props);
 | 
				
			||||||
 | 
					    const now = new Date();
 | 
				
			||||||
 | 
					    this._createdAt = createdAt || now;
 | 
				
			||||||
 | 
					    this._updatedAt = updatedAt || now;
 | 
				
			||||||
 | 
					    this.props = props;
 | 
				
			||||||
 | 
					    this.validate();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  protected readonly props: EntityProps;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * ID is set in the concrete entity implementation to support
 | 
				
			||||||
 | 
					   * different ID types depending on your needs.
 | 
				
			||||||
 | 
					   * For example it could be a UUID for aggregate root,
 | 
				
			||||||
 | 
					   * and shortid / nanoid for child entities.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  protected abstract _id: AggregateID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private readonly _createdAt: Date;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _updatedAt: Date;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get id(): AggregateID {
 | 
				
			||||||
 | 
					    return this._id;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private setId(id: AggregateID): void {
 | 
				
			||||||
 | 
					    this._id = id;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get createdAt(): Date {
 | 
				
			||||||
 | 
					    return this._createdAt;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get updatedAt(): Date {
 | 
				
			||||||
 | 
					    return this._updatedAt;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static isEntity(entity: unknown): entity is Entity<unknown> {
 | 
				
			||||||
 | 
					    return entity instanceof Entity;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   *  Checks if two entities are the same Entity by comparing ID field.
 | 
				
			||||||
 | 
					   * @param object Entity
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public equals(object?: Entity<EntityProps>): boolean {
 | 
				
			||||||
 | 
					    if (object === null || object === undefined) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (this === object) {
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!Entity.isEntity(object)) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return this.id ? this.id === object.id : false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Returns entity properties.
 | 
				
			||||||
 | 
					   * @return {*}  {Props & EntityProps}
 | 
				
			||||||
 | 
					   * @memberof Entity
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public getProps(): EntityProps & BaseEntityProps {
 | 
				
			||||||
 | 
					    const propsCopy = {
 | 
				
			||||||
 | 
					      id: this._id,
 | 
				
			||||||
 | 
					      createdAt: this._createdAt,
 | 
				
			||||||
 | 
					      updatedAt: this._updatedAt,
 | 
				
			||||||
 | 
					      ...this.props,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    return Object.freeze(propsCopy);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Convert an Entity and all sub-entities/Value Objects it
 | 
				
			||||||
 | 
					   * contains to a plain object with primitive types. Can be
 | 
				
			||||||
 | 
					   * useful when logging an entity during testing/debugging
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public toObject(): unknown {
 | 
				
			||||||
 | 
					    const plainProps = convertPropsToObject(this.props);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const result = {
 | 
				
			||||||
 | 
					      id: this._id,
 | 
				
			||||||
 | 
					      createdAt: this._createdAt,
 | 
				
			||||||
 | 
					      updatedAt: this._updatedAt,
 | 
				
			||||||
 | 
					      ...plainProps,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    return Object.freeze(result);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * There are certain rules that always have to be true (invariants)
 | 
				
			||||||
 | 
					   * for each entity. Validate method is called every time before
 | 
				
			||||||
 | 
					   * saving an entity to the database to make sure those rules are respected.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public abstract validate(): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private validateProps(props: EntityProps): void {
 | 
				
			||||||
 | 
					    const MAX_PROPS = 50;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (Guard.isEmpty(props)) {
 | 
				
			||||||
 | 
					      throw new ArgumentNotProvidedException(
 | 
				
			||||||
 | 
					        'Entity props should not be empty',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (typeof props !== 'object') {
 | 
				
			||||||
 | 
					      throw new ArgumentInvalidException('Entity props should be an object');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (Object.keys(props as any).length > MAX_PROPS) {
 | 
				
			||||||
 | 
					      throw new ArgumentOutOfRangeException(
 | 
				
			||||||
 | 
					        `Entity props should not have more than ${MAX_PROPS} properties`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					export * from './aggregate-root.base';
 | 
				
			||||||
 | 
					export * from './command.base';
 | 
				
			||||||
 | 
					export * from './domain-event.base';
 | 
				
			||||||
 | 
					export * from './entity.base';
 | 
				
			||||||
 | 
					export * from './mapper.interface';
 | 
				
			||||||
 | 
					export * from './repository.port';
 | 
				
			||||||
 | 
					export * from './value-object.base';
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					import { Entity } from './entity.base';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface Mapper<
 | 
				
			||||||
 | 
					  DomainEntity extends Entity<any>,
 | 
				
			||||||
 | 
					  DbReadRecord,
 | 
				
			||||||
 | 
					  DbWriteRecord,
 | 
				
			||||||
 | 
					  Response = any,
 | 
				
			||||||
 | 
					> {
 | 
				
			||||||
 | 
					  toPersistence(entity: DomainEntity): DbWriteRecord;
 | 
				
			||||||
 | 
					  toDomain(record: DbReadRecord): DomainEntity;
 | 
				
			||||||
 | 
					  toResponse(entity: DomainEntity): Response;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					import { OrderBy, PaginatedQueryParams } from './repository.port';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Base class for regular queries
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export abstract class QueryBase {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Base class for paginated queries
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export abstract class PaginatedQueryBase extends QueryBase {
 | 
				
			||||||
 | 
					  perPage: number;
 | 
				
			||||||
 | 
					  offset: number;
 | 
				
			||||||
 | 
					  orderBy: OrderBy;
 | 
				
			||||||
 | 
					  page: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: PaginatedParams<PaginatedQueryBase>) {
 | 
				
			||||||
 | 
					    super();
 | 
				
			||||||
 | 
					    this.perPage = props.perPage || 10;
 | 
				
			||||||
 | 
					    this.offset = props.page ? props.page * this.perPage : 0;
 | 
				
			||||||
 | 
					    this.page = props.page || 0;
 | 
				
			||||||
 | 
					    this.orderBy = props.orderBy || { field: true, param: 'desc' };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Paginated query parameters
 | 
				
			||||||
 | 
					export type PaginatedParams<T> = Omit<
 | 
				
			||||||
 | 
					  T,
 | 
				
			||||||
 | 
					  'perPage' | 'offset' | 'orderBy' | 'page'
 | 
				
			||||||
 | 
					> &
 | 
				
			||||||
 | 
					  Partial<Omit<PaginatedQueryParams, 'offset'>>;
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					/*  Most of repositories will probably need generic
 | 
				
			||||||
 | 
					    save/find/delete operations, so it's easier
 | 
				
			||||||
 | 
					    to have some shared interfaces.
 | 
				
			||||||
 | 
					    More specific queries should be defined
 | 
				
			||||||
 | 
					    in a respective repository.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Paginated<T> {
 | 
				
			||||||
 | 
					  readonly total: number;
 | 
				
			||||||
 | 
					  readonly perPage: number;
 | 
				
			||||||
 | 
					  readonly page: number;
 | 
				
			||||||
 | 
					  readonly data: readonly T[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: Paginated<T>) {
 | 
				
			||||||
 | 
					    this.total = props.total;
 | 
				
			||||||
 | 
					    this.perPage = props.perPage;
 | 
				
			||||||
 | 
					    this.page = props.page;
 | 
				
			||||||
 | 
					    this.data = props.data;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type OrderBy = { field: string | true; param: 'asc' | 'desc' };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type PaginatedQueryParams = {
 | 
				
			||||||
 | 
					  perPage: number;
 | 
				
			||||||
 | 
					  page: number;
 | 
				
			||||||
 | 
					  offset: number;
 | 
				
			||||||
 | 
					  orderBy: OrderBy;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface RepositoryPort<Entity> {
 | 
				
			||||||
 | 
					  insert(entity: Entity | Entity[]): Promise<void>;
 | 
				
			||||||
 | 
					  findOneById(id: string, include?: any): Promise<Entity>;
 | 
				
			||||||
 | 
					  healthCheck(): Promise<boolean>;
 | 
				
			||||||
 | 
					  // findAll(): Promise<Entity[]>;
 | 
				
			||||||
 | 
					  // findAllPaginated(params: PaginatedQueryParams): Promise<Paginated<Entity>>;
 | 
				
			||||||
 | 
					  // delete(entity: Entity): Promise<boolean>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // transaction<T>(handler: () => Promise<T>): Promise<T>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,71 @@
 | 
				
			||||||
 | 
					import { ArgumentNotProvidedException } from '../exceptions';
 | 
				
			||||||
 | 
					import { Guard } from '../guard';
 | 
				
			||||||
 | 
					import { convertPropsToObject } from '../utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Domain Primitive is an object that contains only a single value
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type Primitives = string | number | boolean;
 | 
				
			||||||
 | 
					export interface DomainPrimitive<T extends Primitives | Date> {
 | 
				
			||||||
 | 
					  value: T;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ValueObjectProps<T> = T extends Primitives | Date ? DomainPrimitive<T> : T;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export abstract class ValueObject<T> {
 | 
				
			||||||
 | 
					  protected readonly props: ValueObjectProps<T>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: ValueObjectProps<T>) {
 | 
				
			||||||
 | 
					    this.checkIfEmpty(props);
 | 
				
			||||||
 | 
					    this.validate(props);
 | 
				
			||||||
 | 
					    this.props = props;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  protected abstract validate(props: ValueObjectProps<T>): void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static isValueObject(obj: unknown): obj is ValueObject<unknown> {
 | 
				
			||||||
 | 
					    return obj instanceof ValueObject;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   *  Check if two Value Objects are equal. Checks structural equality.
 | 
				
			||||||
 | 
					   * @param vo ValueObject
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public equals(vo?: ValueObject<T>): boolean {
 | 
				
			||||||
 | 
					    if (vo === null || vo === undefined) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return JSON.stringify(this) === JSON.stringify(vo);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Unpack a value object to get its raw properties
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public unpack(): T {
 | 
				
			||||||
 | 
					    if (this.isDomainPrimitive(this.props)) {
 | 
				
			||||||
 | 
					      return this.props.value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const propsCopy = convertPropsToObject(this.props);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Object.freeze(propsCopy);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private checkIfEmpty(props: ValueObjectProps<T>): void {
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      Guard.isEmpty(props) ||
 | 
				
			||||||
 | 
					      (this.isDomainPrimitive(props) && Guard.isEmpty(props.value))
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      throw new ArgumentNotProvidedException('Property cannot be empty');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private isDomainPrimitive(
 | 
				
			||||||
 | 
					    obj: unknown,
 | 
				
			||||||
 | 
					  ): obj is DomainPrimitive<T & (Primitives | Date)> {
 | 
				
			||||||
 | 
					    if (Object.prototype.hasOwnProperty.call(obj, 'value')) {
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,63 @@
 | 
				
			||||||
 | 
					export interface SerializedException {
 | 
				
			||||||
 | 
					  message: string;
 | 
				
			||||||
 | 
					  code: string;
 | 
				
			||||||
 | 
					  correlationId: string;
 | 
				
			||||||
 | 
					  stack?: string;
 | 
				
			||||||
 | 
					  cause?: string;
 | 
				
			||||||
 | 
					  metadata?: unknown;
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * ^ Consider adding optional `metadata` object to
 | 
				
			||||||
 | 
					   * exceptions (if language doesn't support anything
 | 
				
			||||||
 | 
					   * similar by default) and pass some useful technical
 | 
				
			||||||
 | 
					   * information about the exception when throwing.
 | 
				
			||||||
 | 
					   * This will make debugging easier.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Base class for custom exceptions.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @abstract
 | 
				
			||||||
 | 
					 * @class ExceptionBase
 | 
				
			||||||
 | 
					 * @extends {Error}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export abstract class ExceptionBase extends Error {
 | 
				
			||||||
 | 
					  abstract code: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public readonly correlationId: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * @param {string} message
 | 
				
			||||||
 | 
					   * @param {ObjectLiteral} [metadata={}]
 | 
				
			||||||
 | 
					   * **BE CAREFUL** not to include sensitive info in 'metadata'
 | 
				
			||||||
 | 
					   * to prevent leaks since all exception's data will end up
 | 
				
			||||||
 | 
					   * in application's log files. Only include non-sensitive
 | 
				
			||||||
 | 
					   * info that may help with debugging.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    readonly message: string,
 | 
				
			||||||
 | 
					    readonly cause?: Error,
 | 
				
			||||||
 | 
					    readonly metadata?: unknown,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    super(message);
 | 
				
			||||||
 | 
					    Error.captureStackTrace(this, this.constructor);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * By default in NodeJS Error objects are not
 | 
				
			||||||
 | 
					   * serialized properly when sending plain objects
 | 
				
			||||||
 | 
					   * to external processes. This method is a workaround.
 | 
				
			||||||
 | 
					   * Keep in mind not to return a stack trace to user when in production.
 | 
				
			||||||
 | 
					   * https://iaincollins.medium.com/error-handling-in-javascript-a6172ccdf9af
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  toJSON(): SerializedException {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      message: this.message,
 | 
				
			||||||
 | 
					      code: this.code,
 | 
				
			||||||
 | 
					      stack: this.stack,
 | 
				
			||||||
 | 
					      correlationId: this.correlationId,
 | 
				
			||||||
 | 
					      cause: JSON.stringify(this.cause),
 | 
				
			||||||
 | 
					      metadata: this.metadata,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Adding a `code` string with a custom status code for every
 | 
				
			||||||
 | 
					 * exception is a good practice, since when that exception
 | 
				
			||||||
 | 
					 * is transferred to another process `instanceof` check
 | 
				
			||||||
 | 
					 * cannot be performed anymore so a `code` string is used instead.
 | 
				
			||||||
 | 
					 * code constants can be stored in a separate file so they
 | 
				
			||||||
 | 
					 * can be shared and reused on a receiving side (code sharing is
 | 
				
			||||||
 | 
					 * useful when developing fullstack apps or microservices)
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const ARGUMENT_INVALID = 'GENERIC.ARGUMENT_INVALID';
 | 
				
			||||||
 | 
					export const ARGUMENT_OUT_OF_RANGE = 'GENERIC.ARGUMENT_OUT_OF_RANGE';
 | 
				
			||||||
 | 
					export const ARGUMENT_NOT_PROVIDED = 'GENERIC.ARGUMENT_NOT_PROVIDED';
 | 
				
			||||||
 | 
					export const NOT_FOUND = 'GENERIC.NOT_FOUND';
 | 
				
			||||||
 | 
					export const CONFLICT = 'GENERIC.CONFLICT';
 | 
				
			||||||
 | 
					export const INTERNAL_SERVER_ERROR = 'GENERIC.INTERNAL_SERVER_ERROR';
 | 
				
			||||||
 | 
					export const DATABASE_ERROR = 'GENERIC.DATABASE_ERROR';
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,99 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ARGUMENT_INVALID,
 | 
				
			||||||
 | 
					  ARGUMENT_NOT_PROVIDED,
 | 
				
			||||||
 | 
					  ARGUMENT_OUT_OF_RANGE,
 | 
				
			||||||
 | 
					  CONFLICT,
 | 
				
			||||||
 | 
					  DATABASE_ERROR,
 | 
				
			||||||
 | 
					  INTERNAL_SERVER_ERROR,
 | 
				
			||||||
 | 
					  NOT_FOUND,
 | 
				
			||||||
 | 
					} from '.';
 | 
				
			||||||
 | 
					import { ExceptionBase } from './exception.base';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Used to indicate that an incorrect argument was provided to a method/function/class constructor
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @class ArgumentInvalidException
 | 
				
			||||||
 | 
					 * @extends {ExceptionBase}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class ArgumentInvalidException extends ExceptionBase {
 | 
				
			||||||
 | 
					  readonly code = ARGUMENT_INVALID;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Used to indicate that an argument was not provided (is empty object/array, null of undefined).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @class ArgumentNotProvidedException
 | 
				
			||||||
 | 
					 * @extends {ExceptionBase}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class ArgumentNotProvidedException extends ExceptionBase {
 | 
				
			||||||
 | 
					  readonly code = ARGUMENT_NOT_PROVIDED;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Used to indicate that an argument is out of allowed range
 | 
				
			||||||
 | 
					 * (for example: incorrect string/array length, number not in allowed min/max range etc)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @class ArgumentOutOfRangeException
 | 
				
			||||||
 | 
					 * @extends {ExceptionBase}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class ArgumentOutOfRangeException extends ExceptionBase {
 | 
				
			||||||
 | 
					  readonly code = ARGUMENT_OUT_OF_RANGE;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Used to indicate conflicting entities (usually in the database)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @class ConflictException
 | 
				
			||||||
 | 
					 * @extends {ExceptionBase}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class ConflictException extends ExceptionBase {
 | 
				
			||||||
 | 
					  readonly code = CONFLICT;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Used to indicate that entity is not found
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @class NotFoundException
 | 
				
			||||||
 | 
					 * @extends {ExceptionBase}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class NotFoundException extends ExceptionBase {
 | 
				
			||||||
 | 
					  static readonly message = 'Not found';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(message = NotFoundException.message) {
 | 
				
			||||||
 | 
					    super(message);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  readonly code = NOT_FOUND;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Used to indicate an internal server error that does not fall under all other errors
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @class InternalServerErrorException
 | 
				
			||||||
 | 
					 * @extends {ExceptionBase}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class InternalServerErrorException extends ExceptionBase {
 | 
				
			||||||
 | 
					  static readonly message = 'Internal server error';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(message = InternalServerErrorException.message) {
 | 
				
			||||||
 | 
					    super(message);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  readonly code = INTERNAL_SERVER_ERROR;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Used to indicate a database error
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @class DatabaseErrorException
 | 
				
			||||||
 | 
					 * @extends {ExceptionBase}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class DatabaseErrorException extends ExceptionBase {
 | 
				
			||||||
 | 
					  static readonly message = 'Database error';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(message = DatabaseErrorException.message) {
 | 
				
			||||||
 | 
					    super(message);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  readonly code = DATABASE_ERROR;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					export * from './exception.base';
 | 
				
			||||||
 | 
					export * from './exception.codes';
 | 
				
			||||||
 | 
					export * from './exceptions';
 | 
				
			||||||
 | 
					export * from './rpc-exception.codes.enum';
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,19 @@
 | 
				
			||||||
 | 
					export enum RpcExceptionCode {
 | 
				
			||||||
 | 
					  OK = 0,
 | 
				
			||||||
 | 
					  CANCELLED = 1,
 | 
				
			||||||
 | 
					  UNKNOWN = 2,
 | 
				
			||||||
 | 
					  INVALID_ARGUMENT = 3,
 | 
				
			||||||
 | 
					  DEADLINE_EXCEEDED = 4,
 | 
				
			||||||
 | 
					  NOT_FOUND = 5,
 | 
				
			||||||
 | 
					  ALREADY_EXISTS = 6,
 | 
				
			||||||
 | 
					  PERMISSION_DENIED = 7,
 | 
				
			||||||
 | 
					  RESOURCE_EXHAUSTED = 8,
 | 
				
			||||||
 | 
					  FAILED_PRECONDITION = 9,
 | 
				
			||||||
 | 
					  ABORTED = 10,
 | 
				
			||||||
 | 
					  OUT_OF_RANGE = 11,
 | 
				
			||||||
 | 
					  UNIMPLEMENTED = 12,
 | 
				
			||||||
 | 
					  INTERNAL = 13,
 | 
				
			||||||
 | 
					  UNAVAILABLE = 14,
 | 
				
			||||||
 | 
					  DATA_LOSS = 15,
 | 
				
			||||||
 | 
					  UNAUTHENTICATED = 16,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,55 @@
 | 
				
			||||||
 | 
					export class Guard {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Checks if value is empty. Accepts strings, numbers, booleans, objects and arrays.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static isEmpty(value: unknown): boolean {
 | 
				
			||||||
 | 
					    if (typeof value === 'number' || typeof value === 'boolean') {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (typeof value === 'undefined' || value === null) {
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (value instanceof Date) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (value instanceof Object && !Object.keys(value).length) {
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (Array.isArray(value)) {
 | 
				
			||||||
 | 
					      if (value.length === 0) {
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (value.every((item) => Guard.isEmpty(item))) {
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (value === '') {
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Checks length range of a provided number/string/array
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static lengthIsBetween(
 | 
				
			||||||
 | 
					    value: number | string | Array<unknown>,
 | 
				
			||||||
 | 
					    min: number,
 | 
				
			||||||
 | 
					    max: number,
 | 
				
			||||||
 | 
					  ): boolean {
 | 
				
			||||||
 | 
					    if (Guard.isEmpty(value)) {
 | 
				
			||||||
 | 
					      throw new Error(
 | 
				
			||||||
 | 
					        'Cannot check length of a value. Provided value is empty',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const valueLength =
 | 
				
			||||||
 | 
					      typeof value === 'number'
 | 
				
			||||||
 | 
					        ? Number(value).toString().length
 | 
				
			||||||
 | 
					        : value.length;
 | 
				
			||||||
 | 
					    if (valueLength >= min && valueLength <= max) {
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					export interface LoggerPort {
 | 
				
			||||||
 | 
					  log(message: string, ...meta: unknown[]): void;
 | 
				
			||||||
 | 
					  error(message: string, trace?: unknown, ...meta: unknown[]): void;
 | 
				
			||||||
 | 
					  warn(message: string, ...meta: unknown[]): void;
 | 
				
			||||||
 | 
					  debug(message: string, ...meta: unknown[]): void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,3 @@
 | 
				
			||||||
export interface IPublishMessage {
 | 
					export interface MessagePublisherPort {
 | 
				
			||||||
  publish(routingKey: string, message: string): void;
 | 
					  publish(routingKey: string, message: string): void;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					export interface PrismaRepositoryPort<Entity> {
 | 
				
			||||||
 | 
					  findUnique(options: any): Promise<Entity>;
 | 
				
			||||||
 | 
					  create(entity: any): Promise<Entity>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface PrismaRawRepositoryPort {
 | 
				
			||||||
 | 
					  $queryRaw<T = unknown>(
 | 
				
			||||||
 | 
					    query: TemplateStringsArray,
 | 
				
			||||||
 | 
					    ...values: any[]
 | 
				
			||||||
 | 
					  ): Promise<T>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,287 @@
 | 
				
			||||||
 | 
					import { ResponseBase } from '@libs/api/response.base';
 | 
				
			||||||
 | 
					import { PrismaRepositoryBase } from '@libs/db/prisma-repository.base';
 | 
				
			||||||
 | 
					import { PrismaService } from '@libs/db/prisma.service';
 | 
				
			||||||
 | 
					import { AggregateID, AggregateRoot, Mapper, RepositoryPort } from '@libs/ddd';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ConflictException,
 | 
				
			||||||
 | 
					  DatabaseErrorException,
 | 
				
			||||||
 | 
					  NotFoundException,
 | 
				
			||||||
 | 
					} from '@libs/exceptions';
 | 
				
			||||||
 | 
					import { Injectable, Logger } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { EventEmitter2 } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					import { Prisma } from '@prisma/client';
 | 
				
			||||||
 | 
					import { v4 } from 'uuid';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface FakeProps {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface CreateFakeProps {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FakeEntity extends AggregateRoot<FakeProps> {
 | 
				
			||||||
 | 
					  protected readonly _id: AggregateID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static create = (create: CreateFakeProps): FakeEntity => {
 | 
				
			||||||
 | 
					    const id = v4();
 | 
				
			||||||
 | 
					    const props: FakeProps = { ...create };
 | 
				
			||||||
 | 
					    const fake = new FakeEntity({ id, props });
 | 
				
			||||||
 | 
					    return fake;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validate(): void {
 | 
				
			||||||
 | 
					    // not implemented
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FakeModel = {
 | 
				
			||||||
 | 
					  uuid: string;
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  createdAt: Date;
 | 
				
			||||||
 | 
					  updatedAt: Date;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FakeRepositoryPort = RepositoryPort<FakeEntity>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FakeResponseDto extends ResponseBase {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const fakeRecord: FakeModel = {
 | 
				
			||||||
 | 
					  uuid: 'd567ea3b-4981-43c9-9449-a409b5fa9fed',
 | 
				
			||||||
 | 
					  name: 'fakeName',
 | 
				
			||||||
 | 
					  createdAt: new Date('2023-06-28T16:02:00Z'),
 | 
				
			||||||
 | 
					  updatedAt: new Date('2023-06-28T16:02:00Z'),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let recordId = 2;
 | 
				
			||||||
 | 
					const recordUuid = 'uuid-';
 | 
				
			||||||
 | 
					const recordName = 'fakeName-';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const createRandomRecord = (): FakeModel => {
 | 
				
			||||||
 | 
					  const fakeRecord: FakeModel = {
 | 
				
			||||||
 | 
					    uuid: `${recordUuid}${recordId}`,
 | 
				
			||||||
 | 
					    name: `${recordName}${recordId}`,
 | 
				
			||||||
 | 
					    createdAt: new Date('2023-06-30T08:00:00Z'),
 | 
				
			||||||
 | 
					    updatedAt: new Date('2023-06-30T08:00:00Z'),
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  recordId++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return fakeRecord;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const fakeRecords: FakeModel[] = [];
 | 
				
			||||||
 | 
					Array.from({ length: 10 }).forEach(() => {
 | 
				
			||||||
 | 
					  fakeRecords.push(createRandomRecord());
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					class FakeMapper
 | 
				
			||||||
 | 
					  implements Mapper<FakeEntity, FakeModel, FakeModel, FakeResponseDto>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  toPersistence = (entity: FakeEntity): FakeModel => {
 | 
				
			||||||
 | 
					    const copy = entity.getProps();
 | 
				
			||||||
 | 
					    const record: FakeModel = {
 | 
				
			||||||
 | 
					      uuid: copy.id,
 | 
				
			||||||
 | 
					      name: copy.name,
 | 
				
			||||||
 | 
					      createdAt: copy.createdAt,
 | 
				
			||||||
 | 
					      updatedAt: copy.updatedAt,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    return record;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  toDomain = (record: FakeModel): FakeEntity => {
 | 
				
			||||||
 | 
					    const entity = new FakeEntity({
 | 
				
			||||||
 | 
					      id: record.uuid,
 | 
				
			||||||
 | 
					      createdAt: new Date(record.createdAt),
 | 
				
			||||||
 | 
					      updatedAt: new Date(record.updatedAt),
 | 
				
			||||||
 | 
					      props: {
 | 
				
			||||||
 | 
					        name: record.name,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return entity;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  toResponse = (entity: FakeEntity): FakeResponseDto => {
 | 
				
			||||||
 | 
					    const props = entity.getProps();
 | 
				
			||||||
 | 
					    const response = new FakeResponseDto(entity);
 | 
				
			||||||
 | 
					    response.name = props.name;
 | 
				
			||||||
 | 
					    return response;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					class FakePrismaService extends PrismaService {
 | 
				
			||||||
 | 
					  fake: any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockPrismaService = {
 | 
				
			||||||
 | 
					  $queryRaw: jest
 | 
				
			||||||
 | 
					    .fn()
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementation(() => {
 | 
				
			||||||
 | 
					      throw new Prisma.PrismaClientKnownRequestError('Database unavailable', {
 | 
				
			||||||
 | 
					        code: 'code',
 | 
				
			||||||
 | 
					        clientVersion: 'version',
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new Error();
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  fake: {
 | 
				
			||||||
 | 
					    create: jest
 | 
				
			||||||
 | 
					      .fn()
 | 
				
			||||||
 | 
					      .mockResolvedValueOnce(fakeRecord)
 | 
				
			||||||
 | 
					      .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					        throw new Prisma.PrismaClientKnownRequestError('Already exists', {
 | 
				
			||||||
 | 
					          code: 'code',
 | 
				
			||||||
 | 
					          clientVersion: 'version',
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					        throw new Error('An unknown error');
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    findUnique: jest.fn().mockImplementation(async (params?: any) => {
 | 
				
			||||||
 | 
					      let record: FakeModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (params?.where?.uuid) {
 | 
				
			||||||
 | 
					        record = fakeRecords.find(
 | 
				
			||||||
 | 
					          (record) => record.uuid === params?.where?.uuid,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!record && params?.where?.uuid == 'uuid-triggering-error') {
 | 
				
			||||||
 | 
					        throw new Prisma.PrismaClientKnownRequestError('unknown request', {
 | 
				
			||||||
 | 
					          code: 'code',
 | 
				
			||||||
 | 
					          clientVersion: 'version',
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return record;
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					class FakeRepository
 | 
				
			||||||
 | 
					  extends PrismaRepositoryBase<FakeEntity, FakeModel, FakeModel>
 | 
				
			||||||
 | 
					  implements FakeRepositoryPort
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    prisma: FakePrismaService,
 | 
				
			||||||
 | 
					    mapper: FakeMapper,
 | 
				
			||||||
 | 
					    eventEmitter: EventEmitter2,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    super(
 | 
				
			||||||
 | 
					      prisma.fake,
 | 
				
			||||||
 | 
					      prisma,
 | 
				
			||||||
 | 
					      mapper,
 | 
				
			||||||
 | 
					      eventEmitter,
 | 
				
			||||||
 | 
					      new Logger(FakeRepository.name),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('PrismaRepositoryBase', () => {
 | 
				
			||||||
 | 
					  let fakeRepository: FakeRepository;
 | 
				
			||||||
 | 
					  let prisma: FakePrismaService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        EventEmitter2,
 | 
				
			||||||
 | 
					        FakeRepository,
 | 
				
			||||||
 | 
					        FakeMapper,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: FakePrismaService,
 | 
				
			||||||
 | 
					          useValue: mockPrismaService,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fakeRepository = module.get<FakeRepository>(FakeRepository);
 | 
				
			||||||
 | 
					    prisma = module.get<FakePrismaService>(FakePrismaService);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(fakeRepository).toBeDefined();
 | 
				
			||||||
 | 
					    expect(prisma).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('insert', () => {
 | 
				
			||||||
 | 
					    it('should create a record', async () => {
 | 
				
			||||||
 | 
					      jest.spyOn(prisma.fake, 'create');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await fakeRepository.insert(
 | 
				
			||||||
 | 
					        FakeEntity.create({
 | 
				
			||||||
 | 
					          name: 'someFakeName',
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(prisma.fake.create).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should throw a ConflictException if record already exists', async () => {
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        fakeRepository.insert(
 | 
				
			||||||
 | 
					          FakeEntity.create({
 | 
				
			||||||
 | 
					            name: 'someFakeName',
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(ConflictException);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should throw an Error if an error occurs', async () => {
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        fakeRepository.insert(
 | 
				
			||||||
 | 
					          FakeEntity.create({
 | 
				
			||||||
 | 
					            name: 'someFakeName',
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(Error);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('findOneById', () => {
 | 
				
			||||||
 | 
					    it('should find a record by its id', async () => {
 | 
				
			||||||
 | 
					      const record = await fakeRepository.findOneById('uuid-3');
 | 
				
			||||||
 | 
					      expect(record.getProps().name).toBe('fakeName-3');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should throw an Error for client error', async () => {
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        fakeRepository.findOneById('uuid-triggering-error'),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(Error);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should throw a NotFoundException if id is not found', async () => {
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        fakeRepository.findOneById('wrong-id'),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(NotFoundException);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('healthCheck', () => {
 | 
				
			||||||
 | 
					    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(
 | 
				
			||||||
 | 
					        DatabaseErrorException,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should throw a DatabaseErrorException if an error occurs', async () => {
 | 
				
			||||||
 | 
					      await expect(fakeRepository.healthCheck()).rejects.toBeInstanceOf(
 | 
				
			||||||
 | 
					        DatabaseErrorException,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,76 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  AggregateID,
 | 
				
			||||||
 | 
					  AggregateRoot,
 | 
				
			||||||
 | 
					  DomainEvent,
 | 
				
			||||||
 | 
					  DomainEventProps,
 | 
				
			||||||
 | 
					} from '@libs/ddd';
 | 
				
			||||||
 | 
					import { EventEmitter2 } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import { v4 } from 'uuid';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface FakeProps {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface CreateFakeProps {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FakeRecordCreatedDomainEvent extends DomainEvent {
 | 
				
			||||||
 | 
					  readonly name: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: DomainEventProps<FakeRecordCreatedDomainEvent>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.name = props.name;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FakeEntity extends AggregateRoot<FakeProps> {
 | 
				
			||||||
 | 
					  protected readonly _id: AggregateID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static create = (create: CreateFakeProps): FakeEntity => {
 | 
				
			||||||
 | 
					    const id = v4();
 | 
				
			||||||
 | 
					    const props: FakeProps = { ...create };
 | 
				
			||||||
 | 
					    const fake = new FakeEntity({ id, props });
 | 
				
			||||||
 | 
					    fake.addEvent(
 | 
				
			||||||
 | 
					      new FakeRecordCreatedDomainEvent({
 | 
				
			||||||
 | 
					        aggregateId: id,
 | 
				
			||||||
 | 
					        name: props.name,
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    return fake;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validate(): void {
 | 
				
			||||||
 | 
					    // not implemented
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockLogger = {
 | 
				
			||||||
 | 
					  debug: jest.fn(),
 | 
				
			||||||
 | 
					  log: jest.fn(),
 | 
				
			||||||
 | 
					  error: jest.fn(),
 | 
				
			||||||
 | 
					  warn: jest.fn(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('AggregateRoot Base', () => {
 | 
				
			||||||
 | 
					  it('should define an aggregate root based object instance', () => {
 | 
				
			||||||
 | 
					    const fakeInstance = FakeEntity.create({
 | 
				
			||||||
 | 
					      name: 'someFakeName',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(fakeInstance).toBeDefined();
 | 
				
			||||||
 | 
					    expect(fakeInstance.domainEvents.length).toBe(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should publish domain events', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockLogger, 'debug');
 | 
				
			||||||
 | 
					    const eventEmitter = new EventEmitter2();
 | 
				
			||||||
 | 
					    jest.spyOn(eventEmitter, 'emitAsync');
 | 
				
			||||||
 | 
					    const fakeInstance = FakeEntity.create({
 | 
				
			||||||
 | 
					      name: 'someFakeName',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await fakeInstance.publishEvents(mockLogger, eventEmitter);
 | 
				
			||||||
 | 
					    expect(mockLogger.debug).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					    expect(eventEmitter.emitAsync).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					    expect(fakeInstance.domainEvents.length).toBe(0);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,47 @@
 | 
				
			||||||
 | 
					import { Command, CommandProps } from '@libs/ddd';
 | 
				
			||||||
 | 
					import { ArgumentNotProvidedException } from '@libs/exceptions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FakeCommand extends Command {
 | 
				
			||||||
 | 
					  readonly name: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: CommandProps<FakeCommand>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.name = props.name;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BadFakeCommand extends Command {
 | 
				
			||||||
 | 
					  constructor(props: CommandProps<BadFakeCommand>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Command Base', () => {
 | 
				
			||||||
 | 
					  it('should define a command based object instance', () => {
 | 
				
			||||||
 | 
					    const fakeCommand = new FakeCommand({ name: 'fakeName' });
 | 
				
			||||||
 | 
					    expect(fakeCommand).toBeDefined();
 | 
				
			||||||
 | 
					    expect(fakeCommand.id.length).toBe(36);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should define a command based object instance with a provided id', () => {
 | 
				
			||||||
 | 
					    const fakeCommand = new FakeCommand({ id: 'some-id', name: 'fakeName' });
 | 
				
			||||||
 | 
					    expect(fakeCommand.id).toBe('some-id');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should define a command based object instance with metadata', () => {
 | 
				
			||||||
 | 
					    const fakeCommand = new FakeCommand({
 | 
				
			||||||
 | 
					      name: 'fakeName',
 | 
				
			||||||
 | 
					      metadata: {
 | 
				
			||||||
 | 
					        correlationId: 'some-correlation-id',
 | 
				
			||||||
 | 
					        causationId: 'some-causation-id',
 | 
				
			||||||
 | 
					        userId: 'some-user-id',
 | 
				
			||||||
 | 
					        timestamp: new Date('2023-06-28T05:00:00Z').getTime(),
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(fakeCommand.metadata.timestamp).toBe(1687928400000);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should throw an exception if props are empty', () => {
 | 
				
			||||||
 | 
					    expect(() => new BadFakeCommand({})).toThrow(ArgumentNotProvidedException);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					import { DomainEvent, DomainEventProps } from '@libs/ddd';
 | 
				
			||||||
 | 
					import { ArgumentNotProvidedException } from '@libs/exceptions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FakeDomainEvent extends DomainEvent {
 | 
				
			||||||
 | 
					  readonly name: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: DomainEventProps<FakeDomainEvent>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.name = props.name;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('DomainEvent Base', () => {
 | 
				
			||||||
 | 
					  it('should define a domain event based object instance', () => {
 | 
				
			||||||
 | 
					    const fakeDomainEvent = new FakeDomainEvent({
 | 
				
			||||||
 | 
					      aggregateId: 'some-id',
 | 
				
			||||||
 | 
					      name: 'some-name',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(fakeDomainEvent).toBeDefined();
 | 
				
			||||||
 | 
					    expect(fakeDomainEvent.id.length).toBe(36);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should define a domain event based object instance with metadata', () => {
 | 
				
			||||||
 | 
					    const fakeDomainEvent = new FakeDomainEvent({
 | 
				
			||||||
 | 
					      aggregateId: 'some-id',
 | 
				
			||||||
 | 
					      name: 'some-name',
 | 
				
			||||||
 | 
					      metadata: {
 | 
				
			||||||
 | 
					        correlationId: 'some-correlation-id',
 | 
				
			||||||
 | 
					        causationId: 'some-causation-id',
 | 
				
			||||||
 | 
					        userId: 'some-user-id',
 | 
				
			||||||
 | 
					        timestamp: new Date('2023-06-28T05:00:00Z').getTime(),
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(fakeDomainEvent.metadata.timestamp).toBe(1687928400000);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  it('should throw an exception if props are empty', () => {
 | 
				
			||||||
 | 
					    const emptyProps: DomainEventProps<FakeDomainEvent> = undefined;
 | 
				
			||||||
 | 
					    expect(() => new FakeDomainEvent(emptyProps)).toThrow(
 | 
				
			||||||
 | 
					      ArgumentNotProvidedException,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,209 @@
 | 
				
			||||||
 | 
					import { Entity } from '@libs/ddd';
 | 
				
			||||||
 | 
					import { ArgumentOutOfRangeException } from '@libs/exceptions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface FakeProps {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FakeEntity extends Entity<FakeProps> {
 | 
				
			||||||
 | 
					  protected _id: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validate(): void {
 | 
				
			||||||
 | 
					    // not implemented
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Entity Base', () => {
 | 
				
			||||||
 | 
					  it('should define an entity based object instance', () => {
 | 
				
			||||||
 | 
					    const fakeInstance = new FakeEntity({
 | 
				
			||||||
 | 
					      id: 'some-id',
 | 
				
			||||||
 | 
					      props: {
 | 
				
			||||||
 | 
					        name: 'some-name',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(fakeInstance).toBeDefined();
 | 
				
			||||||
 | 
					    expect(fakeInstance.id).toBe('some-id');
 | 
				
			||||||
 | 
					    expect(fakeInstance.createdAt).toBeInstanceOf(Date);
 | 
				
			||||||
 | 
					    expect(fakeInstance.updatedAt).toBeInstanceOf(Date);
 | 
				
			||||||
 | 
					    expect(FakeEntity.isEntity(fakeInstance)).toBeTruthy();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should define an entity with given created and updated dates', () => {
 | 
				
			||||||
 | 
					    const fakeInstance = new FakeEntity({
 | 
				
			||||||
 | 
					      id: 'some-id',
 | 
				
			||||||
 | 
					      createdAt: new Date('2023-06-28T05:00:00Z'),
 | 
				
			||||||
 | 
					      updatedAt: new Date('2023-06-28T06:00:00Z'),
 | 
				
			||||||
 | 
					      props: {
 | 
				
			||||||
 | 
					        name: 'some-name',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(fakeInstance.createdAt.getUTCHours()).toBe(5);
 | 
				
			||||||
 | 
					    expect(fakeInstance.updatedAt.getUTCHours()).toBe(6);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should compare entities', () => {
 | 
				
			||||||
 | 
					    const fakeInstance = new FakeEntity({
 | 
				
			||||||
 | 
					      id: 'some-id',
 | 
				
			||||||
 | 
					      props: {
 | 
				
			||||||
 | 
					        name: 'some-name',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const fakeInstanceClone = new FakeEntity({
 | 
				
			||||||
 | 
					      id: 'some-id',
 | 
				
			||||||
 | 
					      props: {
 | 
				
			||||||
 | 
					        name: 'some-name',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const fakeInstanceNotReallyClone = new FakeEntity({
 | 
				
			||||||
 | 
					      id: 'some-slightly-different-id',
 | 
				
			||||||
 | 
					      props: {
 | 
				
			||||||
 | 
					        name: 'some-name',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const undefinedFakeInstance: FakeEntity = undefined;
 | 
				
			||||||
 | 
					    expect(fakeInstance.equals(undefinedFakeInstance)).toBeFalsy();
 | 
				
			||||||
 | 
					    expect(fakeInstance.equals(fakeInstance)).toBeTruthy();
 | 
				
			||||||
 | 
					    expect(fakeInstance.equals(fakeInstanceClone)).toBeTruthy();
 | 
				
			||||||
 | 
					    expect(fakeInstance.equals(fakeInstanceNotReallyClone)).toBeFalsy();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should convert entity to plain object', () => {
 | 
				
			||||||
 | 
					    const fakeInstance = new FakeEntity({
 | 
				
			||||||
 | 
					      id: 'some-id',
 | 
				
			||||||
 | 
					      createdAt: new Date('2023-06-28T05:00:00Z'),
 | 
				
			||||||
 | 
					      updatedAt: new Date('2023-06-28T06:00:00Z'),
 | 
				
			||||||
 | 
					      props: {
 | 
				
			||||||
 | 
					        name: 'some-name',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(fakeInstance.toObject()).toEqual({
 | 
				
			||||||
 | 
					      id: 'some-id',
 | 
				
			||||||
 | 
					      createdAt: new Date('2023-06-28T05:00:00.000Z'),
 | 
				
			||||||
 | 
					      updatedAt: new Date('2023-06-28T06:00:00.000Z'),
 | 
				
			||||||
 | 
					      name: 'some-name',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should throw an exception if props number is too high', () => {
 | 
				
			||||||
 | 
					    interface BigFakeProps {
 | 
				
			||||||
 | 
					      prop1: string;
 | 
				
			||||||
 | 
					      prop2: string;
 | 
				
			||||||
 | 
					      prop3: string;
 | 
				
			||||||
 | 
					      prop4: string;
 | 
				
			||||||
 | 
					      prop5: string;
 | 
				
			||||||
 | 
					      prop6: string;
 | 
				
			||||||
 | 
					      prop7: string;
 | 
				
			||||||
 | 
					      prop8: string;
 | 
				
			||||||
 | 
					      prop9: string;
 | 
				
			||||||
 | 
					      prop10: string;
 | 
				
			||||||
 | 
					      prop11: string;
 | 
				
			||||||
 | 
					      prop12: string;
 | 
				
			||||||
 | 
					      prop13: string;
 | 
				
			||||||
 | 
					      prop14: string;
 | 
				
			||||||
 | 
					      prop15: string;
 | 
				
			||||||
 | 
					      prop16: string;
 | 
				
			||||||
 | 
					      prop17: string;
 | 
				
			||||||
 | 
					      prop18: string;
 | 
				
			||||||
 | 
					      prop19: string;
 | 
				
			||||||
 | 
					      prop20: string;
 | 
				
			||||||
 | 
					      prop21: string;
 | 
				
			||||||
 | 
					      prop22: string;
 | 
				
			||||||
 | 
					      prop23: string;
 | 
				
			||||||
 | 
					      prop24: string;
 | 
				
			||||||
 | 
					      prop25: string;
 | 
				
			||||||
 | 
					      prop26: string;
 | 
				
			||||||
 | 
					      prop27: string;
 | 
				
			||||||
 | 
					      prop28: string;
 | 
				
			||||||
 | 
					      prop29: string;
 | 
				
			||||||
 | 
					      prop30: string;
 | 
				
			||||||
 | 
					      prop31: string;
 | 
				
			||||||
 | 
					      prop32: string;
 | 
				
			||||||
 | 
					      prop33: string;
 | 
				
			||||||
 | 
					      prop34: string;
 | 
				
			||||||
 | 
					      prop35: string;
 | 
				
			||||||
 | 
					      prop36: string;
 | 
				
			||||||
 | 
					      prop37: string;
 | 
				
			||||||
 | 
					      prop38: string;
 | 
				
			||||||
 | 
					      prop39: string;
 | 
				
			||||||
 | 
					      prop40: string;
 | 
				
			||||||
 | 
					      prop41: string;
 | 
				
			||||||
 | 
					      prop42: string;
 | 
				
			||||||
 | 
					      prop43: string;
 | 
				
			||||||
 | 
					      prop44: string;
 | 
				
			||||||
 | 
					      prop45: string;
 | 
				
			||||||
 | 
					      prop46: string;
 | 
				
			||||||
 | 
					      prop47: string;
 | 
				
			||||||
 | 
					      prop48: string;
 | 
				
			||||||
 | 
					      prop49: string;
 | 
				
			||||||
 | 
					      prop50: string;
 | 
				
			||||||
 | 
					      prop51: string;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class BigFakeEntity extends Entity<BigFakeProps> {
 | 
				
			||||||
 | 
					      protected _id: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      validate(): void {
 | 
				
			||||||
 | 
					        // not implemented
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      () =>
 | 
				
			||||||
 | 
					        new BigFakeEntity({
 | 
				
			||||||
 | 
					          id: 'some-id',
 | 
				
			||||||
 | 
					          props: {
 | 
				
			||||||
 | 
					            prop1: 'some-name',
 | 
				
			||||||
 | 
					            prop2: 'some-name',
 | 
				
			||||||
 | 
					            prop3: 'some-name',
 | 
				
			||||||
 | 
					            prop4: 'some-name',
 | 
				
			||||||
 | 
					            prop5: 'some-name',
 | 
				
			||||||
 | 
					            prop6: 'some-name',
 | 
				
			||||||
 | 
					            prop7: 'some-name',
 | 
				
			||||||
 | 
					            prop8: 'some-name',
 | 
				
			||||||
 | 
					            prop9: 'some-name',
 | 
				
			||||||
 | 
					            prop10: 'some-name',
 | 
				
			||||||
 | 
					            prop11: 'some-name',
 | 
				
			||||||
 | 
					            prop12: 'some-name',
 | 
				
			||||||
 | 
					            prop13: 'some-name',
 | 
				
			||||||
 | 
					            prop14: 'some-name',
 | 
				
			||||||
 | 
					            prop15: 'some-name',
 | 
				
			||||||
 | 
					            prop16: 'some-name',
 | 
				
			||||||
 | 
					            prop17: 'some-name',
 | 
				
			||||||
 | 
					            prop18: 'some-name',
 | 
				
			||||||
 | 
					            prop19: 'some-name',
 | 
				
			||||||
 | 
					            prop20: 'some-name',
 | 
				
			||||||
 | 
					            prop21: 'some-name',
 | 
				
			||||||
 | 
					            prop22: 'some-name',
 | 
				
			||||||
 | 
					            prop23: 'some-name',
 | 
				
			||||||
 | 
					            prop24: 'some-name',
 | 
				
			||||||
 | 
					            prop25: 'some-name',
 | 
				
			||||||
 | 
					            prop26: 'some-name',
 | 
				
			||||||
 | 
					            prop27: 'some-name',
 | 
				
			||||||
 | 
					            prop28: 'some-name',
 | 
				
			||||||
 | 
					            prop29: 'some-name',
 | 
				
			||||||
 | 
					            prop30: 'some-name',
 | 
				
			||||||
 | 
					            prop31: 'some-name',
 | 
				
			||||||
 | 
					            prop32: 'some-name',
 | 
				
			||||||
 | 
					            prop33: 'some-name',
 | 
				
			||||||
 | 
					            prop34: 'some-name',
 | 
				
			||||||
 | 
					            prop35: 'some-name',
 | 
				
			||||||
 | 
					            prop36: 'some-name',
 | 
				
			||||||
 | 
					            prop37: 'some-name',
 | 
				
			||||||
 | 
					            prop38: 'some-name',
 | 
				
			||||||
 | 
					            prop39: 'some-name',
 | 
				
			||||||
 | 
					            prop40: 'some-name',
 | 
				
			||||||
 | 
					            prop41: 'some-name',
 | 
				
			||||||
 | 
					            prop42: 'some-name',
 | 
				
			||||||
 | 
					            prop43: 'some-name',
 | 
				
			||||||
 | 
					            prop44: 'some-name',
 | 
				
			||||||
 | 
					            prop45: 'some-name',
 | 
				
			||||||
 | 
					            prop46: 'some-name',
 | 
				
			||||||
 | 
					            prop47: 'some-name',
 | 
				
			||||||
 | 
					            prop48: 'some-name',
 | 
				
			||||||
 | 
					            prop49: 'some-name',
 | 
				
			||||||
 | 
					            prop50: 'some-name',
 | 
				
			||||||
 | 
					            prop51: 'some-name',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					    ).toThrow(ArgumentOutOfRangeException);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  PaginatedParams,
 | 
				
			||||||
 | 
					  PaginatedQueryBase,
 | 
				
			||||||
 | 
					  QueryBase,
 | 
				
			||||||
 | 
					} from '@libs/ddd/query.base';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FakeQuery extends QueryBase {
 | 
				
			||||||
 | 
					  readonly id: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(id: string) {
 | 
				
			||||||
 | 
					    super();
 | 
				
			||||||
 | 
					    this.id = id;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Query Base', () => {
 | 
				
			||||||
 | 
					  it('should define a query based object instance', () => {
 | 
				
			||||||
 | 
					    const fakeQuery = new FakeQuery('some-id');
 | 
				
			||||||
 | 
					    expect(fakeQuery).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FakePaginatedQuery extends PaginatedQueryBase {
 | 
				
			||||||
 | 
					  readonly id: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: PaginatedParams<FakePaginatedQuery>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.id = props.id;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Paginated Query Base', () => {
 | 
				
			||||||
 | 
					  it('should define a paginated query based object instance', () => {
 | 
				
			||||||
 | 
					    const fakePaginatedQuery = new FakePaginatedQuery({
 | 
				
			||||||
 | 
					      id: 'some-id',
 | 
				
			||||||
 | 
					      page: 1,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(fakePaginatedQuery).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					import { ValueObject } from '@libs/ddd';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface FakeProps {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FakeValueObject extends ValueObject<FakeProps> {
 | 
				
			||||||
 | 
					  get name(): string {
 | 
				
			||||||
 | 
					    return this.props.name;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					  protected validate(props: FakeProps): void {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Value Object Base', () => {
 | 
				
			||||||
 | 
					  it('should create a base value object', () => {
 | 
				
			||||||
 | 
					    const fakeValueObject = new FakeValueObject({ name: 'fakeName' });
 | 
				
			||||||
 | 
					    expect(fakeValueObject).toBeDefined();
 | 
				
			||||||
 | 
					    expect(ValueObject.isValueObject(fakeValueObject)).toBeTruthy();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should compare value objects', () => {
 | 
				
			||||||
 | 
					    const fakeValueObject = new FakeValueObject({ name: 'fakeName' });
 | 
				
			||||||
 | 
					    const fakeValueObjectClone = new FakeValueObject({ name: 'fakeName' });
 | 
				
			||||||
 | 
					    const undefinedFakeValueObject: FakeValueObject = undefined;
 | 
				
			||||||
 | 
					    const nullFakeValueObject: FakeValueObject = null;
 | 
				
			||||||
 | 
					    expect(fakeValueObject.equals(undefinedFakeValueObject)).toBeFalsy();
 | 
				
			||||||
 | 
					    expect(fakeValueObject.equals(nullFakeValueObject)).toBeFalsy();
 | 
				
			||||||
 | 
					    expect(fakeValueObject.equals(fakeValueObject)).toBeTruthy();
 | 
				
			||||||
 | 
					    expect(fakeValueObject.equals(fakeValueObjectClone)).toBeTruthy();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should unpack value object props', () => {
 | 
				
			||||||
 | 
					    const fakeValueObject = new FakeValueObject({ name: 'fakeName' });
 | 
				
			||||||
 | 
					    expect(fakeValueObject.unpack()).toEqual({
 | 
				
			||||||
 | 
					      name: 'fakeName',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,65 @@
 | 
				
			||||||
 | 
					import { Guard } from '@libs/guard';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Guard', () => {
 | 
				
			||||||
 | 
					  describe('isEmpty', () => {
 | 
				
			||||||
 | 
					    it('should return false for a number', () => {
 | 
				
			||||||
 | 
					      expect(Guard.isEmpty(1)).toBeFalsy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return false for a falsy boolean', () => {
 | 
				
			||||||
 | 
					      expect(Guard.isEmpty(false)).toBeFalsy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return false for a truthy boolean', () => {
 | 
				
			||||||
 | 
					      expect(Guard.isEmpty(true)).toBeFalsy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return true for undefined', () => {
 | 
				
			||||||
 | 
					      expect(Guard.isEmpty(undefined)).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return true for null', () => {
 | 
				
			||||||
 | 
					      expect(Guard.isEmpty(null)).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return false for a Date', () => {
 | 
				
			||||||
 | 
					      expect(Guard.isEmpty(new Date('2023-06-28'))).toBeFalsy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return false for an object with keys', () => {
 | 
				
			||||||
 | 
					      expect(Guard.isEmpty({ key: 'value' })).toBeFalsy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return true for an object without keys', () => {
 | 
				
			||||||
 | 
					      expect(Guard.isEmpty({})).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return true for an array without values', () => {
 | 
				
			||||||
 | 
					      expect(Guard.isEmpty([])).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return true for an array with only empty values', () => {
 | 
				
			||||||
 | 
					      expect(Guard.isEmpty([null, undefined])).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return false for an array with some empty values', () => {
 | 
				
			||||||
 | 
					      expect(Guard.isEmpty([1, null, undefined])).toBeFalsy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return true for an empty string', () => {
 | 
				
			||||||
 | 
					      expect(Guard.isEmpty('')).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  describe('lengthIsBetween', () => {
 | 
				
			||||||
 | 
					    it('should return true for a string in the range', () => {
 | 
				
			||||||
 | 
					      expect(Guard.lengthIsBetween('test', 0, 4)).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return true for a number in the range', () => {
 | 
				
			||||||
 | 
					      expect(Guard.lengthIsBetween(2, 0, 4)).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return true for an array with number of elements in the range', () => {
 | 
				
			||||||
 | 
					      expect(Guard.lengthIsBetween([1, 2, 3], 0, 4)).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return false for a string not in the range', () => {
 | 
				
			||||||
 | 
					      expect(Guard.lengthIsBetween('test', 5, 9)).toBeFalsy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return false for a number not in the range', () => {
 | 
				
			||||||
 | 
					      expect(Guard.lengthIsBetween(2, 3, 6)).toBeFalsy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should return false for an array with number of elements not in the range', () => {
 | 
				
			||||||
 | 
					      expect(Guard.lengthIsBetween([1, 2, 3], 10, 12)).toBeFalsy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should throw an exception if value is empty', () => {
 | 
				
			||||||
 | 
					      expect(() => Guard.lengthIsBetween(undefined, 0, 4)).toThrow();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import { ArgumentMetadata } from '@nestjs/common';
 | 
					import { ArgumentMetadata } from '@nestjs/common';
 | 
				
			||||||
import { RpcValidationPipe } from '../../pipes/rpc.validation-pipe';
 | 
					import { FindAdByIdRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/find-ad-by-id.request.dto';
 | 
				
			||||||
import { FindAdByUuidRequest } from '../../../modules/ad/domain/dtos/find-ad-by-uuid.request';
 | 
					import { RpcValidationPipe } from '@libs/utils/pipes/rpc.validation-pipe';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('RpcValidationPipe', () => {
 | 
					describe('RpcValidationPipe', () => {
 | 
				
			||||||
  it('should not validate request', async () => {
 | 
					  it('should not validate request', async () => {
 | 
				
			||||||
| 
						 | 
					@ -10,10 +10,10 @@ describe('RpcValidationPipe', () => {
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    const metadata: ArgumentMetadata = {
 | 
					    const metadata: ArgumentMetadata = {
 | 
				
			||||||
      type: 'body',
 | 
					      type: 'body',
 | 
				
			||||||
      metatype: FindAdByUuidRequest,
 | 
					      metatype: FindAdByIdRequestDto,
 | 
				
			||||||
      data: '',
 | 
					      data: '',
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    await target.transform(<FindAdByUuidRequest>{}, metadata).catch((err) => {
 | 
					    await target.transform(<FindAdByIdRequestDto>{}, metadata).catch((err) => {
 | 
				
			||||||
      expect(err.message).toEqual('Rpc Exception');
 | 
					      expect(err.message).toEqual('Rpc Exception');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					/** Consider creating a bunch of shared custom utility
 | 
				
			||||||
 | 
					 * types for different situations.
 | 
				
			||||||
 | 
					 * Alternatively you can use a library like
 | 
				
			||||||
 | 
					 * https://github.com/andnp/SimplyTyped
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export * from './object-literal.type';
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Interface of the simple literal object with any string keys.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export interface ObjectLiteral {
 | 
				
			||||||
 | 
					  [key: string]: unknown;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,47 @@
 | 
				
			||||||
 | 
					/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types */
 | 
				
			||||||
 | 
					import { Entity } from '../ddd/entity.base';
 | 
				
			||||||
 | 
					import { ValueObject } from '../ddd/value-object.base';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isEntity(obj: unknown): obj is Entity<unknown> {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 'instanceof Entity' causes error here for some reason.
 | 
				
			||||||
 | 
					   * Probably creates some circular dependency. This is a workaround
 | 
				
			||||||
 | 
					   * until I find a solution :)
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    Object.prototype.hasOwnProperty.call(obj, 'toObject') &&
 | 
				
			||||||
 | 
					    Object.prototype.hasOwnProperty.call(obj, 'id') &&
 | 
				
			||||||
 | 
					    ValueObject.isValueObject((obj as Entity<unknown>).id)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function convertToPlainObject(item: any): any {
 | 
				
			||||||
 | 
					  if (ValueObject.isValueObject(item)) {
 | 
				
			||||||
 | 
					    return item.unpack();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (isEntity(item)) {
 | 
				
			||||||
 | 
					    return item.toObject();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return item;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Converts Entity/Value Objects props to a plain object.
 | 
				
			||||||
 | 
					 * Useful for testing and debugging.
 | 
				
			||||||
 | 
					 * @param props
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function convertPropsToObject(props: any): any {
 | 
				
			||||||
 | 
					  const propsCopy = structuredClone(props);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // eslint-disable-next-line guard-for-in
 | 
				
			||||||
 | 
					  for (const prop in propsCopy) {
 | 
				
			||||||
 | 
					    if (Array.isArray(propsCopy[prop])) {
 | 
				
			||||||
 | 
					      propsCopy[prop] = (propsCopy[prop] as Array<unknown>).map((item) => {
 | 
				
			||||||
 | 
					        return convertToPlainObject(item);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    propsCopy[prop] = convertToPlainObject(propsCopy[prop]);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return propsCopy;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					export * from './convert-props-to-object.util';
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
 | 
					import { RpcExceptionCode } from '@libs/exceptions/rpc-exception.codes.enum';
 | 
				
			||||||
import { Injectable, ValidationPipe } from '@nestjs/common';
 | 
					import { Injectable, ValidationPipe } from '@nestjs/common';
 | 
				
			||||||
import { RpcException } from '@nestjs/microservices';
 | 
					import { RpcException } from '@nestjs/microservices';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +7,7 @@ export class RpcValidationPipe extends ValidationPipe {
 | 
				
			||||||
  createExceptionFactory() {
 | 
					  createExceptionFactory() {
 | 
				
			||||||
    return (validationErrors = []) => {
 | 
					    return (validationErrors = []) => {
 | 
				
			||||||
      return new RpcException({
 | 
					      return new RpcException({
 | 
				
			||||||
        code: 3,
 | 
					        code: RpcExceptionCode.INVALID_ARGUMENT,
 | 
				
			||||||
        message: this.flattenValidationErrors(validationErrors),
 | 
					        message: this.flattenValidationErrors(validationErrors),
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
| 
						 | 
					@ -13,10 +13,13 @@ async function bootstrap() {
 | 
				
			||||||
    options: {
 | 
					    options: {
 | 
				
			||||||
      package: ['ad', 'health'],
 | 
					      package: ['ad', 'health'],
 | 
				
			||||||
      protoPath: [
 | 
					      protoPath: [
 | 
				
			||||||
        join(__dirname, 'modules/ad/adapters/primaries/ad.proto'),
 | 
					        join(__dirname, 'modules/ad/interface/grpc-controllers/ad.proto'),
 | 
				
			||||||
        join(__dirname, 'modules/health/adapters/primaries/health.proto'),
 | 
					        join(
 | 
				
			||||||
 | 
					          __dirname,
 | 
				
			||||||
 | 
					          'modules/health/interface/grpc-controllers/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 +0,0 @@
 | 
				
			||||||
export const PARAMS_PROVIDER = Symbol();
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					export const PARAMS_PROVIDER = Symbol('PARAMS_PROVIDER');
 | 
				
			||||||
 | 
					export const TIMEZONE_FINDER = Symbol('TIMEZONE_FINDER');
 | 
				
			||||||
 | 
					export const TIME_CONVERTER = Symbol('TIME_CONVERTER');
 | 
				
			||||||
 | 
					export const AD_REPOSITORY = Symbol('AD_REPOSITORY');
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,247 @@
 | 
				
			||||||
 | 
					import { Mapper } from '@libs/ddd';
 | 
				
			||||||
 | 
					import { AdResponseDto } from './interface/dtos/ad.response.dto';
 | 
				
			||||||
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { AdEntity } from './core/ad.entity';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  AdWriteModel,
 | 
				
			||||||
 | 
					  AdReadModel,
 | 
				
			||||||
 | 
					  WaypointModel,
 | 
				
			||||||
 | 
					} from './infrastructure/ad.repository';
 | 
				
			||||||
 | 
					import { Frequency } from './core/ad.types';
 | 
				
			||||||
 | 
					import { WaypointProps } from './core/value-objects/waypoint.value-object';
 | 
				
			||||||
 | 
					import { v4 } from 'uuid';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  PARAMS_PROVIDER,
 | 
				
			||||||
 | 
					  TIMEZONE_FINDER,
 | 
				
			||||||
 | 
					  TIME_CONVERTER,
 | 
				
			||||||
 | 
					} from './ad.di-tokens';
 | 
				
			||||||
 | 
					import { TimezoneFinderPort } from './core/ports/timezone-finder.port';
 | 
				
			||||||
 | 
					import { DefaultParamsProviderPort } from './core/ports/default-params-provider.port';
 | 
				
			||||||
 | 
					import { DefaultParams } from './core/ports/default-params.type';
 | 
				
			||||||
 | 
					import { TimeConverterPort } from './core/ports/time-converter.port';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Mapper constructs objects that are used in different layers:
 | 
				
			||||||
 | 
					 * Record is an object that is stored in a database,
 | 
				
			||||||
 | 
					 * Entity is an object that is used in application domain layer,
 | 
				
			||||||
 | 
					 * and a ResponseDTO is an object returned to a user (usually as json).
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class AdMapper
 | 
				
			||||||
 | 
					  implements Mapper<AdEntity, AdReadModel, AdWriteModel, AdResponseDto>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  private readonly _defaultParams: DefaultParams;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(PARAMS_PROVIDER)
 | 
				
			||||||
 | 
					    private readonly defaultParamsProvider: DefaultParamsProviderPort,
 | 
				
			||||||
 | 
					    @Inject(TIMEZONE_FINDER)
 | 
				
			||||||
 | 
					    private readonly timezoneFinder: TimezoneFinderPort,
 | 
				
			||||||
 | 
					    @Inject(TIME_CONVERTER)
 | 
				
			||||||
 | 
					    private readonly timeConverter: TimeConverterPort,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    this._defaultParams = defaultParamsProvider.getParams();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  toPersistence = (entity: AdEntity): AdWriteModel => {
 | 
				
			||||||
 | 
					    const copy = entity.getProps();
 | 
				
			||||||
 | 
					    const { lon, lat } = copy.waypoints[0].address.coordinates;
 | 
				
			||||||
 | 
					    const timezone = this.timezoneFinder.timezones(
 | 
				
			||||||
 | 
					      lon,
 | 
				
			||||||
 | 
					      lat,
 | 
				
			||||||
 | 
					      this._defaultParams.DEFAULT_TIMEZONE,
 | 
				
			||||||
 | 
					    )[0];
 | 
				
			||||||
 | 
					    const now = new Date();
 | 
				
			||||||
 | 
					    const record: AdWriteModel = {
 | 
				
			||||||
 | 
					      uuid: copy.id,
 | 
				
			||||||
 | 
					      userUuid: copy.userId,
 | 
				
			||||||
 | 
					      driver: copy.driver,
 | 
				
			||||||
 | 
					      passenger: copy.passenger,
 | 
				
			||||||
 | 
					      frequency: copy.frequency,
 | 
				
			||||||
 | 
					      fromDate: new Date(copy.fromDate),
 | 
				
			||||||
 | 
					      toDate: new Date(copy.toDate),
 | 
				
			||||||
 | 
					      monTime: copy.schedule.mon
 | 
				
			||||||
 | 
					        ? this.timeConverter.localDateTimeToUtc(
 | 
				
			||||||
 | 
					            copy.fromDate,
 | 
				
			||||||
 | 
					            copy.schedule.mon,
 | 
				
			||||||
 | 
					            timezone,
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        : undefined,
 | 
				
			||||||
 | 
					      tueTime: copy.schedule.tue
 | 
				
			||||||
 | 
					        ? this.timeConverter.localDateTimeToUtc(
 | 
				
			||||||
 | 
					            copy.fromDate,
 | 
				
			||||||
 | 
					            copy.schedule.tue,
 | 
				
			||||||
 | 
					            timezone,
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        : undefined,
 | 
				
			||||||
 | 
					      wedTime: copy.schedule.wed
 | 
				
			||||||
 | 
					        ? this.timeConverter.localDateTimeToUtc(
 | 
				
			||||||
 | 
					            copy.fromDate,
 | 
				
			||||||
 | 
					            copy.schedule.wed,
 | 
				
			||||||
 | 
					            timezone,
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        : undefined,
 | 
				
			||||||
 | 
					      thuTime: copy.schedule.thu
 | 
				
			||||||
 | 
					        ? this.timeConverter.localDateTimeToUtc(
 | 
				
			||||||
 | 
					            copy.fromDate,
 | 
				
			||||||
 | 
					            copy.schedule.thu,
 | 
				
			||||||
 | 
					            timezone,
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        : undefined,
 | 
				
			||||||
 | 
					      friTime: copy.schedule.fri
 | 
				
			||||||
 | 
					        ? this.timeConverter.localDateTimeToUtc(
 | 
				
			||||||
 | 
					            copy.fromDate,
 | 
				
			||||||
 | 
					            copy.schedule.fri,
 | 
				
			||||||
 | 
					            timezone,
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        : undefined,
 | 
				
			||||||
 | 
					      satTime: copy.schedule.sat
 | 
				
			||||||
 | 
					        ? this.timeConverter.localDateTimeToUtc(
 | 
				
			||||||
 | 
					            copy.fromDate,
 | 
				
			||||||
 | 
					            copy.schedule.sat,
 | 
				
			||||||
 | 
					            timezone,
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        : undefined,
 | 
				
			||||||
 | 
					      sunTime: copy.schedule.sun
 | 
				
			||||||
 | 
					        ? this.timeConverter.localDateTimeToUtc(
 | 
				
			||||||
 | 
					            copy.fromDate,
 | 
				
			||||||
 | 
					            copy.schedule.sun,
 | 
				
			||||||
 | 
					            timezone,
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        : undefined,
 | 
				
			||||||
 | 
					      monMargin: copy.marginDurations.mon,
 | 
				
			||||||
 | 
					      tueMargin: copy.marginDurations.tue,
 | 
				
			||||||
 | 
					      wedMargin: copy.marginDurations.wed,
 | 
				
			||||||
 | 
					      thuMargin: copy.marginDurations.thu,
 | 
				
			||||||
 | 
					      friMargin: copy.marginDurations.fri,
 | 
				
			||||||
 | 
					      satMargin: copy.marginDurations.sat,
 | 
				
			||||||
 | 
					      sunMargin: copy.marginDurations.sun,
 | 
				
			||||||
 | 
					      seatsProposed: copy.seatsProposed,
 | 
				
			||||||
 | 
					      seatsRequested: copy.seatsRequested,
 | 
				
			||||||
 | 
					      strict: copy.strict,
 | 
				
			||||||
 | 
					      waypoints: {
 | 
				
			||||||
 | 
					        create: copy.waypoints.map((waypoint: WaypointProps) => ({
 | 
				
			||||||
 | 
					          uuid: v4(),
 | 
				
			||||||
 | 
					          position: waypoint.position,
 | 
				
			||||||
 | 
					          name: waypoint.address.name,
 | 
				
			||||||
 | 
					          houseNumber: waypoint.address.houseNumber,
 | 
				
			||||||
 | 
					          street: waypoint.address.street,
 | 
				
			||||||
 | 
					          locality: waypoint.address.locality,
 | 
				
			||||||
 | 
					          postalCode: waypoint.address.postalCode,
 | 
				
			||||||
 | 
					          country: waypoint.address.country,
 | 
				
			||||||
 | 
					          lon: waypoint.address.coordinates.lon,
 | 
				
			||||||
 | 
					          lat: waypoint.address.coordinates.lat,
 | 
				
			||||||
 | 
					          createdAt: now,
 | 
				
			||||||
 | 
					          updatedAt: now,
 | 
				
			||||||
 | 
					        })),
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      createdAt: copy.createdAt,
 | 
				
			||||||
 | 
					      updatedAt: copy.updatedAt,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    return record;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  toDomain = (record: AdReadModel): AdEntity => {
 | 
				
			||||||
 | 
					    const timezone = this.timezoneFinder.timezones(
 | 
				
			||||||
 | 
					      record.waypoints[0].lon,
 | 
				
			||||||
 | 
					      record.waypoints[0].lat,
 | 
				
			||||||
 | 
					      this._defaultParams.DEFAULT_TIMEZONE,
 | 
				
			||||||
 | 
					    )[0];
 | 
				
			||||||
 | 
					    const entity = new AdEntity({
 | 
				
			||||||
 | 
					      id: record.uuid,
 | 
				
			||||||
 | 
					      createdAt: new Date(record.createdAt),
 | 
				
			||||||
 | 
					      updatedAt: new Date(record.updatedAt),
 | 
				
			||||||
 | 
					      props: {
 | 
				
			||||||
 | 
					        userId: record.userUuid,
 | 
				
			||||||
 | 
					        driver: record.driver,
 | 
				
			||||||
 | 
					        passenger: record.passenger,
 | 
				
			||||||
 | 
					        frequency: Frequency[record.frequency],
 | 
				
			||||||
 | 
					        fromDate: record.fromDate.toISOString().split('T')[0],
 | 
				
			||||||
 | 
					        toDate: record.toDate.toISOString().split('T')[0],
 | 
				
			||||||
 | 
					        schedule: {
 | 
				
			||||||
 | 
					          mon: record.monTime?.toISOString(),
 | 
				
			||||||
 | 
					          tue: record.tueTime?.toISOString(),
 | 
				
			||||||
 | 
					          wed: record.wedTime
 | 
				
			||||||
 | 
					            ? this.timeConverter.utcDatetimeToLocalTime(
 | 
				
			||||||
 | 
					                record.wedTime.toISOString(),
 | 
				
			||||||
 | 
					                timezone,
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					            : undefined,
 | 
				
			||||||
 | 
					          thu: record.thuTime
 | 
				
			||||||
 | 
					            ? this.timeConverter.utcDatetimeToLocalTime(
 | 
				
			||||||
 | 
					                record.thuTime.toISOString(),
 | 
				
			||||||
 | 
					                timezone,
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					            : undefined,
 | 
				
			||||||
 | 
					          fri: record.friTime?.toISOString(),
 | 
				
			||||||
 | 
					          sat: record.satTime?.toISOString(),
 | 
				
			||||||
 | 
					          sun: record.sunTime?.toISOString(),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        marginDurations: {
 | 
				
			||||||
 | 
					          mon: record.monMargin,
 | 
				
			||||||
 | 
					          tue: record.tueMargin,
 | 
				
			||||||
 | 
					          wed: record.wedMargin,
 | 
				
			||||||
 | 
					          thu: record.thuMargin,
 | 
				
			||||||
 | 
					          fri: record.friMargin,
 | 
				
			||||||
 | 
					          sat: record.satMargin,
 | 
				
			||||||
 | 
					          sun: record.sunMargin,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        seatsProposed: record.seatsProposed,
 | 
				
			||||||
 | 
					        seatsRequested: record.seatsRequested,
 | 
				
			||||||
 | 
					        strict: record.strict,
 | 
				
			||||||
 | 
					        waypoints: record.waypoints.map((waypoint: WaypointModel) => ({
 | 
				
			||||||
 | 
					          position: waypoint.position,
 | 
				
			||||||
 | 
					          address: {
 | 
				
			||||||
 | 
					            name: waypoint.name,
 | 
				
			||||||
 | 
					            houseNumber: waypoint.houseNumber,
 | 
				
			||||||
 | 
					            street: waypoint.street,
 | 
				
			||||||
 | 
					            postalCode: waypoint.postalCode,
 | 
				
			||||||
 | 
					            locality: waypoint.locality,
 | 
				
			||||||
 | 
					            country: waypoint.country,
 | 
				
			||||||
 | 
					            coordinates: {
 | 
				
			||||||
 | 
					              lon: waypoint.lon,
 | 
				
			||||||
 | 
					              lat: waypoint.lat,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        })),
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return entity;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  toResponse = (entity: AdEntity): AdResponseDto => {
 | 
				
			||||||
 | 
					    const props = entity.getProps();
 | 
				
			||||||
 | 
					    const response = new AdResponseDto(entity);
 | 
				
			||||||
 | 
					    response.userId = props.userId;
 | 
				
			||||||
 | 
					    response.driver = props.driver;
 | 
				
			||||||
 | 
					    response.passenger = props.passenger;
 | 
				
			||||||
 | 
					    response.frequency = props.frequency;
 | 
				
			||||||
 | 
					    response.fromDate = props.fromDate;
 | 
				
			||||||
 | 
					    response.toDate = props.toDate;
 | 
				
			||||||
 | 
					    response.schedule = { ...props.schedule };
 | 
				
			||||||
 | 
					    response.marginDurations = { ...props.marginDurations };
 | 
				
			||||||
 | 
					    response.seatsProposed = props.seatsProposed;
 | 
				
			||||||
 | 
					    response.seatsRequested = props.seatsRequested;
 | 
				
			||||||
 | 
					    response.waypoints = props.waypoints.map((waypoint: WaypointProps) => ({
 | 
				
			||||||
 | 
					      position: waypoint.position,
 | 
				
			||||||
 | 
					      name: waypoint.address.name,
 | 
				
			||||||
 | 
					      houseNumber: waypoint.address.houseNumber,
 | 
				
			||||||
 | 
					      street: waypoint.address.street,
 | 
				
			||||||
 | 
					      postalCode: waypoint.address.postalCode,
 | 
				
			||||||
 | 
					      locality: waypoint.address.locality,
 | 
				
			||||||
 | 
					      country: waypoint.address.country,
 | 
				
			||||||
 | 
					      lon: waypoint.address.coordinates.lon,
 | 
				
			||||||
 | 
					      lat: waypoint.address.coordinates.lat,
 | 
				
			||||||
 | 
					    }));
 | 
				
			||||||
 | 
					    return response;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* ^ Data returned to the user is whitelisted to avoid leaks.
 | 
				
			||||||
 | 
					     If a new property is added, like password or a
 | 
				
			||||||
 | 
					     credit card number, it won't be returned
 | 
				
			||||||
 | 
					     unless you specifically allow this.
 | 
				
			||||||
 | 
					     (avoid blacklisting, which will return everything
 | 
				
			||||||
 | 
					      but blacklisted items, which can lead to a data leak).
 | 
				
			||||||
 | 
					  */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,37 +1,97 @@
 | 
				
			||||||
import { Module } from '@nestjs/common';
 | 
					import { Module, Provider } from '@nestjs/common';
 | 
				
			||||||
import { AdController } from './adapters/primaries/ad.controller';
 | 
					import { CreateAdGrpcController } from './interface/grpc-controllers/create-ad.grpc.controller';
 | 
				
			||||||
import { DatabaseModule } from '../database/database.module';
 | 
					 | 
				
			||||||
import { CqrsModule } from '@nestjs/cqrs';
 | 
					import { CqrsModule } from '@nestjs/cqrs';
 | 
				
			||||||
import { AdProfile } from './mappers/ad.profile';
 | 
					import {
 | 
				
			||||||
import { AdsRepository } from './adapters/secondaries/ads.repository';
 | 
					  AD_REPOSITORY,
 | 
				
			||||||
import { FindAdByUuidUseCase } from './domain/usecases/find-ad-by-uuid.usecase';
 | 
					  PARAMS_PROVIDER,
 | 
				
			||||||
import { CreateAdUseCase } from './domain/usecases/create-ad.usecase';
 | 
					  TIMEZONE_FINDER,
 | 
				
			||||||
import { DefaultParamsProvider } from './adapters/secondaries/default-params.provider';
 | 
					  TIME_CONVERTER,
 | 
				
			||||||
import { PARAMS_PROVIDER } from './ad.constants';
 | 
					} from './ad.di-tokens';
 | 
				
			||||||
import { MESSAGE_BROKER_PUBLISHER, MESSAGE_PUBLISHER } from 'src/app.constants';
 | 
					import {
 | 
				
			||||||
 | 
					  MESSAGE_BROKER_PUBLISHER,
 | 
				
			||||||
 | 
					  MESSAGE_PUBLISHER,
 | 
				
			||||||
 | 
					} from '@src/app.constants';
 | 
				
			||||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
					import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
				
			||||||
import { MessagePublisher } from './adapters/secondaries/message-publisher';
 | 
					import { AdRepository } from './infrastructure/ad.repository';
 | 
				
			||||||
 | 
					import { DefaultParamsProvider } from './infrastructure/default-params-provider';
 | 
				
			||||||
 | 
					import { MessagePublisher } from './infrastructure/message-publisher';
 | 
				
			||||||
 | 
					import { AdMapper } from './ad.mapper';
 | 
				
			||||||
 | 
					import { CreateAdService } from './core/commands/create-ad/create-ad.service';
 | 
				
			||||||
 | 
					import { TimezoneFinder } from './infrastructure/timezone-finder';
 | 
				
			||||||
 | 
					import { PrismaService } from '@libs/db/prisma.service';
 | 
				
			||||||
 | 
					import { TimeConverter } from './infrastructure/time-converter';
 | 
				
			||||||
 | 
					import { FindAdByIdGrpcController } from './interface/grpc-controllers/find-ad-by-id.grpc.controller';
 | 
				
			||||||
 | 
					import { FindAdByIdQueryHandler } from './core/queries/find-ad-by-id/find-ad-by-id.query-handler';
 | 
				
			||||||
 | 
					import { PublishMessageWhenAdIsCreatedDomainEventHandler } from './core/event-handlers/publish-message-when-ad-is-created.domain-event-handler';
 | 
				
			||||||
 | 
					import { PublishLogMessageWhenAdIsCreatedDomainEventHandler } from './core/event-handlers/publish-log-message-when-ad-is-created.domain-event-handler';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const grpcControllers = [CreateAdGrpcController, FindAdByIdGrpcController];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const eventHandlers: Provider[] = [
 | 
				
			||||||
 | 
					  PublishMessageWhenAdIsCreatedDomainEventHandler,
 | 
				
			||||||
 | 
					  PublishLogMessageWhenAdIsCreatedDomainEventHandler,
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const commandHandlers: Provider[] = [CreateAdService];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const queryHandlers: Provider[] = [FindAdByIdQueryHandler];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mappers: Provider[] = [AdMapper];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const repositories: Provider[] = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    provide: AD_REPOSITORY,
 | 
				
			||||||
 | 
					    useClass: AdRepository,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const messageBrokers: Provider[] = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    provide: MESSAGE_BROKER_PUBLISHER,
 | 
				
			||||||
 | 
					    useClass: MessageBrokerPublisher,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    provide: MESSAGE_PUBLISHER,
 | 
				
			||||||
 | 
					    useClass: MessagePublisher,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const orms: Provider[] = [PrismaService];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const utilities: Provider[] = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    provide: PARAMS_PROVIDER,
 | 
				
			||||||
 | 
					    useClass: DefaultParamsProvider,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    provide: TIMEZONE_FINDER,
 | 
				
			||||||
 | 
					    useClass: TimezoneFinder,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    provide: TIME_CONVERTER,
 | 
				
			||||||
 | 
					    useClass: TimeConverter,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Module({
 | 
					@Module({
 | 
				
			||||||
  imports: [DatabaseModule, CqrsModule],
 | 
					  imports: [CqrsModule],
 | 
				
			||||||
  controllers: [AdController],
 | 
					  controllers: [...grpcControllers],
 | 
				
			||||||
  providers: [
 | 
					  providers: [
 | 
				
			||||||
    AdProfile,
 | 
					    ...eventHandlers,
 | 
				
			||||||
    AdsRepository,
 | 
					    ...commandHandlers,
 | 
				
			||||||
    FindAdByUuidUseCase,
 | 
					    ...queryHandlers,
 | 
				
			||||||
    CreateAdUseCase,
 | 
					    ...mappers,
 | 
				
			||||||
    {
 | 
					    ...repositories,
 | 
				
			||||||
      provide: PARAMS_PROVIDER,
 | 
					    ...messageBrokers,
 | 
				
			||||||
      useClass: DefaultParamsProvider,
 | 
					    ...orms,
 | 
				
			||||||
    },
 | 
					    ...utilities,
 | 
				
			||||||
    {
 | 
					  ],
 | 
				
			||||||
      provide: MESSAGE_BROKER_PUBLISHER,
 | 
					  exports: [
 | 
				
			||||||
      useClass: MessageBrokerPublisher,
 | 
					    PrismaService,
 | 
				
			||||||
    },
 | 
					    AdMapper,
 | 
				
			||||||
    {
 | 
					    AD_REPOSITORY,
 | 
				
			||||||
      provide: MESSAGE_PUBLISHER,
 | 
					    PARAMS_PROVIDER,
 | 
				
			||||||
      useClass: MessagePublisher,
 | 
					    TIMEZONE_FINDER,
 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class AdModule {}
 | 
					export class AdModule {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,59 +0,0 @@
 | 
				
			||||||
import { Mapper } from '@automapper/core';
 | 
					 | 
				
			||||||
import { InjectMapper } from '@automapper/nestjs';
 | 
					 | 
				
			||||||
import { Controller, UsePipes } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { CommandBus, QueryBus } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
 | 
					 | 
				
			||||||
import { RpcValidationPipe } from '../../../../utils/pipes/rpc.validation-pipe';
 | 
					 | 
				
			||||||
import { FindAdByUuidRequest } from '../../domain/dtos/find-ad-by-uuid.request';
 | 
					 | 
				
			||||||
import { AdPresenter } from './ad.presenter';
 | 
					 | 
				
			||||||
import { FindAdByUuidQuery } from '../../queries/find-ad-by-uuid.query';
 | 
					 | 
				
			||||||
import { Ad } from '../../domain/entities/ad';
 | 
					 | 
				
			||||||
import { CreateAdRequest } from '../../domain/dtos/create-ad.request';
 | 
					 | 
				
			||||||
import { CreateAdCommand } from '../../commands/create-ad.command';
 | 
					 | 
				
			||||||
import { DatabaseException } from '../../../database/exceptions/database.exception';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@UsePipes(
 | 
					 | 
				
			||||||
  new RpcValidationPipe({
 | 
					 | 
				
			||||||
    whitelist: false,
 | 
					 | 
				
			||||||
    forbidUnknownValues: false,
 | 
					 | 
				
			||||||
  }),
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
@Controller()
 | 
					 | 
				
			||||||
export class AdController {
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    private readonly commandBus: CommandBus,
 | 
					 | 
				
			||||||
    private readonly queryBus: QueryBus,
 | 
					 | 
				
			||||||
    @InjectMapper() private readonly mapper: Mapper,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @GrpcMethod('AdsService', 'FindOneByUuid')
 | 
					 | 
				
			||||||
  async findOnebyUuid(data: FindAdByUuidRequest): Promise<AdPresenter> {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const ad = await this.queryBus.execute(new FindAdByUuidQuery(data));
 | 
					 | 
				
			||||||
      return this.mapper.map(ad, Ad, AdPresenter);
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      throw new RpcException({
 | 
					 | 
				
			||||||
        code: e.code,
 | 
					 | 
				
			||||||
        message: e.message,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @GrpcMethod('AdsService', 'Create')
 | 
					 | 
				
			||||||
  async createAd(data: CreateAdRequest): Promise<AdPresenter> {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const ad = await this.commandBus.execute(new CreateAdCommand(data));
 | 
					 | 
				
			||||||
      return this.mapper.map(ad, Ad, AdPresenter);
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      if (e instanceof DatabaseException) {
 | 
					 | 
				
			||||||
        if (e.message.includes('Already exists')) {
 | 
					 | 
				
			||||||
          throw new RpcException({
 | 
					 | 
				
			||||||
            code: 6,
 | 
					 | 
				
			||||||
            message: 'Ad already exists',
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      throw new RpcException({});
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +0,0 @@
 | 
				
			||||||
import { AutoMap } from '@automapper/classes';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class AdPresenter {
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  uuid: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,83 +0,0 @@
 | 
				
			||||||
syntax = "proto3";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package ad;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
service AdsService {
 | 
					 | 
				
			||||||
  rpc FindOneByUuid(AdByUuid) returns (Ad);
 | 
					 | 
				
			||||||
  rpc FindAll(AdFilter) returns (Ads);
 | 
					 | 
				
			||||||
  rpc Create(Ad) returns (AdByUuid);
 | 
					 | 
				
			||||||
  rpc Update(Ad) returns (Ad);
 | 
					 | 
				
			||||||
  rpc Delete(AdByUuid) returns (Empty);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
message AdByUuid {
 | 
					 | 
				
			||||||
  string uuid = 1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
message Ad {
 | 
					 | 
				
			||||||
  string uuid = 1;
 | 
					 | 
				
			||||||
  string userUuid = 2;
 | 
					 | 
				
			||||||
  bool driver = 3;
 | 
					 | 
				
			||||||
  bool passenger = 4;
 | 
					 | 
				
			||||||
  Frequency frequency = 5;
 | 
					 | 
				
			||||||
  optional string departureDateTime = 6;
 | 
					 | 
				
			||||||
  string fromDate = 7;
 | 
					 | 
				
			||||||
  string toDate = 8;
 | 
					 | 
				
			||||||
  Schedule schedule = 9;
 | 
					 | 
				
			||||||
  MarginDurations marginDurations = 10;
 | 
					 | 
				
			||||||
  int32 seatsPassenger = 11;
 | 
					 | 
				
			||||||
  int32 seatsDriver = 12;
 | 
					 | 
				
			||||||
  bool strict = 13;
 | 
					 | 
				
			||||||
  repeated Address addresses = 14;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
message Schedule {
 | 
					 | 
				
			||||||
  optional string mon = 1;
 | 
					 | 
				
			||||||
  optional string tue = 2;
 | 
					 | 
				
			||||||
  optional string wed = 3;
 | 
					 | 
				
			||||||
  optional string thu = 4;
 | 
					 | 
				
			||||||
  optional string fri = 5;
 | 
					 | 
				
			||||||
  optional string sat = 6;
 | 
					 | 
				
			||||||
  optional string sun = 7;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
message MarginDurations {
 | 
					 | 
				
			||||||
  int32 mon = 1;
 | 
					 | 
				
			||||||
  int32 tue = 2;
 | 
					 | 
				
			||||||
  int32 wed = 3;
 | 
					 | 
				
			||||||
  int32 thu = 4;
 | 
					 | 
				
			||||||
  int32 fri = 5;
 | 
					 | 
				
			||||||
  int32 sat = 6;
 | 
					 | 
				
			||||||
  int32 sun = 7;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
message Address {
 | 
					 | 
				
			||||||
  string uuid = 1;
 | 
					 | 
				
			||||||
  int32 position = 2;
 | 
					 | 
				
			||||||
  float lon = 3;
 | 
					 | 
				
			||||||
  float lat = 4;
 | 
					 | 
				
			||||||
  optional string name = 5;
 | 
					 | 
				
			||||||
  optional string houseNumber = 6;
 | 
					 | 
				
			||||||
  optional string street = 7;
 | 
					 | 
				
			||||||
  optional string locality = 8;
 | 
					 | 
				
			||||||
  optional string postalCode = 9;
 | 
					 | 
				
			||||||
  string country = 10;
 | 
					 | 
				
			||||||
} 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum Frequency {
 | 
					 | 
				
			||||||
  PUNCTUAL = 1;
 | 
					 | 
				
			||||||
  RECURRENT = 2;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
message AdFilter {
 | 
					 | 
				
			||||||
  int32 page = 1;
 | 
					 | 
				
			||||||
  int32 perPage = 2;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
message Ads {
 | 
					 | 
				
			||||||
  repeated Ad data = 1;
 | 
					 | 
				
			||||||
  int32 total = 2;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
message Empty {}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,8 +0,0 @@
 | 
				
			||||||
import { Injectable } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { AdRepository } from '../../../database/domain/ad-repository';
 | 
					 | 
				
			||||||
import { Ad } from '../../domain/entities/ad';
 | 
					 | 
				
			||||||
//TODO : properly implement mutate operation to prisma
 | 
					 | 
				
			||||||
@Injectable()
 | 
					 | 
				
			||||||
export class AdsRepository extends AdRepository<Ad> {
 | 
					 | 
				
			||||||
  protected model = 'ad';
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,23 +0,0 @@
 | 
				
			||||||
import { Injectable } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { ConfigService } from '@nestjs/config';
 | 
					 | 
				
			||||||
import { DefaultParams } from '../../domain/types/default-params.type';
 | 
					 | 
				
			||||||
import { IProvideParams } from '../../domain/interfaces/param-provider.interface';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Injectable()
 | 
					 | 
				
			||||||
export class DefaultParamsProvider implements IProvideParams {
 | 
					 | 
				
			||||||
  constructor(private readonly configService: ConfigService) {}
 | 
					 | 
				
			||||||
  getParams = (): DefaultParams => ({
 | 
					 | 
				
			||||||
    MON_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')),
 | 
					 | 
				
			||||||
    TUE_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')),
 | 
					 | 
				
			||||||
    WED_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')),
 | 
					 | 
				
			||||||
    THU_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')),
 | 
					 | 
				
			||||||
    FRI_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')),
 | 
					 | 
				
			||||||
    SAT_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')),
 | 
					 | 
				
			||||||
    SUN_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')),
 | 
					 | 
				
			||||||
    DRIVER: this.configService.get('ROLE') == 'driver',
 | 
					 | 
				
			||||||
    SEATS_PROVIDED: parseInt(this.configService.get('SEATS_PROVIDED')),
 | 
					 | 
				
			||||||
    PASSENGER: this.configService.get('ROLE') == 'passenger',
 | 
					 | 
				
			||||||
    SEATS_REQUESTED: parseInt(this.configService.get('SEATS_REQUESTED')),
 | 
					 | 
				
			||||||
    STRICT: this.configService.get('STRICT_FREQUENCY') == 'true',
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,9 +0,0 @@
 | 
				
			||||||
import { CreateAdRequest } from '../domain/dtos/create-ad.request';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class CreateAdCommand {
 | 
					 | 
				
			||||||
  readonly createAdRequest: CreateAdRequest;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(request: CreateAdRequest) {
 | 
					 | 
				
			||||||
    this.createAdRequest = request;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,137 @@
 | 
				
			||||||
 | 
					import { AggregateRoot, AggregateID } from '@libs/ddd';
 | 
				
			||||||
 | 
					import { v4 } from 'uuid';
 | 
				
			||||||
 | 
					import { AdCreatedDomainEvent } from './events/ad-created.domain-events';
 | 
				
			||||||
 | 
					import { AdProps, CreateAdProps, DefaultAdProps } from './ad.types';
 | 
				
			||||||
 | 
					import { Waypoint } from './value-objects/waypoint.value-object';
 | 
				
			||||||
 | 
					import { MarginDurationsProps } from './value-objects/margin-durations.value-object';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class AdEntity extends AggregateRoot<AdProps> {
 | 
				
			||||||
 | 
					  protected readonly _id: AggregateID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static create = (
 | 
				
			||||||
 | 
					    create: CreateAdProps,
 | 
				
			||||||
 | 
					    defaultAdProps: DefaultAdProps,
 | 
				
			||||||
 | 
					  ): AdEntity => {
 | 
				
			||||||
 | 
					    const id = v4();
 | 
				
			||||||
 | 
					    const props: AdProps = { ...create };
 | 
				
			||||||
 | 
					    const ad = new AdEntity({ id, props })
 | 
				
			||||||
 | 
					      .setMissingMarginDurations(defaultAdProps.marginDurations)
 | 
				
			||||||
 | 
					      .setMissingStrict(defaultAdProps.strict)
 | 
				
			||||||
 | 
					      .setDefaultDriverAndPassengerParameters({
 | 
				
			||||||
 | 
					        driver: defaultAdProps.driver,
 | 
				
			||||||
 | 
					        passenger: defaultAdProps.passenger,
 | 
				
			||||||
 | 
					        seatsProposed: defaultAdProps.seatsProposed,
 | 
				
			||||||
 | 
					        seatsRequested: defaultAdProps.seatsRequested,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .setMissingWaypointsPosition();
 | 
				
			||||||
 | 
					    ad.addEvent(
 | 
				
			||||||
 | 
					      new AdCreatedDomainEvent({
 | 
				
			||||||
 | 
					        aggregateId: id,
 | 
				
			||||||
 | 
					        userId: props.userId,
 | 
				
			||||||
 | 
					        driver: props.driver,
 | 
				
			||||||
 | 
					        passenger: props.passenger,
 | 
				
			||||||
 | 
					        frequency: props.frequency,
 | 
				
			||||||
 | 
					        fromDate: props.fromDate,
 | 
				
			||||||
 | 
					        toDate: props.toDate,
 | 
				
			||||||
 | 
					        monTime: props.schedule.mon,
 | 
				
			||||||
 | 
					        tueTime: props.schedule.tue,
 | 
				
			||||||
 | 
					        wedTime: props.schedule.wed,
 | 
				
			||||||
 | 
					        thuTime: props.schedule.thu,
 | 
				
			||||||
 | 
					        friTime: props.schedule.fri,
 | 
				
			||||||
 | 
					        satTime: props.schedule.sat,
 | 
				
			||||||
 | 
					        sunTime: props.schedule.sun,
 | 
				
			||||||
 | 
					        monMarginDuration: props.marginDurations.mon,
 | 
				
			||||||
 | 
					        tueMarginDuration: props.marginDurations.tue,
 | 
				
			||||||
 | 
					        wedMarginDuration: props.marginDurations.wed,
 | 
				
			||||||
 | 
					        thuMarginDuration: props.marginDurations.thu,
 | 
				
			||||||
 | 
					        friMarginDuration: props.marginDurations.fri,
 | 
				
			||||||
 | 
					        satMarginDuration: props.marginDurations.sat,
 | 
				
			||||||
 | 
					        sunMarginDuration: props.marginDurations.sun,
 | 
				
			||||||
 | 
					        seatsProposed: props.seatsProposed,
 | 
				
			||||||
 | 
					        seatsRequested: props.seatsRequested,
 | 
				
			||||||
 | 
					        strict: props.strict,
 | 
				
			||||||
 | 
					        waypoints: props.waypoints.map((waypoint: Waypoint) => ({
 | 
				
			||||||
 | 
					          position: waypoint.position,
 | 
				
			||||||
 | 
					          name: waypoint.address.name,
 | 
				
			||||||
 | 
					          houseNumber: waypoint.address.houseNumber,
 | 
				
			||||||
 | 
					          street: waypoint.address.street,
 | 
				
			||||||
 | 
					          postalCode: waypoint.address.postalCode,
 | 
				
			||||||
 | 
					          locality: waypoint.address.locality,
 | 
				
			||||||
 | 
					          country: waypoint.address.postalCode,
 | 
				
			||||||
 | 
					          lon: waypoint.address.coordinates.lon,
 | 
				
			||||||
 | 
					          lat: waypoint.address.coordinates.lat,
 | 
				
			||||||
 | 
					        })),
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    return ad;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private setMissingMarginDurations = (
 | 
				
			||||||
 | 
					    defaultMarginDurations: MarginDurationsProps,
 | 
				
			||||||
 | 
					  ): AdEntity => {
 | 
				
			||||||
 | 
					    if (!this.props.marginDurations) this.props.marginDurations = {};
 | 
				
			||||||
 | 
					    if (!this.props.marginDurations.mon)
 | 
				
			||||||
 | 
					      this.props.marginDurations.mon = defaultMarginDurations.mon;
 | 
				
			||||||
 | 
					    if (!this.props.marginDurations.tue)
 | 
				
			||||||
 | 
					      this.props.marginDurations.tue = defaultMarginDurations.tue;
 | 
				
			||||||
 | 
					    if (!this.props.marginDurations.wed)
 | 
				
			||||||
 | 
					      this.props.marginDurations.wed = defaultMarginDurations.wed;
 | 
				
			||||||
 | 
					    if (!this.props.marginDurations.thu)
 | 
				
			||||||
 | 
					      this.props.marginDurations.thu = defaultMarginDurations.thu;
 | 
				
			||||||
 | 
					    if (!this.props.marginDurations.fri)
 | 
				
			||||||
 | 
					      this.props.marginDurations.fri = defaultMarginDurations.fri;
 | 
				
			||||||
 | 
					    if (!this.props.marginDurations.sat)
 | 
				
			||||||
 | 
					      this.props.marginDurations.sat = defaultMarginDurations.sat;
 | 
				
			||||||
 | 
					    if (!this.props.marginDurations.sun)
 | 
				
			||||||
 | 
					      this.props.marginDurations.sun = defaultMarginDurations.sun;
 | 
				
			||||||
 | 
					    return this;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private setMissingStrict = (strict: boolean): AdEntity => {
 | 
				
			||||||
 | 
					    if (this.props.strict === undefined) this.props.strict = strict;
 | 
				
			||||||
 | 
					    return this;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private setDefaultDriverAndPassengerParameters = (
 | 
				
			||||||
 | 
					    defaultDriverAndPassengerParameters: DefaultDriverAndPassengerParameters,
 | 
				
			||||||
 | 
					  ): AdEntity => {
 | 
				
			||||||
 | 
					    this.props.driver = !!this.props.driver;
 | 
				
			||||||
 | 
					    this.props.passenger = !!this.props.passenger;
 | 
				
			||||||
 | 
					    if (!this.props.driver && !this.props.passenger) {
 | 
				
			||||||
 | 
					      this.props.driver = defaultDriverAndPassengerParameters.driver;
 | 
				
			||||||
 | 
					      this.props.seatsProposed =
 | 
				
			||||||
 | 
					        defaultDriverAndPassengerParameters.seatsProposed;
 | 
				
			||||||
 | 
					      this.props.passenger = defaultDriverAndPassengerParameters.passenger;
 | 
				
			||||||
 | 
					      this.props.seatsRequested =
 | 
				
			||||||
 | 
					        defaultDriverAndPassengerParameters.seatsRequested;
 | 
				
			||||||
 | 
					      return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!this.props.seatsProposed || this.props.seatsProposed <= 0)
 | 
				
			||||||
 | 
					      this.props.seatsProposed =
 | 
				
			||||||
 | 
					        defaultDriverAndPassengerParameters.seatsProposed;
 | 
				
			||||||
 | 
					    if (!this.props.seatsRequested || this.props.seatsRequested <= 0)
 | 
				
			||||||
 | 
					      this.props.seatsRequested =
 | 
				
			||||||
 | 
					        defaultDriverAndPassengerParameters.seatsRequested;
 | 
				
			||||||
 | 
					    return this;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private setMissingWaypointsPosition = (): AdEntity => {
 | 
				
			||||||
 | 
					    if (this.props.waypoints[0].position === undefined) {
 | 
				
			||||||
 | 
					      for (let i = 0; i < this.props.waypoints.length; i++) {
 | 
				
			||||||
 | 
					        this.props.waypoints[i].position = i;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return this;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validate(): void {
 | 
				
			||||||
 | 
					    // entity business rules validation to protect it's invariant before saving entity to a database
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface DefaultDriverAndPassengerParameters {
 | 
				
			||||||
 | 
					  driver: boolean;
 | 
				
			||||||
 | 
					  passenger: boolean;
 | 
				
			||||||
 | 
					  seatsProposed: number;
 | 
				
			||||||
 | 
					  seatsRequested: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					import { ExceptionBase } from '@libs/exceptions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class AdAlreadyExistsException extends ExceptionBase {
 | 
				
			||||||
 | 
					  static readonly message = 'Ad already exists';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public readonly code = 'AD.ALREADY_EXISTS';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(cause?: Error, metadata?: unknown) {
 | 
				
			||||||
 | 
					    super(AdAlreadyExistsException.message, cause, metadata);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,49 @@
 | 
				
			||||||
 | 
					import { MarginDurationsProps } from './value-objects/margin-durations.value-object';
 | 
				
			||||||
 | 
					import { ScheduleProps } from './value-objects/schedule.value-object';
 | 
				
			||||||
 | 
					import { WaypointProps } from './value-objects/waypoint.value-object';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// All properties that an Ad has
 | 
				
			||||||
 | 
					export interface AdProps {
 | 
				
			||||||
 | 
					  userId: string;
 | 
				
			||||||
 | 
					  driver: boolean;
 | 
				
			||||||
 | 
					  passenger: boolean;
 | 
				
			||||||
 | 
					  frequency: Frequency;
 | 
				
			||||||
 | 
					  fromDate: string;
 | 
				
			||||||
 | 
					  toDate: string;
 | 
				
			||||||
 | 
					  schedule: ScheduleProps;
 | 
				
			||||||
 | 
					  marginDurations: MarginDurationsProps;
 | 
				
			||||||
 | 
					  seatsProposed: number;
 | 
				
			||||||
 | 
					  seatsRequested: number;
 | 
				
			||||||
 | 
					  strict: boolean;
 | 
				
			||||||
 | 
					  waypoints: WaypointProps[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Properties that are needed for an Ad creation
 | 
				
			||||||
 | 
					export interface CreateAdProps {
 | 
				
			||||||
 | 
					  userId: string;
 | 
				
			||||||
 | 
					  driver: boolean;
 | 
				
			||||||
 | 
					  passenger: boolean;
 | 
				
			||||||
 | 
					  frequency: Frequency;
 | 
				
			||||||
 | 
					  fromDate: string;
 | 
				
			||||||
 | 
					  toDate: string;
 | 
				
			||||||
 | 
					  schedule: ScheduleProps;
 | 
				
			||||||
 | 
					  marginDurations: MarginDurationsProps;
 | 
				
			||||||
 | 
					  seatsProposed: number;
 | 
				
			||||||
 | 
					  seatsRequested: number;
 | 
				
			||||||
 | 
					  strict: boolean;
 | 
				
			||||||
 | 
					  waypoints: WaypointProps[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface DefaultAdProps {
 | 
				
			||||||
 | 
					  driver: boolean;
 | 
				
			||||||
 | 
					  passenger: boolean;
 | 
				
			||||||
 | 
					  marginDurations: MarginDurationsProps;
 | 
				
			||||||
 | 
					  strict: boolean;
 | 
				
			||||||
 | 
					  seatsProposed: number;
 | 
				
			||||||
 | 
					  seatsRequested: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum Frequency {
 | 
				
			||||||
 | 
					  PUNCTUAL = 'PUNCTUAL',
 | 
				
			||||||
 | 
					  RECURRENT = 'RECURRENT',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					import { Command, CommandProps } from '@libs/ddd';
 | 
				
			||||||
 | 
					import { Frequency } from '@modules/ad/core/ad.types';
 | 
				
			||||||
 | 
					import { Schedule } from '../../types/schedule';
 | 
				
			||||||
 | 
					import { MarginDurations } from '../../types/margin-durations';
 | 
				
			||||||
 | 
					import { Waypoint } from '../../types/waypoint';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class CreateAdCommand extends Command {
 | 
				
			||||||
 | 
					  readonly userId: string;
 | 
				
			||||||
 | 
					  readonly driver?: boolean;
 | 
				
			||||||
 | 
					  readonly passenger?: boolean;
 | 
				
			||||||
 | 
					  readonly frequency?: Frequency;
 | 
				
			||||||
 | 
					  readonly fromDate: string;
 | 
				
			||||||
 | 
					  readonly toDate: string;
 | 
				
			||||||
 | 
					  readonly schedule: Schedule;
 | 
				
			||||||
 | 
					  readonly marginDurations?: MarginDurations;
 | 
				
			||||||
 | 
					  readonly seatsProposed?: number;
 | 
				
			||||||
 | 
					  readonly seatsRequested?: number;
 | 
				
			||||||
 | 
					  readonly strict?: boolean;
 | 
				
			||||||
 | 
					  readonly waypoints: Waypoint[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: CommandProps<CreateAdCommand>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.userId = props.userId;
 | 
				
			||||||
 | 
					    this.driver = props.driver;
 | 
				
			||||||
 | 
					    this.passenger = props.passenger;
 | 
				
			||||||
 | 
					    this.frequency = props.frequency;
 | 
				
			||||||
 | 
					    this.fromDate = props.fromDate;
 | 
				
			||||||
 | 
					    this.toDate = props.toDate;
 | 
				
			||||||
 | 
					    this.schedule = props.schedule;
 | 
				
			||||||
 | 
					    this.marginDurations = props.marginDurations;
 | 
				
			||||||
 | 
					    this.seatsProposed = props.seatsProposed;
 | 
				
			||||||
 | 
					    this.seatsRequested = props.seatsRequested;
 | 
				
			||||||
 | 
					    this.strict = props.strict;
 | 
				
			||||||
 | 
					    this.waypoints = props.waypoints;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,85 @@
 | 
				
			||||||
 | 
					import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { CreateAdCommand } from './create-ad.command';
 | 
				
			||||||
 | 
					import { DefaultParams } from '@modules/ad/core/ports/default-params.type';
 | 
				
			||||||
 | 
					import { Inject } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { AD_REPOSITORY, PARAMS_PROVIDER } from '@modules/ad/ad.di-tokens';
 | 
				
			||||||
 | 
					import { AdRepositoryPort } from '@modules/ad/core/ports/ad.repository.port';
 | 
				
			||||||
 | 
					import { DefaultParamsProviderPort } from '@modules/ad/core/ports/default-params-provider.port';
 | 
				
			||||||
 | 
					import { AggregateID } from '@libs/ddd';
 | 
				
			||||||
 | 
					import { AdAlreadyExistsException } from '@modules/ad/core/ad.errors';
 | 
				
			||||||
 | 
					import { AdEntity } from '@modules/ad/core/ad.entity';
 | 
				
			||||||
 | 
					import { ConflictException } from '@libs/exceptions';
 | 
				
			||||||
 | 
					import { Waypoint } from '../../types/waypoint';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@CommandHandler(CreateAdCommand)
 | 
				
			||||||
 | 
					export class CreateAdService implements ICommandHandler {
 | 
				
			||||||
 | 
					  private readonly _defaultParams: DefaultParams;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(AD_REPOSITORY)
 | 
				
			||||||
 | 
					    private readonly repository: AdRepositoryPort,
 | 
				
			||||||
 | 
					    @Inject(PARAMS_PROVIDER)
 | 
				
			||||||
 | 
					    private readonly defaultParamsProvider: DefaultParamsProviderPort,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    this._defaultParams = defaultParamsProvider.getParams();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async execute(command: CreateAdCommand): Promise<AggregateID> {
 | 
				
			||||||
 | 
					    const ad = AdEntity.create(
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        userId: command.userId,
 | 
				
			||||||
 | 
					        driver: command.driver,
 | 
				
			||||||
 | 
					        passenger: command.passenger,
 | 
				
			||||||
 | 
					        frequency: command.frequency,
 | 
				
			||||||
 | 
					        fromDate: command.fromDate,
 | 
				
			||||||
 | 
					        toDate: command.toDate,
 | 
				
			||||||
 | 
					        schedule: command.schedule,
 | 
				
			||||||
 | 
					        marginDurations: command.marginDurations,
 | 
				
			||||||
 | 
					        seatsProposed: command.seatsProposed,
 | 
				
			||||||
 | 
					        seatsRequested: command.seatsRequested,
 | 
				
			||||||
 | 
					        strict: command.strict,
 | 
				
			||||||
 | 
					        waypoints: command.waypoints.map((waypoint: Waypoint) => ({
 | 
				
			||||||
 | 
					          position: waypoint.position,
 | 
				
			||||||
 | 
					          address: {
 | 
				
			||||||
 | 
					            name: waypoint.name,
 | 
				
			||||||
 | 
					            houseNumber: waypoint.houseNumber,
 | 
				
			||||||
 | 
					            street: waypoint.street,
 | 
				
			||||||
 | 
					            postalCode: waypoint.postalCode,
 | 
				
			||||||
 | 
					            locality: waypoint.locality,
 | 
				
			||||||
 | 
					            country: waypoint.country,
 | 
				
			||||||
 | 
					            coordinates: {
 | 
				
			||||||
 | 
					              lon: waypoint.lon,
 | 
				
			||||||
 | 
					              lat: waypoint.lat,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        })),
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        driver: this._defaultParams.DRIVER,
 | 
				
			||||||
 | 
					        passenger: this._defaultParams.PASSENGER,
 | 
				
			||||||
 | 
					        marginDurations: {
 | 
				
			||||||
 | 
					          mon: this._defaultParams.MON_MARGIN,
 | 
				
			||||||
 | 
					          tue: this._defaultParams.TUE_MARGIN,
 | 
				
			||||||
 | 
					          wed: this._defaultParams.WED_MARGIN,
 | 
				
			||||||
 | 
					          thu: this._defaultParams.THU_MARGIN,
 | 
				
			||||||
 | 
					          fri: this._defaultParams.FRI_MARGIN,
 | 
				
			||||||
 | 
					          sat: this._defaultParams.SAT_MARGIN,
 | 
				
			||||||
 | 
					          sun: this._defaultParams.SUN_MARGIN,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        strict: this._defaultParams.STRICT,
 | 
				
			||||||
 | 
					        seatsProposed: this._defaultParams.SEATS_PROPOSED,
 | 
				
			||||||
 | 
					        seatsRequested: this._defaultParams.SEATS_REQUESTED,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await this.repository.insert(ad);
 | 
				
			||||||
 | 
					      return ad.id;
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					      if (error instanceof ConflictException) {
 | 
				
			||||||
 | 
					        throw new AdAlreadyExistsException(error);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      throw error;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					import { MessagePublisherPort } from '@libs/ports/message-publisher.port';
 | 
				
			||||||
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { OnEvent } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import { MESSAGE_PUBLISHER } from '@src/app.constants';
 | 
				
			||||||
 | 
					import { AdCreatedDomainEvent } from '../events/ad-created.domain-events';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class PublishLogMessageWhenAdIsCreatedDomainEventHandler {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(MESSAGE_PUBLISHER)
 | 
				
			||||||
 | 
					    private readonly messagePublisher: MessagePublisherPort,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @OnEvent(AdCreatedDomainEvent.name, { async: true, promisify: true })
 | 
				
			||||||
 | 
					  async handle(event: AdCreatedDomainEvent): Promise<any> {
 | 
				
			||||||
 | 
					    this.messagePublisher.publish(
 | 
				
			||||||
 | 
					      'logging.ad.created.info',
 | 
				
			||||||
 | 
					      JSON.stringify(event),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					import { MessagePublisherPort } from '@libs/ports/message-publisher.port';
 | 
				
			||||||
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { OnEvent } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import { MESSAGE_PUBLISHER } from '@src/app.constants';
 | 
				
			||||||
 | 
					import { AdCreatedDomainEvent } from '../events/ad-created.domain-events';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class PublishMessageWhenAdIsCreatedDomainEventHandler {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(MESSAGE_PUBLISHER)
 | 
				
			||||||
 | 
					    private readonly messagePublisher: MessagePublisherPort,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @OnEvent(AdCreatedDomainEvent.name, { async: true, promisify: true })
 | 
				
			||||||
 | 
					  async handle(event: AdCreatedDomainEvent): Promise<any> {
 | 
				
			||||||
 | 
					    this.messagePublisher.publish('ad.created', JSON.stringify(event));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,68 @@
 | 
				
			||||||
 | 
					import { DomainEvent, DomainEventProps } from '@libs/ddd';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class AdCreatedDomainEvent extends DomainEvent {
 | 
				
			||||||
 | 
					  readonly userId: string;
 | 
				
			||||||
 | 
					  readonly driver: boolean;
 | 
				
			||||||
 | 
					  readonly passenger: boolean;
 | 
				
			||||||
 | 
					  readonly frequency: string;
 | 
				
			||||||
 | 
					  readonly fromDate: string;
 | 
				
			||||||
 | 
					  readonly toDate: string;
 | 
				
			||||||
 | 
					  readonly monTime: string;
 | 
				
			||||||
 | 
					  readonly tueTime: string;
 | 
				
			||||||
 | 
					  readonly wedTime: string;
 | 
				
			||||||
 | 
					  readonly thuTime: string;
 | 
				
			||||||
 | 
					  readonly friTime: string;
 | 
				
			||||||
 | 
					  readonly satTime: string;
 | 
				
			||||||
 | 
					  readonly sunTime: string;
 | 
				
			||||||
 | 
					  readonly monMarginDuration: number;
 | 
				
			||||||
 | 
					  readonly tueMarginDuration: number;
 | 
				
			||||||
 | 
					  readonly wedMarginDuration: number;
 | 
				
			||||||
 | 
					  readonly thuMarginDuration: number;
 | 
				
			||||||
 | 
					  readonly friMarginDuration: number;
 | 
				
			||||||
 | 
					  readonly satMarginDuration: number;
 | 
				
			||||||
 | 
					  readonly sunMarginDuration: number;
 | 
				
			||||||
 | 
					  readonly seatsProposed: number;
 | 
				
			||||||
 | 
					  readonly seatsRequested: number;
 | 
				
			||||||
 | 
					  readonly strict: boolean;
 | 
				
			||||||
 | 
					  readonly waypoints: Waypoint[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: DomainEventProps<AdCreatedDomainEvent>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.userId = props.userId;
 | 
				
			||||||
 | 
					    this.driver = props.driver;
 | 
				
			||||||
 | 
					    this.passenger = props.passenger;
 | 
				
			||||||
 | 
					    this.frequency = props.frequency;
 | 
				
			||||||
 | 
					    this.fromDate = props.fromDate;
 | 
				
			||||||
 | 
					    this.toDate = props.toDate;
 | 
				
			||||||
 | 
					    this.monTime = props.monTime;
 | 
				
			||||||
 | 
					    this.tueTime = props.tueTime;
 | 
				
			||||||
 | 
					    this.wedTime = props.wedTime;
 | 
				
			||||||
 | 
					    this.thuTime = props.thuTime;
 | 
				
			||||||
 | 
					    this.friTime = props.friTime;
 | 
				
			||||||
 | 
					    this.satTime = props.satTime;
 | 
				
			||||||
 | 
					    this.sunTime = props.sunTime;
 | 
				
			||||||
 | 
					    this.monMarginDuration = props.monMarginDuration;
 | 
				
			||||||
 | 
					    this.tueMarginDuration = props.tueMarginDuration;
 | 
				
			||||||
 | 
					    this.wedMarginDuration = props.wedMarginDuration;
 | 
				
			||||||
 | 
					    this.thuMarginDuration = props.thuMarginDuration;
 | 
				
			||||||
 | 
					    this.friMarginDuration = props.friMarginDuration;
 | 
				
			||||||
 | 
					    this.satMarginDuration = props.satMarginDuration;
 | 
				
			||||||
 | 
					    this.sunMarginDuration = props.sunMarginDuration;
 | 
				
			||||||
 | 
					    this.seatsProposed = props.seatsProposed;
 | 
				
			||||||
 | 
					    this.seatsRequested = props.seatsRequested;
 | 
				
			||||||
 | 
					    this.strict = props.strict;
 | 
				
			||||||
 | 
					    this.waypoints = props.waypoints;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Waypoint {
 | 
				
			||||||
 | 
					  position: number;
 | 
				
			||||||
 | 
					  name?: string;
 | 
				
			||||||
 | 
					  houseNumber?: string;
 | 
				
			||||||
 | 
					  street?: string;
 | 
				
			||||||
 | 
					  locality?: string;
 | 
				
			||||||
 | 
					  postalCode?: string;
 | 
				
			||||||
 | 
					  country: string;
 | 
				
			||||||
 | 
					  lon: number;
 | 
				
			||||||
 | 
					  lat: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					import { RepositoryPort } from '@libs/ddd';
 | 
				
			||||||
 | 
					import { AdEntity } from '../ad.entity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type AdRepositoryPort = RepositoryPort<AdEntity>;
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					import { DefaultParams } from './default-params.type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface DefaultParamsProviderPort {
 | 
				
			||||||
 | 
					  getParams(): DefaultParams;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -7,8 +7,9 @@ export type DefaultParams = {
 | 
				
			||||||
  SAT_MARGIN: number;
 | 
					  SAT_MARGIN: number;
 | 
				
			||||||
  SUN_MARGIN: number;
 | 
					  SUN_MARGIN: number;
 | 
				
			||||||
  DRIVER: boolean;
 | 
					  DRIVER: boolean;
 | 
				
			||||||
  SEATS_PROVIDED: number;
 | 
					  SEATS_PROPOSED: number;
 | 
				
			||||||
  PASSENGER: boolean;
 | 
					  PASSENGER: boolean;
 | 
				
			||||||
  SEATS_REQUESTED: number;
 | 
					  SEATS_REQUESTED: number;
 | 
				
			||||||
  STRICT: boolean;
 | 
					  STRICT: boolean;
 | 
				
			||||||
 | 
					  DEFAULT_TIMEZONE: string;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					export interface TimeConverterPort {
 | 
				
			||||||
 | 
					  localDateTimeToUtc(
 | 
				
			||||||
 | 
					    date: string,
 | 
				
			||||||
 | 
					    time: string,
 | 
				
			||||||
 | 
					    timezone: string,
 | 
				
			||||||
 | 
					    dst?: boolean,
 | 
				
			||||||
 | 
					  ): Date;
 | 
				
			||||||
 | 
					  utcDatetimeToLocalTime(isoString: string, timezone: string): string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					export interface TimezoneFinderPort {
 | 
				
			||||||
 | 
					  timezones(lon: number, lat: number, defaultTimezone?: string): string[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
 | 
				
			||||||
 | 
					import { FindAdByIdQuery } from './find-ad-by-id.query';
 | 
				
			||||||
 | 
					import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
 | 
				
			||||||
 | 
					import { AdRepositoryPort } from '../../ports/ad.repository.port';
 | 
				
			||||||
 | 
					import { Inject } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { AdEntity } from '../../ad.entity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@QueryHandler(FindAdByIdQuery)
 | 
				
			||||||
 | 
					export class FindAdByIdQueryHandler implements IQueryHandler {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(AD_REPOSITORY)
 | 
				
			||||||
 | 
					    private readonly repository: AdRepositoryPort,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					  async execute(query: FindAdByIdQuery): Promise<AdEntity> {
 | 
				
			||||||
 | 
					    return await this.repository.findOneById(query.id, { waypoints: true });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					import { QueryBase } from '@libs/ddd/query.base';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class FindAdByIdQuery extends QueryBase {
 | 
				
			||||||
 | 
					  readonly id: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(id: string) {
 | 
				
			||||||
 | 
					    super();
 | 
				
			||||||
 | 
					    this.id = id;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					import { Coordinates } from './coordinates';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Address = {
 | 
				
			||||||
 | 
					  name?: string;
 | 
				
			||||||
 | 
					  houseNumber?: string;
 | 
				
			||||||
 | 
					  street?: string;
 | 
				
			||||||
 | 
					  locality?: string;
 | 
				
			||||||
 | 
					  postalCode?: string;
 | 
				
			||||||
 | 
					  country: string;
 | 
				
			||||||
 | 
					} & Coordinates;
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					export type Coordinates = {
 | 
				
			||||||
 | 
					  lon: number;
 | 
				
			||||||
 | 
					  lat: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					export type MarginDurations = {
 | 
				
			||||||
 | 
					  mon?: number;
 | 
				
			||||||
 | 
					  tue?: number;
 | 
				
			||||||
 | 
					  wed?: number;
 | 
				
			||||||
 | 
					  thu?: number;
 | 
				
			||||||
 | 
					  fri?: number;
 | 
				
			||||||
 | 
					  sat?: number;
 | 
				
			||||||
 | 
					  sun?: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					export type Schedule = {
 | 
				
			||||||
 | 
					  mon?: string;
 | 
				
			||||||
 | 
					  tue?: string;
 | 
				
			||||||
 | 
					  wed?: string;
 | 
				
			||||||
 | 
					  thu?: string;
 | 
				
			||||||
 | 
					  fri?: string;
 | 
				
			||||||
 | 
					  sat?: string;
 | 
				
			||||||
 | 
					  sun?: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					import { Address } from './address';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Waypoint = {
 | 
				
			||||||
 | 
					  position?: number;
 | 
				
			||||||
 | 
					} & Address;
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,52 @@
 | 
				
			||||||
 | 
					import { ValueObject } from '@libs/ddd';
 | 
				
			||||||
 | 
					import { CoordinatesProps } from './coordinates.value-object';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Note:
 | 
				
			||||||
 | 
					 * Value Objects with multiple properties can contain
 | 
				
			||||||
 | 
					 * other Value Objects inside if needed.
 | 
				
			||||||
 | 
					 * */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AddressProps {
 | 
				
			||||||
 | 
					  name?: string;
 | 
				
			||||||
 | 
					  houseNumber?: string;
 | 
				
			||||||
 | 
					  street?: string;
 | 
				
			||||||
 | 
					  locality?: string;
 | 
				
			||||||
 | 
					  postalCode?: string;
 | 
				
			||||||
 | 
					  country: string;
 | 
				
			||||||
 | 
					  coordinates: CoordinatesProps;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Address extends ValueObject<AddressProps> {
 | 
				
			||||||
 | 
					  get name(): string {
 | 
				
			||||||
 | 
					    return this.props.name;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get houseNumber(): string {
 | 
				
			||||||
 | 
					    return this.props.houseNumber;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get street(): string {
 | 
				
			||||||
 | 
					    return this.props.street;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get locality(): string {
 | 
				
			||||||
 | 
					    return this.props.locality;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get postalCode(): string {
 | 
				
			||||||
 | 
					    return this.props.postalCode;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get country(): string {
 | 
				
			||||||
 | 
					    return this.props.country;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get coordinates(): CoordinatesProps {
 | 
				
			||||||
 | 
					    return this.props.coordinates;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					  protected validate(props: AddressProps): void {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,26 @@
 | 
				
			||||||
 | 
					import { ValueObject } from '@libs/ddd';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Note:
 | 
				
			||||||
 | 
					 * Value Objects with multiple properties can contain
 | 
				
			||||||
 | 
					 * other Value Objects inside if needed.
 | 
				
			||||||
 | 
					 * */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface CoordinatesProps {
 | 
				
			||||||
 | 
					  lon: number;
 | 
				
			||||||
 | 
					  lat: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Coordinates extends ValueObject<CoordinatesProps> {
 | 
				
			||||||
 | 
					  get lon(): number {
 | 
				
			||||||
 | 
					    return this.props.lon;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get lat(): number {
 | 
				
			||||||
 | 
					    return this.props.lat;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					  protected validate(props: CoordinatesProps): void {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,79 @@
 | 
				
			||||||
 | 
					import { ValueObject } from '@libs/ddd';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Note:
 | 
				
			||||||
 | 
					 * Value Objects with multiple properties can contain
 | 
				
			||||||
 | 
					 * other Value Objects inside if needed.
 | 
				
			||||||
 | 
					 * */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface MarginDurationsProps {
 | 
				
			||||||
 | 
					  mon?: number;
 | 
				
			||||||
 | 
					  tue?: number;
 | 
				
			||||||
 | 
					  wed?: number;
 | 
				
			||||||
 | 
					  thu?: number;
 | 
				
			||||||
 | 
					  fri?: number;
 | 
				
			||||||
 | 
					  sat?: number;
 | 
				
			||||||
 | 
					  sun?: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class MarginDurations extends ValueObject<MarginDurationsProps> {
 | 
				
			||||||
 | 
					  get mon(): number {
 | 
				
			||||||
 | 
					    return this.props.mon;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set mon(margin: number) {
 | 
				
			||||||
 | 
					    this.props.mon = margin;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get tue(): number {
 | 
				
			||||||
 | 
					    return this.props.tue;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set tue(margin: number) {
 | 
				
			||||||
 | 
					    this.props.tue = margin;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get wed(): number {
 | 
				
			||||||
 | 
					    return this.props.wed;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set wed(margin: number) {
 | 
				
			||||||
 | 
					    this.props.wed = margin;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get thu(): number {
 | 
				
			||||||
 | 
					    return this.props.thu;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set thu(margin: number) {
 | 
				
			||||||
 | 
					    this.props.thu = margin;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get fri(): number {
 | 
				
			||||||
 | 
					    return this.props.fri;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set fri(margin: number) {
 | 
				
			||||||
 | 
					    this.props.fri = margin;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get sat(): number {
 | 
				
			||||||
 | 
					    return this.props.sat;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set sat(margin: number) {
 | 
				
			||||||
 | 
					    this.props.sat = margin;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get sun(): number {
 | 
				
			||||||
 | 
					    return this.props.sun;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set sun(margin: number) {
 | 
				
			||||||
 | 
					    this.props.sun = margin;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					  protected validate(props: MarginDurationsProps): void {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,51 @@
 | 
				
			||||||
 | 
					import { ValueObject } from '@libs/ddd';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Note:
 | 
				
			||||||
 | 
					 * Value Objects with multiple properties can contain
 | 
				
			||||||
 | 
					 * other Value Objects inside if needed.
 | 
				
			||||||
 | 
					 * */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ScheduleProps {
 | 
				
			||||||
 | 
					  mon?: string;
 | 
				
			||||||
 | 
					  tue?: string;
 | 
				
			||||||
 | 
					  wed?: string;
 | 
				
			||||||
 | 
					  thu?: string;
 | 
				
			||||||
 | 
					  fri?: string;
 | 
				
			||||||
 | 
					  sat?: string;
 | 
				
			||||||
 | 
					  sun?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Schedule extends ValueObject<ScheduleProps> {
 | 
				
			||||||
 | 
					  get mon(): string | undefined {
 | 
				
			||||||
 | 
					    return this.props.mon;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get tue(): string | undefined {
 | 
				
			||||||
 | 
					    return this.props.tue;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get wed(): string | undefined {
 | 
				
			||||||
 | 
					    return this.props.wed;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get thu(): string | undefined {
 | 
				
			||||||
 | 
					    return this.props.thu;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get fri(): string | undefined {
 | 
				
			||||||
 | 
					    return this.props.fri;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get sat(): string | undefined {
 | 
				
			||||||
 | 
					    return this.props.sat;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get sun(): string | undefined {
 | 
				
			||||||
 | 
					    return this.props.sun;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					  protected validate(props: ScheduleProps): void {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,27 @@
 | 
				
			||||||
 | 
					import { ValueObject } from '@libs/ddd';
 | 
				
			||||||
 | 
					import { AddressProps } from './address.value-object';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Note:
 | 
				
			||||||
 | 
					 * Value Objects with multiple properties can contain
 | 
				
			||||||
 | 
					 * other Value Objects inside if needed.
 | 
				
			||||||
 | 
					 * */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface WaypointProps {
 | 
				
			||||||
 | 
					  position: number;
 | 
				
			||||||
 | 
					  address: AddressProps;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Waypoint extends ValueObject<WaypointProps> {
 | 
				
			||||||
 | 
					  get position(): number {
 | 
				
			||||||
 | 
					    return this.props.position;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get address(): AddressProps {
 | 
				
			||||||
 | 
					    return this.props.address;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					  protected validate(props: WaypointProps): void {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,86 +0,0 @@
 | 
				
			||||||
import { AutoMap } from '@automapper/classes';
 | 
					 | 
				
			||||||
import { Frequency } from '../types/frequency.enum';
 | 
					 | 
				
			||||||
import { Address } from '../entities/address';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class AdCreation {
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  uuid: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  userUuid: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  driver: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  passenger: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  frequency: Frequency;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  fromDate: Date;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  toDate: Date;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  monTime?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  tueTime?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  wedTime?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  thuTime?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  friTime?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  satTime?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  sunTime?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  monMargin: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  tueMargin: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  wedMargin: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  thuMargin: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  friMargin: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  satMargin: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  sunMargin: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  seatsDriver: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  seatsPassenger: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  strict: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  createdAt: Date;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  updatedAt?: Date;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  addresses: { create: Address[] };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,62 +0,0 @@
 | 
				
			||||||
import { AutoMap } from '@automapper/classes';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  IsInt,
 | 
					 | 
				
			||||||
  IsLatitude,
 | 
					 | 
				
			||||||
  IsLongitude,
 | 
					 | 
				
			||||||
  IsOptional,
 | 
					 | 
				
			||||||
  IsString,
 | 
					 | 
				
			||||||
  IsUUID,
 | 
					 | 
				
			||||||
} from 'class-validator';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class AddressDTO {
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsUUID(4)
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  uuid?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsUUID(4)
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  adUuid?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsInt()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  position?: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsLongitude()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  lon: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsLatitude()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  lat: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  name?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsString()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  houseNumber?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsString()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  street?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsString()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  locality?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsString()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  postalCode?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsString()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  country: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,107 +0,0 @@
 | 
				
			||||||
import { AutoMap } from '@automapper/classes';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  IsOptional,
 | 
					 | 
				
			||||||
  IsBoolean,
 | 
					 | 
				
			||||||
  IsDate,
 | 
					 | 
				
			||||||
  IsInt,
 | 
					 | 
				
			||||||
  IsEnum,
 | 
					 | 
				
			||||||
  ValidateNested,
 | 
					 | 
				
			||||||
  ArrayMinSize,
 | 
					 | 
				
			||||||
  IsUUID,
 | 
					 | 
				
			||||||
} from 'class-validator';
 | 
					 | 
				
			||||||
import { Frequency } from '../types/frequency.enum';
 | 
					 | 
				
			||||||
import { Transform, Type } from 'class-transformer';
 | 
					 | 
				
			||||||
import { intToFrequency } from './validators/frequency.mapping';
 | 
					 | 
				
			||||||
import { MarginDTO } from './margin.dto';
 | 
					 | 
				
			||||||
import { ScheduleDTO } from './schedule.dto';
 | 
					 | 
				
			||||||
import { AddressDTO } from './address.dto';
 | 
					 | 
				
			||||||
import { IsPunctualOrRecurrent } from './validators/decorators/is-punctual-or-recurrent.validator';
 | 
					 | 
				
			||||||
import { HasProperDriverSeats } from './validators/decorators/has-driver-seats.validator';
 | 
					 | 
				
			||||||
import { HasProperPassengerSeats } from './validators/decorators/has-passenger-seats.validator';
 | 
					 | 
				
			||||||
import { HasProperPositionIndexes } from './validators/decorators/address-position.validator';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class CreateAdRequest {
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsUUID(4)
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  uuid?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsUUID(4)
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  userUuid: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsBoolean()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  driver?: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsBoolean()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  passenger?: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Transform(({ value }) => intToFrequency(value), {
 | 
					 | 
				
			||||||
    toClassOnly: true,
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
  @IsEnum(Frequency)
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  frequency: Frequency;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsPunctualOrRecurrent()
 | 
					 | 
				
			||||||
  @Type(() => Date)
 | 
					 | 
				
			||||||
  @IsDate()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  departureDateTime?: Date;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsPunctualOrRecurrent()
 | 
					 | 
				
			||||||
  @Type(() => Date)
 | 
					 | 
				
			||||||
  @IsDate()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  fromDate?: Date;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsPunctualOrRecurrent()
 | 
					 | 
				
			||||||
  @Type(() => Date)
 | 
					 | 
				
			||||||
  @IsDate()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  toDate?: Date;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @Type(() => ScheduleDTO)
 | 
					 | 
				
			||||||
  @IsPunctualOrRecurrent()
 | 
					 | 
				
			||||||
  @ValidateNested({ each: true })
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  schedule: ScheduleDTO = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @Type(() => MarginDTO)
 | 
					 | 
				
			||||||
  @ValidateNested({ each: true })
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  marginDurations?: MarginDTO;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @HasProperDriverSeats()
 | 
					 | 
				
			||||||
  @IsInt()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  seatsDriver?: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @HasProperPassengerSeats()
 | 
					 | 
				
			||||||
  @IsInt()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  seatsPassenger?: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsBoolean()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  strict?: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @ArrayMinSize(2)
 | 
					 | 
				
			||||||
  @Type(() => AddressDTO)
 | 
					 | 
				
			||||||
  @HasProperPositionIndexes()
 | 
					 | 
				
			||||||
  @ValidateNested({ each: true })
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  addresses: AddressDTO[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,13 +0,0 @@
 | 
				
			||||||
import { AddressDTO } from '../address.dto';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const hasProperPositionIndexes = (value: AddressDTO[]): boolean => {
 | 
					 | 
				
			||||||
  if (value.every((address) => address.position === undefined)) return true;
 | 
					 | 
				
			||||||
  if (value.every((address) => typeof address.position === 'number')) {
 | 
					 | 
				
			||||||
    value.sort((a, b) => a.position - b.position);
 | 
					 | 
				
			||||||
    for (let i = 1; i < value.length; i++) {
 | 
					 | 
				
			||||||
      if (value[i - 1].position >= value[i].position) return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return false;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,24 +0,0 @@
 | 
				
			||||||
import { ValidateBy, ValidationOptions, buildMessage } from 'class-validator';
 | 
					 | 
				
			||||||
import { AddressDTO } from '../../address.dto';
 | 
					 | 
				
			||||||
import { hasProperPositionIndexes } from '../address-position';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const HasProperPositionIndexes = (
 | 
					 | 
				
			||||||
  validationOptions?: ValidationOptions,
 | 
					 | 
				
			||||||
): PropertyDecorator =>
 | 
					 | 
				
			||||||
  ValidateBy(
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: '',
 | 
					 | 
				
			||||||
      constraints: [],
 | 
					 | 
				
			||||||
      validator: {
 | 
					 | 
				
			||||||
        validate: (value: AddressDTO[]): boolean =>
 | 
					 | 
				
			||||||
          hasProperPositionIndexes(value),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        defaultMessage: buildMessage(
 | 
					 | 
				
			||||||
          () =>
 | 
					 | 
				
			||||||
            `indexes position incorrect, please provide a complete list of indexes or ordened list of adresses from start to end of journey`,
 | 
					 | 
				
			||||||
          validationOptions,
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    validationOptions,
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,26 +0,0 @@
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  ValidateBy,
 | 
					 | 
				
			||||||
  ValidationArguments,
 | 
					 | 
				
			||||||
  ValidationOptions,
 | 
					 | 
				
			||||||
  buildMessage,
 | 
					 | 
				
			||||||
} from 'class-validator';
 | 
					 | 
				
			||||||
import { hasProperDriverSeats } from '../has-driver-seats';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const HasProperDriverSeats = (
 | 
					 | 
				
			||||||
  validationOptions?: ValidationOptions,
 | 
					 | 
				
			||||||
): PropertyDecorator =>
 | 
					 | 
				
			||||||
  ValidateBy(
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: '',
 | 
					 | 
				
			||||||
      constraints: [],
 | 
					 | 
				
			||||||
      validator: {
 | 
					 | 
				
			||||||
        validate: (value: any, args: ValidationArguments): boolean =>
 | 
					 | 
				
			||||||
          hasProperDriverSeats(args),
 | 
					 | 
				
			||||||
        defaultMessage: buildMessage(
 | 
					 | 
				
			||||||
          () => `driver and driver seats are not correct`,
 | 
					 | 
				
			||||||
          validationOptions,
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    validationOptions,
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,27 +0,0 @@
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  ValidateBy,
 | 
					 | 
				
			||||||
  ValidationArguments,
 | 
					 | 
				
			||||||
  ValidationOptions,
 | 
					 | 
				
			||||||
  buildMessage,
 | 
					 | 
				
			||||||
} from 'class-validator';
 | 
					 | 
				
			||||||
import { isPunctualOrRecurrent } from '../is-punctual-or-recurrent';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const IsPunctualOrRecurrent = (
 | 
					 | 
				
			||||||
  validationOptions?: ValidationOptions,
 | 
					 | 
				
			||||||
): PropertyDecorator =>
 | 
					 | 
				
			||||||
  ValidateBy(
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: '',
 | 
					 | 
				
			||||||
      constraints: [],
 | 
					 | 
				
			||||||
      validator: {
 | 
					 | 
				
			||||||
        validate: (value, args: ValidationArguments): boolean =>
 | 
					 | 
				
			||||||
          isPunctualOrRecurrent(args),
 | 
					 | 
				
			||||||
        defaultMessage: buildMessage(
 | 
					 | 
				
			||||||
          () =>
 | 
					 | 
				
			||||||
            `the departure Date and time , from date, to date and schedule must be properly set on recurrent or punctual ad`,
 | 
					 | 
				
			||||||
          validationOptions,
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    validationOptions,
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,7 +0,0 @@
 | 
				
			||||||
import { Frequency } from '../../types/frequency.enum';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const intToFrequency = (index: number): Frequency => {
 | 
					 | 
				
			||||||
  if (index == 1) return Frequency.PUNCTUAL;
 | 
					 | 
				
			||||||
  if (index == 2) return Frequency.RECURRENT;
 | 
					 | 
				
			||||||
  return undefined;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,19 +0,0 @@
 | 
				
			||||||
import { ValidationArguments } from 'class-validator';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const hasProperDriverSeats = (args: ValidationArguments): boolean => {
 | 
					 | 
				
			||||||
  if (
 | 
					 | 
				
			||||||
    args.object['driver'] === true &&
 | 
					 | 
				
			||||||
    typeof args.object['seatsDriver'] === 'number'
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
    return args.object['seatsDriver'] > 0;
 | 
					 | 
				
			||||||
  if (
 | 
					 | 
				
			||||||
    (args.object['driver'] === false ||
 | 
					 | 
				
			||||||
      args.object['driver'] === null ||
 | 
					 | 
				
			||||||
      args.object['driver'] === undefined) &&
 | 
					 | 
				
			||||||
    (args.object['seatsDriver'] === 0 ||
 | 
					 | 
				
			||||||
      args.object['seatsDriver'] === null ||
 | 
					 | 
				
			||||||
      args.object['seatsDriver'] === undefined)
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
  return false;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,19 +0,0 @@
 | 
				
			||||||
import { ValidationArguments } from 'class-validator';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const hasProperPassengerSeats = (args: ValidationArguments): boolean => {
 | 
					 | 
				
			||||||
  if (
 | 
					 | 
				
			||||||
    args.object['passenger'] === true &&
 | 
					 | 
				
			||||||
    typeof args.object['seatsPassenger'] === 'number'
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
    return args.object['seatsPassenger'] > 0;
 | 
					 | 
				
			||||||
  if (
 | 
					 | 
				
			||||||
    (args.object['passenger'] === false ||
 | 
					 | 
				
			||||||
      args.object['passenger'] === null ||
 | 
					 | 
				
			||||||
      args.object['passenger'] === undefined) &&
 | 
					 | 
				
			||||||
    (args.object['seatsPassenger'] === 0 ||
 | 
					 | 
				
			||||||
      args.object['seatsPassenger'] === null ||
 | 
					 | 
				
			||||||
      args.object['seatsPassenger'] === undefined)
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
  return false;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,16 +0,0 @@
 | 
				
			||||||
import { ValidationArguments } from 'class-validator';
 | 
					 | 
				
			||||||
import { Frequency } from '../../types/frequency.enum';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const isPunctual = (args: ValidationArguments): boolean =>
 | 
					 | 
				
			||||||
  args.object['frequency'] === Frequency.PUNCTUAL &&
 | 
					 | 
				
			||||||
  args.object['departureDateTime'] instanceof Date &&
 | 
					 | 
				
			||||||
  !Object.keys(args.object['schedule']).length;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const isRecurrent = (args: ValidationArguments): boolean =>
 | 
					 | 
				
			||||||
  args.object['frequency'] === Frequency.RECURRENT &&
 | 
					 | 
				
			||||||
  args.object['fromDate'] instanceof Date &&
 | 
					 | 
				
			||||||
  args.object['toDate'] instanceof Date &&
 | 
					 | 
				
			||||||
  Object.keys(args.object['schedule']).length > 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const isPunctualOrRecurrent = (args: ValidationArguments): boolean =>
 | 
					 | 
				
			||||||
  isPunctual(args) || isRecurrent(args);
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,132 +0,0 @@
 | 
				
			||||||
import { AutoMap } from '@automapper/classes';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  IsOptional,
 | 
					 | 
				
			||||||
  IsString,
 | 
					 | 
				
			||||||
  IsBoolean,
 | 
					 | 
				
			||||||
  IsDate,
 | 
					 | 
				
			||||||
  IsInt,
 | 
					 | 
				
			||||||
  IsEnum,
 | 
					 | 
				
			||||||
  ValidateNested,
 | 
					 | 
				
			||||||
  ArrayMinSize,
 | 
					 | 
				
			||||||
  IsUUID,
 | 
					 | 
				
			||||||
} from 'class-validator';
 | 
					 | 
				
			||||||
import { Address } from '../entities/address';
 | 
					 | 
				
			||||||
import { Frequency } from '../types/frequency.enum';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class Ad {
 | 
					 | 
				
			||||||
  @IsUUID(4)
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  uuid: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsUUID(4)
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  userUuid: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsBoolean()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  driver: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsBoolean()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  passenger: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsEnum(Frequency)
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  frequency: Frequency;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsDate()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  fromDate: Date;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsDate()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  toDate: Date;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsDate()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  monTime?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsString()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  tueTime?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsString()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  wedTime?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsString()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  thuTime?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsString()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  friTime?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsString()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  satTime?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsString()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  sunTime?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsInt()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  monMargin: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsInt()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  tueMargin: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsInt()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  wedMargin: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsInt()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  thuMargin: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsInt()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  friMargin: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsInt()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  satMargin: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsInt()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  sunMargin: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsInt()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  seatsDriver: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsInt()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  seatsPassenger: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsBoolean()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  strict: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsDate()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  createdAt: Date;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsDate()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  updatedAt?: Date;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @ArrayMinSize(2)
 | 
					 | 
				
			||||||
  @ValidateNested({ each: true })
 | 
					 | 
				
			||||||
  @AutoMap(() => [Address])
 | 
					 | 
				
			||||||
  addresses: Address[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,40 +0,0 @@
 | 
				
			||||||
import { AutoMap } from '@automapper/classes';
 | 
					 | 
				
			||||||
import { IsInt, IsUUID } from 'class-validator';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class Address {
 | 
					 | 
				
			||||||
  @IsUUID(4)
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  uuid: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsUUID(4)
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  adUuid: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsInt()
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  position: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  lon: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  lat: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  name?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  houseNumber?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  street?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  locality: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  postalCode: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @AutoMap()
 | 
					 | 
				
			||||||
  country: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,35 +0,0 @@
 | 
				
			||||||
import { CreateAdRequest } from '../dtos/create-ad.request';
 | 
					 | 
				
			||||||
import { Day } from '../types/day.enum';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Frequency } from '../types/frequency.enum';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class FrequencyNormaliser {
 | 
					 | 
				
			||||||
  fromDateResolver(createAdRequest: CreateAdRequest): Date {
 | 
					 | 
				
			||||||
    if (createAdRequest.frequency === Frequency.PUNCTUAL)
 | 
					 | 
				
			||||||
      return createAdRequest.departureDateTime;
 | 
					 | 
				
			||||||
    return createAdRequest.fromDate;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  toDateResolver(createAdRequest: CreateAdRequest): Date {
 | 
					 | 
				
			||||||
    if (createAdRequest.frequency === Frequency.PUNCTUAL)
 | 
					 | 
				
			||||||
      return createAdRequest.departureDateTime;
 | 
					 | 
				
			||||||
    return createAdRequest.toDate;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  scheduleResolver = (
 | 
					 | 
				
			||||||
    createAdRequest: CreateAdRequest,
 | 
					 | 
				
			||||||
    day: number,
 | 
					 | 
				
			||||||
  ): string => {
 | 
					 | 
				
			||||||
    if (
 | 
					 | 
				
			||||||
      Object.keys(createAdRequest.schedule).length === 0 &&
 | 
					 | 
				
			||||||
      createAdRequest.frequency == Frequency.PUNCTUAL &&
 | 
					 | 
				
			||||||
      createAdRequest.departureDateTime.getDay() === day
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
      return `${createAdRequest.departureDateTime
 | 
					 | 
				
			||||||
        .getHours()
 | 
					 | 
				
			||||||
        .toString()
 | 
					 | 
				
			||||||
        .padStart(2, '0')}:${createAdRequest.departureDateTime
 | 
					 | 
				
			||||||
        .getMinutes()
 | 
					 | 
				
			||||||
        .toString()
 | 
					 | 
				
			||||||
        .padStart(2, '0')}`;
 | 
					 | 
				
			||||||
    return createAdRequest.schedule[Day[day]];
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,4 +0,0 @@
 | 
				
			||||||
import { DefaultParams } from '../types/default-params.type';
 | 
					 | 
				
			||||||
export interface IProvideParams {
 | 
					 | 
				
			||||||
  getParams(): DefaultParams;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,9 +0,0 @@
 | 
				
			||||||
export enum Day {
 | 
					 | 
				
			||||||
  sun = 0,
 | 
					 | 
				
			||||||
  mon,
 | 
					 | 
				
			||||||
  tue,
 | 
					 | 
				
			||||||
  wed,
 | 
					 | 
				
			||||||
  thu,
 | 
					 | 
				
			||||||
  fri,
 | 
					 | 
				
			||||||
  sat,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,4 +0,0 @@
 | 
				
			||||||
export enum Frequency {
 | 
					 | 
				
			||||||
  PUNCTUAL = 'PUNCTUAL',
 | 
					 | 
				
			||||||
  RECURRENT = 'RECURRENT',
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,111 +0,0 @@
 | 
				
			||||||
import { Mapper } from '@automapper/core';
 | 
					 | 
				
			||||||
import { InjectMapper } from '@automapper/nestjs';
 | 
					 | 
				
			||||||
import { Inject } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { CommandHandler } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import { AdsRepository } from '../../adapters/secondaries/ads.repository';
 | 
					 | 
				
			||||||
import { CreateAdCommand } from '../../commands/create-ad.command';
 | 
					 | 
				
			||||||
import { CreateAdRequest } from '../dtos/create-ad.request';
 | 
					 | 
				
			||||||
import { IProvideParams } from '../interfaces/param-provider.interface';
 | 
					 | 
				
			||||||
import { DefaultParams } from '../types/default-params.type';
 | 
					 | 
				
			||||||
import { AdCreation } from '../dtos/ad.creation';
 | 
					 | 
				
			||||||
import { Ad } from '../entities/ad';
 | 
					 | 
				
			||||||
import { PARAMS_PROVIDER } from '../../ad.constants';
 | 
					 | 
				
			||||||
import { IPublishMessage } from '../../../../interfaces/message-publisher';
 | 
					 | 
				
			||||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@CommandHandler(CreateAdCommand)
 | 
					 | 
				
			||||||
export class CreateAdUseCase {
 | 
					 | 
				
			||||||
  private readonly defaultParams: DefaultParams;
 | 
					 | 
				
			||||||
  private ad: AdCreation;
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    private readonly repository: AdsRepository,
 | 
					 | 
				
			||||||
    @Inject(MESSAGE_PUBLISHER)
 | 
					 | 
				
			||||||
    private readonly messagePublisher: IPublishMessage,
 | 
					 | 
				
			||||||
    @InjectMapper() private readonly mapper: Mapper,
 | 
					 | 
				
			||||||
    @Inject(PARAMS_PROVIDER)
 | 
					 | 
				
			||||||
    private readonly defaultParamsProvider: IProvideParams,
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    this.defaultParams = defaultParamsProvider.getParams();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async execute(command: CreateAdCommand): Promise<Ad> {
 | 
					 | 
				
			||||||
    this.ad = this.mapper.map(
 | 
					 | 
				
			||||||
      command.createAdRequest,
 | 
					 | 
				
			||||||
      CreateAdRequest,
 | 
					 | 
				
			||||||
      AdCreation,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    this.setDefaultMarginDurations();
 | 
					 | 
				
			||||||
    this.setDefaultAddressesPosition();
 | 
					 | 
				
			||||||
    this.setDefaultDriverAndPassengerParameters();
 | 
					 | 
				
			||||||
    this.setDefaultStrict();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const adCreated: Ad = await this.repository.create(this.ad);
 | 
					 | 
				
			||||||
      this.messagePublisher.publish('ad.create', JSON.stringify(adCreated));
 | 
					 | 
				
			||||||
      this.messagePublisher.publish(
 | 
					 | 
				
			||||||
        'logging.ad.create.info',
 | 
					 | 
				
			||||||
        JSON.stringify(adCreated),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      return adCreated;
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      let key = 'logging.ad.create.crit';
 | 
					 | 
				
			||||||
      if (error.message.includes('Already exists')) {
 | 
					 | 
				
			||||||
        key = 'logging.ad.create.warning';
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      this.messagePublisher.publish(
 | 
					 | 
				
			||||||
        key,
 | 
					 | 
				
			||||||
        JSON.stringify({
 | 
					 | 
				
			||||||
          command,
 | 
					 | 
				
			||||||
          error,
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      throw error;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private setDefaultMarginDurations = (): void => {
 | 
					 | 
				
			||||||
    if (this.ad.monMargin === undefined)
 | 
					 | 
				
			||||||
      this.ad.monMargin = this.defaultParams.MON_MARGIN;
 | 
					 | 
				
			||||||
    if (this.ad.tueMargin === undefined)
 | 
					 | 
				
			||||||
      this.ad.tueMargin = this.defaultParams.TUE_MARGIN;
 | 
					 | 
				
			||||||
    if (this.ad.wedMargin === undefined)
 | 
					 | 
				
			||||||
      this.ad.wedMargin = this.defaultParams.WED_MARGIN;
 | 
					 | 
				
			||||||
    if (this.ad.thuMargin === undefined)
 | 
					 | 
				
			||||||
      this.ad.thuMargin = this.defaultParams.THU_MARGIN;
 | 
					 | 
				
			||||||
    if (this.ad.friMargin === undefined)
 | 
					 | 
				
			||||||
      this.ad.friMargin = this.defaultParams.FRI_MARGIN;
 | 
					 | 
				
			||||||
    if (this.ad.satMargin === undefined)
 | 
					 | 
				
			||||||
      this.ad.satMargin = this.defaultParams.SAT_MARGIN;
 | 
					 | 
				
			||||||
    if (this.ad.sunMargin === undefined)
 | 
					 | 
				
			||||||
      this.ad.sunMargin = this.defaultParams.SUN_MARGIN;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private setDefaultStrict = (): void => {
 | 
					 | 
				
			||||||
    if (this.ad.strict === undefined)
 | 
					 | 
				
			||||||
      this.ad.strict = this.defaultParams.STRICT;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private setDefaultDriverAndPassengerParameters = (): void => {
 | 
					 | 
				
			||||||
    this.ad.driver = !!this.ad.driver;
 | 
					 | 
				
			||||||
    this.ad.passenger = !!this.ad.passenger;
 | 
					 | 
				
			||||||
    if (!this.ad.driver && !this.ad.passenger) {
 | 
					 | 
				
			||||||
      this.ad.driver = this.defaultParams.DRIVER;
 | 
					 | 
				
			||||||
      this.ad.seatsDriver = this.defaultParams.SEATS_PROVIDED;
 | 
					 | 
				
			||||||
      this.ad.passenger = this.defaultParams.PASSENGER;
 | 
					 | 
				
			||||||
      this.ad.seatsPassenger = this.defaultParams.SEATS_REQUESTED;
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!this.ad.seatsDriver || this.ad.seatsDriver <= 0)
 | 
					 | 
				
			||||||
      this.ad.seatsDriver = this.defaultParams.SEATS_PROVIDED;
 | 
					 | 
				
			||||||
    if (!this.ad.seatsPassenger || this.ad.seatsPassenger <= 0)
 | 
					 | 
				
			||||||
      this.ad.seatsPassenger = this.defaultParams.SEATS_REQUESTED;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private setDefaultAddressesPosition = (): void => {
 | 
					 | 
				
			||||||
    if (this.ad.addresses.create[0].position === undefined) {
 | 
					 | 
				
			||||||
      for (let i = 0; i < this.ad.addresses.create.length; i++) {
 | 
					 | 
				
			||||||
        this.ad.addresses.create[i].position = i;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,33 +0,0 @@
 | 
				
			||||||
import { Inject, NotFoundException } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { QueryHandler } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import { FindAdByUuidQuery } from '../../queries/find-ad-by-uuid.query';
 | 
					 | 
				
			||||||
import { AdsRepository } from '../../adapters/secondaries/ads.repository';
 | 
					 | 
				
			||||||
import { Ad } from '../entities/ad';
 | 
					 | 
				
			||||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
 | 
					 | 
				
			||||||
import { IPublishMessage } from '../../../../interfaces/message-publisher';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@QueryHandler(FindAdByUuidQuery)
 | 
					 | 
				
			||||||
export class FindAdByUuidUseCase {
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    private readonly repository: AdsRepository,
 | 
					 | 
				
			||||||
    @Inject(MESSAGE_PUBLISHER)
 | 
					 | 
				
			||||||
    private readonly messagePublisher: IPublishMessage,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async execute(findAdByUuid: FindAdByUuidQuery): Promise<Ad> {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const ad = await this.repository.findOneByUuid(findAdByUuid.uuid);
 | 
					 | 
				
			||||||
      if (!ad) throw new NotFoundException();
 | 
					 | 
				
			||||||
      return ad;
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      this.messagePublisher.publish(
 | 
					 | 
				
			||||||
        'logging.ad.read.warning',
 | 
					 | 
				
			||||||
        JSON.stringify({
 | 
					 | 
				
			||||||
          query: findAdByUuid,
 | 
					 | 
				
			||||||
          error,
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      throw error;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue