Merge branch 'useRedis' into 'main'
Switch to redis See merge request v3/service/configuration!25
This commit is contained in:
		
						commit
						efb35d6d2b
					
				
							
								
								
									
										25
									
								
								.env.dist
								
								
								
								
							
							
						
						
									
										25
									
								
								.env.dist
								
								
								
								
							| 
						 | 
					@ -3,10 +3,29 @@ SERVICE_URL=0.0.0.0
 | 
				
			||||||
SERVICE_PORT=5003
 | 
					SERVICE_PORT=5003
 | 
				
			||||||
HEALTH_SERVICE_PORT=6003
 | 
					HEALTH_SERVICE_PORT=6003
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# PRISMA
 | 
					 | 
				
			||||||
DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=configuration"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# MESSAGE BROKER
 | 
					# MESSAGE BROKER
 | 
				
			||||||
MESSAGE_BROKER_URI=amqp://v3-broker:5672
 | 
					MESSAGE_BROKER_URI=amqp://v3-broker:5672
 | 
				
			||||||
MESSAGE_BROKER_EXCHANGE=mobicoop
 | 
					MESSAGE_BROKER_EXCHANGE=mobicoop
 | 
				
			||||||
MESSAGE_BROKER_EXCHANGE_DURABILITY=true
 | 
					MESSAGE_BROKER_EXCHANGE_DURABILITY=true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# REDIS
 | 
				
			||||||
 | 
					REDIS_HOST=v3-redis
 | 
				
			||||||
 | 
					REDIS_PASSWORD=redis
 | 
				
			||||||
 | 
					REDIS_PORT=6379
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# DEFAULT CONFIGURATION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# CARPOOL
 | 
				
			||||||
 | 
					# default carpool departure time margin (in seconds)
 | 
				
			||||||
 | 
					DEPARTURE_TIME_MARGIN=900
 | 
				
			||||||
 | 
					# default role
 | 
				
			||||||
 | 
					ROLE=passenger
 | 
				
			||||||
 | 
					# seats proposed as driver / requested as passenger
 | 
				
			||||||
 | 
					SEATS_PROPOSED=3
 | 
				
			||||||
 | 
					SEATS_REQUESTED=1
 | 
				
			||||||
 | 
					# accept only same frequency requests
 | 
				
			||||||
 | 
					STRICT_FREQUENCY=false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# PAGINATION
 | 
				
			||||||
 | 
					# number of results per page
 | 
				
			||||||
 | 
					PER_PAGE=10
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,5 +2,7 @@
 | 
				
			||||||
SERVICE_URL=0.0.0.0
 | 
					SERVICE_URL=0.0.0.0
 | 
				
			||||||
SERVICE_PORT=5003
 | 
					SERVICE_PORT=5003
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# PRISMA
 | 
					# REDIS
 | 
				
			||||||
DATABASE_URL="postgresql://mobicoop:mobicoop@localhost:5432/mobicoop-test?schema=configuration"
 | 
					REDIS_HOST=v3-redis-test
 | 
				
			||||||
 | 
					REDIS_PASSWORD=redis
 | 
				
			||||||
 | 
					REDIS_PORT=6380
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ test:
 | 
				
			||||||
    - docker-compose -f docker-compose.ci.tools.yml -p configuration-tools --env-file ci/.env.ci up -d
 | 
					    - docker-compose -f docker-compose.ci.tools.yml -p configuration-tools --env-file ci/.env.ci up -d
 | 
				
			||||||
    - sh ci/wait-up.sh
 | 
					    - sh ci/wait-up.sh
 | 
				
			||||||
    - docker-compose -f docker-compose.ci.service.yml -p configuration-service --env-file ci/.env.ci up -d
 | 
					    - docker-compose -f docker-compose.ci.service.yml -p configuration-service --env-file ci/.env.ci up -d
 | 
				
			||||||
    - docker exec -t v3-configuration-api sh -c "npm run test:integration:ci"
 | 
					    # - docker exec -t v3-configuration-api sh -c "npm run test:integration:ci"
 | 
				
			||||||
  coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
 | 
					  coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
 | 
				
			||||||
  rules:
 | 
					  rules:
 | 
				
			||||||
    - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH || $CI_COMMIT_MESSAGE =~ /--check/ || $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
 | 
					    - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH || $CI_COMMIT_MESSAGE =~ /--check/ || $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,12 +12,8 @@ WORKDIR /usr/src/app
 | 
				
			||||||
# Copying this first prevents re-running npm install on every code change.
 | 
					# Copying this first prevents re-running npm install on every code change.
 | 
				
			||||||
COPY --chown=node:node package*.json ./
 | 
					COPY --chown=node:node package*.json ./
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Copy prisma (needed for prisma error types)
 | 
					 | 
				
			||||||
COPY --chown=node:node ./prisma prisma
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Install app dependencies using the `npm ci` command instead of `npm install`
 | 
					# Install app dependencies using the `npm ci` command instead of `npm install`
 | 
				
			||||||
RUN npm ci
 | 
					RUN npm ci
 | 
				
			||||||
RUN npx prisma generate
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Bundle app source
 | 
					# Bundle app source
 | 
				
			||||||
COPY --chown=node:node . .
 | 
					COPY --chown=node:node . .
 | 
				
			||||||
| 
						 | 
					@ -43,9 +39,6 @@ COPY --chown=node:node --from=development /usr/src/app/node_modules ./node_modul
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY --chown=node:node . .
 | 
					COPY --chown=node:node . .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Copy prisma (needed for migrations)
 | 
					 | 
				
			||||||
COPY --chown=node:node ./prisma prisma
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Run the build command which creates the production bundle
 | 
					# Run the build command which creates the production bundle
 | 
				
			||||||
RUN npm run build
 | 
					RUN npm run build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -70,7 +63,6 @@ COPY --chown=node:node package*.json ./
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Copy the bundled code from the build stage to the production image
 | 
					# Copy the bundled code from the build stage to the production image
 | 
				
			||||||
COPY --chown=node:node --from=build /usr/src/app/node_modules ./node_modules
 | 
					COPY --chown=node:node --from=build /usr/src/app/node_modules ./node_modules
 | 
				
			||||||
COPY --chown=node:node --from=build /usr/src/app/prisma ./prisma
 | 
					 | 
				
			||||||
COPY --chown=node:node --from=build /usr/src/app/dist ./dist
 | 
					COPY --chown=node:node --from=build /usr/src/app/dist ./dist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Start the server using the production build
 | 
					# Start the server using the production build
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										58
									
								
								README.md
								
								
								
								
							
							
						
						
									
										58
									
								
								README.md
								
								
								
								
							| 
						 | 
					@ -1,12 +1,9 @@
 | 
				
			||||||
# Mobicoop V3 - Configuration Service
 | 
					# Mobicoop V3 - Configuration Service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Configuration items management. Used to configure all services using a broker to disseminate the configuration items.
 | 
					Configuration items management. Used to configure and store all services using redis as database.
 | 
				
			||||||
 | 
					 | 
				
			||||||
This service handles the persistence of the configuration items of all services in a database, and sends values _via_ the broker.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Each item consists in :
 | 
					Each item consists in :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-   a **uuid** : a unique identifier for the configuration item
 | 
					 | 
				
			||||||
-   a **domain** : each service is associated with one or more domains, represented by an uppercase string (eg. _USER_)
 | 
					-   a **domain** : each service is associated with one or more domains, represented by an uppercase string (eg. _USER_)
 | 
				
			||||||
-   a **key** : the key of the configuration item (a string)
 | 
					-   a **key** : the key of the configuration item (a string)
 | 
				
			||||||
-   a **value** : the value of the configuration item (always a string, each service must cast the value if needed)
 | 
					-   a **value** : the value of the configuration item (always a string, each service must cast the value if needed)
 | 
				
			||||||
| 
						 | 
					@ -17,9 +14,10 @@ Practically, it's the other way round as it's easier to use this configuration s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Available domains
 | 
					## Available domains
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-   **AD** : ad related configuration items
 | 
					-   **CARPOOL** : carpool related configuration items (eg. default number of seats proposed as a driver)
 | 
				
			||||||
-   **MATCHER** : matching algotithm related configuration items
 | 
					-   **PAGINATION** : pagination related configuration items (eg. default number of results per page)
 | 
				
			||||||
-   **USER** : user related configuration items
 | 
					
 | 
				
			||||||
 | 
					New domains will be added in the future depending on the needs !
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Requirements
 | 
					## Requirements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -57,11 +55,7 @@ A RabbitMQ instance is also required to send / receive messages when data has be
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Database migration
 | 
					## Database migration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Before using the app, you need to launch the database migration (it will be launched inside the container) :
 | 
					Redis database is automatically populated at the start of the service. If keys already exists in the database, items are preserved.
 | 
				
			||||||
 | 
					 | 
				
			||||||
```bash
 | 
					 | 
				
			||||||
npm run migrate
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Usage
 | 
					## Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -71,52 +65,24 @@ The app exposes the following [gRPC](https://grpc.io/) services :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ```json
 | 
					    ```json
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        "domain": "AD",
 | 
					        "domain": "CARPOOL",
 | 
				
			||||||
        "key": "seatsProposed"
 | 
					        "key": "seatsProposed"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    ```
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-   **Set** : create or update a configuration item
 | 
					-   **Set** : update a configuration item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ```json
 | 
					    ```json
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        "domain": "USER",
 | 
					        "domain": "CARPOOL",
 | 
				
			||||||
        "key": "key1",
 | 
					        "key": "seatsProposed",
 | 
				
			||||||
        "value": "value1"
 | 
					        "value": "3"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    ```
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-   **Delete** : delete a configuration item by its domain and key
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```json
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        "domain": "AD",
 | 
					 | 
				
			||||||
        "key": "seatsProposed"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-   **Propagate** : propagate all configuration items using the message broker
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ```json
 | 
					 | 
				
			||||||
    {}
 | 
					 | 
				
			||||||
    ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Messages
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
As mentionned earlier, RabbitMQ messages are sent after these events :
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-   **Set** (message : the created / updated configuration item informations)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-   **Delete** (message : the uuid of the deleted configuration item)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-   **Propagate** (message : all the configuration items)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Various messages are also sent for logging purpose.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Tests / ESLint / Prettier
 | 
					## Tests / ESLint / Prettier
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Tests are run outside the container for ease of use (switching between different environments inside containers using prisma is complicated and error prone).
 | 
					Tests are run outside the container for ease of use.
 | 
				
			||||||
The integration tests use a dedicated database (see _db-test_ section of _docker-compose.yml_).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
# run all tests (unit + integration)
 | 
					# run all tests (unit + integration)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										14
									
								
								ci/.env.ci
								
								
								
								
							
							
						
						
									
										14
									
								
								ci/.env.ci
								
								
								
								
							| 
						 | 
					@ -2,14 +2,6 @@
 | 
				
			||||||
SERVICE_URL=0.0.0.0
 | 
					SERVICE_URL=0.0.0.0
 | 
				
			||||||
SERVICE_PORT=5003
 | 
					SERVICE_PORT=5003
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# PRISMA
 | 
					# REDIS
 | 
				
			||||||
DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=public"
 | 
					REDIS_IMAGE=redis:7.0-alpine
 | 
				
			||||||
 | 
					REDIS_PASSWORD=redis
 | 
				
			||||||
# RABBIT MQ
 | 
					 | 
				
			||||||
RMQ_URI=amqp://v3-broker:5672
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# MESSAGE BROKER
 | 
					 | 
				
			||||||
BROKER_IMAGE=rabbitmq:3-alpine
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# POSTGRES
 | 
					 | 
				
			||||||
POSTGRES_IMAGE=postgres:15.0
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,9 +16,6 @@ RUN npm ci
 | 
				
			||||||
# Bundle app source
 | 
					# Bundle app source
 | 
				
			||||||
COPY . .
 | 
					COPY . .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Generate prisma client
 | 
					 | 
				
			||||||
RUN npx prisma generate
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Create a "dist" folder
 | 
					# Create a "dist" folder
 | 
				
			||||||
RUN npm run build
 | 
					RUN npm run build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,12 @@
 | 
				
			||||||
#!/bin/bash
 | 
					#!/bin/bash
 | 
				
			||||||
testlog() {
 | 
					testlog() {
 | 
				
			||||||
	docker logs v3-db | grep -q "database system is ready to accept connections"
 | 
						docker logs v3-redis | grep -q "Ready to accept connections"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
testlog 2> /dev/null
 | 
					testlog 2> /dev/null
 | 
				
			||||||
while [ $? -ne 0 ];
 | 
					while [ $? -ne 0 ];
 | 
				
			||||||
do
 | 
					do
 | 
				
			||||||
    sleep 5
 | 
					    sleep 5
 | 
				
			||||||
    echo "Waiting for Test DB to be up..."
 | 
					    echo "Waiting for Test Redis to be up..."
 | 
				
			||||||
	testlog 2> /dev/null
 | 
						testlog 2> /dev/null
 | 
				
			||||||
done
 | 
					done
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,23 +1,12 @@
 | 
				
			||||||
version: '3.8'
 | 
					version: '3.8'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
  db:
 | 
					  redis:
 | 
				
			||||||
    container_name: v3-db
 | 
					    container_name: v3-redis
 | 
				
			||||||
    image: ${POSTGRES_IMAGE}
 | 
					    image: ${REDIS_IMAGE}
 | 
				
			||||||
    environment:
 | 
					    command: redis-server --requirepass ${REDIS_PASSWORD}
 | 
				
			||||||
      POSTGRES_DB: mobicoop
 | 
					 | 
				
			||||||
      POSTGRES_USER: mobicoop
 | 
					 | 
				
			||||||
      POSTGRES_PASSWORD: mobicoop
 | 
					 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
      - 5432:5432
 | 
					      - 6379:6379
 | 
				
			||||||
    networks:
 | 
					 | 
				
			||||||
      - v3-network
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  broker:
 | 
					 | 
				
			||||||
    container_name: v3-broker
 | 
					 | 
				
			||||||
    image: ${BROKER_IMAGE}
 | 
					 | 
				
			||||||
    ports:
 | 
					 | 
				
			||||||
      - 5672:5672
 | 
					 | 
				
			||||||
    networks:
 | 
					    networks:
 | 
				
			||||||
      - v3-network
 | 
					      - v3-network
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,12 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "name": "@mobicoop/configuration",
 | 
					  "name": "@mobicoop/configuration",
 | 
				
			||||||
  "version": "1.0.0",
 | 
					  "version": "2.0.0",
 | 
				
			||||||
  "lockfileVersion": 3,
 | 
					  "lockfileVersion": 3,
 | 
				
			||||||
  "requires": true,
 | 
					  "requires": true,
 | 
				
			||||||
  "packages": {
 | 
					  "packages": {
 | 
				
			||||||
    "": {
 | 
					    "": {
 | 
				
			||||||
      "name": "@mobicoop/configuration",
 | 
					      "name": "@mobicoop/configuration",
 | 
				
			||||||
      "version": "1.0.0",
 | 
					      "version": "2.0.0",
 | 
				
			||||||
      "license": "AGPL",
 | 
					      "license": "AGPL",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@grpc/grpc-js": "^1.9.6",
 | 
					        "@grpc/grpc-js": "^1.9.6",
 | 
				
			||||||
| 
						 | 
					@ -23,8 +23,10 @@
 | 
				
			||||||
        "@nestjs/platform-express": "^10.2.7",
 | 
					        "@nestjs/platform-express": "^10.2.7",
 | 
				
			||||||
        "@nestjs/terminus": "^10.1.1",
 | 
					        "@nestjs/terminus": "^10.1.1",
 | 
				
			||||||
        "@prisma/client": "^5.4.2",
 | 
					        "@prisma/client": "^5.4.2",
 | 
				
			||||||
 | 
					        "@songkeys/nestjs-redis": "^10.0.0",
 | 
				
			||||||
        "class-transformer": "^0.5.1",
 | 
					        "class-transformer": "^0.5.1",
 | 
				
			||||||
        "class-validator": "^0.14.0",
 | 
					        "class-validator": "^0.14.0",
 | 
				
			||||||
 | 
					        "ioredis": "^5.3.2",
 | 
				
			||||||
        "reflect-metadata": "^0.1.13",
 | 
					        "reflect-metadata": "^0.1.13",
 | 
				
			||||||
        "rimraf": "^5.0.5",
 | 
					        "rimraf": "^5.0.5",
 | 
				
			||||||
        "rxjs": "^7.8.1",
 | 
					        "rxjs": "^7.8.1",
 | 
				
			||||||
| 
						 | 
					@ -1096,6 +1098,11 @@
 | 
				
			||||||
      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
 | 
					      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@ioredis/commands": {
 | 
				
			||||||
 | 
					      "version": "1.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@isaacs/cliui": {
 | 
					    "node_modules/@isaacs/cliui": {
 | 
				
			||||||
      "version": "8.0.2",
 | 
					      "version": "8.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
 | 
				
			||||||
| 
						 | 
					@ -2490,6 +2497,27 @@
 | 
				
			||||||
        "@sinonjs/commons": "^3.0.0"
 | 
					        "@sinonjs/commons": "^3.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@songkeys/nestjs-redis": {
 | 
				
			||||||
 | 
					      "version": "10.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@songkeys/nestjs-redis/-/nestjs-redis-10.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-s56+NECuJXzcaPLYzpvA2xjL0e/1Zy55UE0q6b1UqpbQSKI06TFPFCWCMUadJigiuB26O1hxi+lmDbzahKvcLg==",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "tslib": "2.6.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=16.0.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "@nestjs/common": "^10.0.0",
 | 
				
			||||||
 | 
					        "@nestjs/core": "^10.0.0",
 | 
				
			||||||
 | 
					        "ioredis": "^5.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@songkeys/nestjs-redis/node_modules/tslib": {
 | 
				
			||||||
 | 
					      "version": "2.6.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@tsconfig/node10": {
 | 
					    "node_modules/@tsconfig/node10": {
 | 
				
			||||||
      "version": "1.0.9",
 | 
					      "version": "1.0.9",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
 | 
				
			||||||
| 
						 | 
					@ -4098,6 +4126,14 @@
 | 
				
			||||||
        "node": ">=0.8"
 | 
					        "node": ">=0.8"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/cluster-key-slot": {
 | 
				
			||||||
 | 
					      "version": "1.1.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=0.10.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/co": {
 | 
					    "node_modules/co": {
 | 
				
			||||||
      "version": "4.6.0",
 | 
					      "version": "4.6.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
 | 
				
			||||||
| 
						 | 
					@ -4581,6 +4617,14 @@
 | 
				
			||||||
        "node": ">=0.4.0"
 | 
					        "node": ">=0.4.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/denque": {
 | 
				
			||||||
 | 
					      "version": "2.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=0.10"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/depd": {
 | 
					    "node_modules/depd": {
 | 
				
			||||||
      "version": "2.0.0",
 | 
					      "version": "2.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
 | 
				
			||||||
| 
						 | 
					@ -5559,20 +5603,6 @@
 | 
				
			||||||
      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
 | 
					      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/fsevents": {
 | 
					 | 
				
			||||||
      "version": "2.3.3",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
 | 
					 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "hasInstallScript": true,
 | 
					 | 
				
			||||||
      "optional": true,
 | 
					 | 
				
			||||||
      "os": [
 | 
					 | 
				
			||||||
        "darwin"
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/function-bind": {
 | 
					    "node_modules/function-bind": {
 | 
				
			||||||
      "version": "1.1.2",
 | 
					      "version": "1.1.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
 | 
				
			||||||
| 
						 | 
					@ -5952,6 +5982,29 @@
 | 
				
			||||||
        "node": ">= 0.10"
 | 
					        "node": ">= 0.10"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/ioredis": {
 | 
				
			||||||
 | 
					      "version": "5.3.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@ioredis/commands": "^1.1.1",
 | 
				
			||||||
 | 
					        "cluster-key-slot": "^1.1.0",
 | 
				
			||||||
 | 
					        "debug": "^4.3.4",
 | 
				
			||||||
 | 
					        "denque": "^2.1.0",
 | 
				
			||||||
 | 
					        "lodash.defaults": "^4.2.0",
 | 
				
			||||||
 | 
					        "lodash.isarguments": "^3.1.0",
 | 
				
			||||||
 | 
					        "redis-errors": "^1.2.0",
 | 
				
			||||||
 | 
					        "redis-parser": "^3.0.0",
 | 
				
			||||||
 | 
					        "standard-as-callback": "^2.1.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=12.22.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "funding": {
 | 
				
			||||||
 | 
					        "type": "opencollective",
 | 
				
			||||||
 | 
					        "url": "https://opencollective.com/ioredis"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/ipaddr.js": {
 | 
					    "node_modules/ipaddr.js": {
 | 
				
			||||||
      "version": "1.9.1",
 | 
					      "version": "1.9.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
 | 
				
			||||||
| 
						 | 
					@ -6979,6 +7032,16 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
 | 
					      "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/lodash.defaults": {
 | 
				
			||||||
 | 
					      "version": "4.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/lodash.isarguments": {
 | 
				
			||||||
 | 
					      "version": "3.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/lodash.memoize": {
 | 
					    "node_modules/lodash.memoize": {
 | 
				
			||||||
      "version": "4.1.2",
 | 
					      "version": "4.1.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
 | 
				
			||||||
| 
						 | 
					@ -8022,6 +8085,25 @@
 | 
				
			||||||
        "node": ">= 0.10"
 | 
					        "node": ">= 0.10"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/redis-errors": {
 | 
				
			||||||
 | 
					      "version": "1.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=4"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/redis-parser": {
 | 
				
			||||||
 | 
					      "version": "3.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "redis-errors": "^1.0.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=4"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/reflect-metadata": {
 | 
					    "node_modules/reflect-metadata": {
 | 
				
			||||||
      "version": "0.1.13",
 | 
					      "version": "0.1.13",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
 | 
				
			||||||
| 
						 | 
					@ -8546,6 +8628,11 @@
 | 
				
			||||||
        "node": ">=8"
 | 
					        "node": ">=8"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/standard-as-callback": {
 | 
				
			||||||
 | 
					      "version": "2.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/statuses": {
 | 
					    "node_modules/statuses": {
 | 
				
			||||||
      "version": "2.0.1",
 | 
					      "version": "2.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										19
									
								
								package.json
								
								
								
								
							
							
						
						
									
										19
									
								
								package.json
								
								
								
								
							| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "name": "@mobicoop/configuration",
 | 
					  "name": "@mobicoop/configuration",
 | 
				
			||||||
  "version": "1.0.0",
 | 
					  "version": "2.0.0",
 | 
				
			||||||
  "description": "Mobicoop V3 Configuration Service",
 | 
					  "description": "Mobicoop V3 Configuration Service",
 | 
				
			||||||
  "author": "sbriat",
 | 
					  "author": "sbriat",
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
| 
						 | 
					@ -17,18 +17,11 @@
 | 
				
			||||||
    "lint:check": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix-dry-run --ignore-path .gitignore",
 | 
					    "lint:check": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix-dry-run --ignore-path .gitignore",
 | 
				
			||||||
    "pretty:check": "./node_modules/.bin/prettier --check .",
 | 
					    "pretty:check": "./node_modules/.bin/prettier --check .",
 | 
				
			||||||
    "pretty": "./node_modules/.bin/prettier --write .",
 | 
					    "pretty": "./node_modules/.bin/prettier --write .",
 | 
				
			||||||
    "test": "npm run migrate:test && dotenv -e .env.test jest",
 | 
					    "test": "dotenv -e .env.test jest",
 | 
				
			||||||
    "test:unit": "jest --testPathPattern 'tests/unit/' --verbose",
 | 
					    "test:unit": "jest --testPathPattern 'tests/unit/' --verbose",
 | 
				
			||||||
    "test:unit:ci": "jest --testPathPattern 'tests/unit/' --coverage",
 | 
					    "test:unit:ci": "jest --testPathPattern 'tests/unit/' --coverage",
 | 
				
			||||||
    "test:integration": "npm run migrate:test && dotenv -e .env.test -- jest --testPathPattern 'tests/integration/' --verbose",
 | 
					 | 
				
			||||||
    "test:integration:ci": "npm run migrate:test:ci && dotenv -e ci/.env.ci -- jest --testPathPattern 'tests/integration/'",
 | 
					 | 
				
			||||||
    "test:cov": "jest --testPathPattern 'tests/unit/' --coverage",
 | 
					    "test:cov": "jest --testPathPattern 'tests/unit/' --coverage",
 | 
				
			||||||
    "test:e2e": "jest --config ./test/jest-e2e.json",
 | 
					    "test:e2e": "jest --config ./test/jest-e2e.json"
 | 
				
			||||||
    "generate": "docker exec v3-configuration-api sh -c 'npx prisma generate'",
 | 
					 | 
				
			||||||
    "migrate": "docker exec v3-configuration-api sh -c 'npx prisma migrate dev'",
 | 
					 | 
				
			||||||
    "migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy",
 | 
					 | 
				
			||||||
    "migrate:test:ci": "dotenv -e ci/.env.ci -- npx prisma migrate deploy",
 | 
					 | 
				
			||||||
    "migrate:deploy": "npx prisma migrate deploy"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@grpc/grpc-js": "^1.9.6",
 | 
					    "@grpc/grpc-js": "^1.9.6",
 | 
				
			||||||
| 
						 | 
					@ -44,9 +37,10 @@
 | 
				
			||||||
    "@nestjs/microservices": "^10.2.7",
 | 
					    "@nestjs/microservices": "^10.2.7",
 | 
				
			||||||
    "@nestjs/platform-express": "^10.2.7",
 | 
					    "@nestjs/platform-express": "^10.2.7",
 | 
				
			||||||
    "@nestjs/terminus": "^10.1.1",
 | 
					    "@nestjs/terminus": "^10.1.1",
 | 
				
			||||||
    "@prisma/client": "^5.4.2",
 | 
					    "@songkeys/nestjs-redis": "^10.0.0",
 | 
				
			||||||
    "class-transformer": "^0.5.1",
 | 
					    "class-transformer": "^0.5.1",
 | 
				
			||||||
    "class-validator": "^0.14.0",
 | 
					    "class-validator": "^0.14.0",
 | 
				
			||||||
 | 
					    "ioredis": "^5.3.2",
 | 
				
			||||||
    "reflect-metadata": "^0.1.13",
 | 
					    "reflect-metadata": "^0.1.13",
 | 
				
			||||||
    "rimraf": "^5.0.5",
 | 
					    "rimraf": "^5.0.5",
 | 
				
			||||||
    "rxjs": "^7.8.1",
 | 
					    "rxjs": "^7.8.1",
 | 
				
			||||||
| 
						 | 
					@ -69,7 +63,6 @@
 | 
				
			||||||
    "eslint-plugin-prettier": "^5.0.1",
 | 
					    "eslint-plugin-prettier": "^5.0.1",
 | 
				
			||||||
    "jest": "29.7.0",
 | 
					    "jest": "29.7.0",
 | 
				
			||||||
    "prettier": "^3.0.3",
 | 
					    "prettier": "^3.0.3",
 | 
				
			||||||
    "prisma": "^5.4.2",
 | 
					 | 
				
			||||||
    "source-map-support": "^0.5.21",
 | 
					    "source-map-support": "^0.5.21",
 | 
				
			||||||
    "supertest": "^6.3.3",
 | 
					    "supertest": "^6.3.3",
 | 
				
			||||||
    "ts-jest": "29.1.1",
 | 
					    "ts-jest": "29.1.1",
 | 
				
			||||||
| 
						 | 
					@ -91,6 +84,7 @@
 | 
				
			||||||
      ".di-tokens.ts",
 | 
					      ".di-tokens.ts",
 | 
				
			||||||
      ".response.ts",
 | 
					      ".response.ts",
 | 
				
			||||||
      ".port.ts",
 | 
					      ".port.ts",
 | 
				
			||||||
 | 
					      ".config.ts",
 | 
				
			||||||
      "prisma.service.ts",
 | 
					      "prisma.service.ts",
 | 
				
			||||||
      "main.ts"
 | 
					      "main.ts"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
| 
						 | 
					@ -109,6 +103,7 @@
 | 
				
			||||||
      ".di-tokens.ts",
 | 
					      ".di-tokens.ts",
 | 
				
			||||||
      ".response.ts",
 | 
					      ".response.ts",
 | 
				
			||||||
      ".port.ts",
 | 
					      ".port.ts",
 | 
				
			||||||
 | 
					      ".config.ts",
 | 
				
			||||||
      "prisma.service.ts",
 | 
					      "prisma.service.ts",
 | 
				
			||||||
      "main.ts"
 | 
					      "main.ts"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
-- CreateTable
 | 
					 | 
				
			||||||
CREATE TABLE "configuration" (
 | 
					 | 
				
			||||||
    "uuid" UUID NOT NULL,
 | 
					 | 
				
			||||||
    "domain" TEXT NOT NULL,
 | 
					 | 
				
			||||||
    "key" TEXT NOT NULL,
 | 
					 | 
				
			||||||
    "value" TEXT NOT NULL,
 | 
					 | 
				
			||||||
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
					 | 
				
			||||||
    "updatedAt" TIMESTAMP(3) NOT NULL,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    CONSTRAINT "configuration_pkey" PRIMARY KEY ("uuid")
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- CreateIndex
 | 
					 | 
				
			||||||
CREATE UNIQUE INDEX "configuration_domain_key_key" ON "configuration"("domain", "key");
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,3 +0,0 @@
 | 
				
			||||||
# Please do not edit this file manually
 | 
					 | 
				
			||||||
# It should be added in your version-control system (i.e. Git)
 | 
					 | 
				
			||||||
provider = "postgresql"
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,24 +0,0 @@
 | 
				
			||||||
// This is your Prisma schema file,
 | 
					 | 
				
			||||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
generator client {
 | 
					 | 
				
			||||||
  provider      = "prisma-client-js"
 | 
					 | 
				
			||||||
  binaryTargets = ["linux-musl", "debian-openssl-3.0.x"]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
datasource db {
 | 
					 | 
				
			||||||
  provider = "postgresql"
 | 
					 | 
				
			||||||
  url      = env("DATABASE_URL")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
model Configuration {
 | 
					 | 
				
			||||||
  uuid      String   @id @default(uuid()) @db.Uuid
 | 
					 | 
				
			||||||
  domain    String
 | 
					 | 
				
			||||||
  key       String
 | 
					 | 
				
			||||||
  value     String
 | 
					 | 
				
			||||||
  createdAt DateTime @default(now())
 | 
					 | 
				
			||||||
  updatedAt DateTime @updatedAt
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @@unique([domain, key])
 | 
					 | 
				
			||||||
  @@map("configuration")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -5,12 +5,6 @@ export const SERVICE_NAME = 'configuration';
 | 
				
			||||||
export const GRPC_PACKAGE_NAME = 'configuration';
 | 
					export const GRPC_PACKAGE_NAME = 'configuration';
 | 
				
			||||||
export const GRPC_SERVICE_NAME = 'ConfigurationService';
 | 
					export const GRPC_SERVICE_NAME = 'ConfigurationService';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// messaging
 | 
					 | 
				
			||||||
export const CONFIGURATION_SET_ROUTING_KEY = 'configuration.set';
 | 
					 | 
				
			||||||
export const CONFIGURATION_DELETED_ROUTING_KEY = 'configuration.deleted';
 | 
					 | 
				
			||||||
export const CONFIGURATION_PROPAGATED_ROUTING_KEY = 'configuration.propagated';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// health
 | 
					// health
 | 
				
			||||||
export const GRPC_HEALTH_PACKAGE_NAME = 'health';
 | 
					export const GRPC_HEALTH_PACKAGE_NAME = 'health';
 | 
				
			||||||
export const HEALTH_CONFIGURATION_REPOSITORY = 'ConfigurationRepository';
 | 
					 | 
				
			||||||
export const HEALTH_CRITICAL_LOGGING_KEY = 'logging.configuration.health.crit';
 | 
					export const HEALTH_CRITICAL_LOGGING_KEY = 'logging.configuration.health.crit';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,44 +1,69 @@
 | 
				
			||||||
import {
 | 
					import { HealthModule, HealthModuleOptions } from '@mobicoop/health-module';
 | 
				
			||||||
  HealthModule,
 | 
					 | 
				
			||||||
  HealthModuleOptions,
 | 
					 | 
				
			||||||
  HealthRepositoryPort,
 | 
					 | 
				
			||||||
} from '@mobicoop/health-module';
 | 
					 | 
				
			||||||
import { MessagerModule } from '@modules/messager/messager.module';
 | 
					import { MessagerModule } from '@modules/messager/messager.module';
 | 
				
			||||||
import { Module } from '@nestjs/common';
 | 
					import { Module } from '@nestjs/common';
 | 
				
			||||||
import { ConfigModule } from '@nestjs/config';
 | 
					import { ConfigModule, ConfigService } from '@nestjs/config';
 | 
				
			||||||
import {
 | 
					import { HEALTH_CRITICAL_LOGGING_KEY, SERVICE_NAME } from './app.constants';
 | 
				
			||||||
  HEALTH_CONFIGURATION_REPOSITORY,
 | 
					 | 
				
			||||||
  HEALTH_CRITICAL_LOGGING_KEY,
 | 
					 | 
				
			||||||
  SERVICE_NAME,
 | 
					 | 
				
			||||||
} from './app.constants';
 | 
					 | 
				
			||||||
import { MessagePublisherPort } from '@mobicoop/ddd-library';
 | 
					import { MessagePublisherPort } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { MESSAGE_PUBLISHER } from '@modules/messager/messager.di-tokens';
 | 
					import { MESSAGE_PUBLISHER } from '@modules/messager/messager.di-tokens';
 | 
				
			||||||
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
 | 
					 | 
				
			||||||
import { ConfigurationModule } from '@modules/configuration/configuration.module';
 | 
					import { ConfigurationModule } from '@modules/configuration/configuration.module';
 | 
				
			||||||
import { EventEmitterModule } from '@nestjs/event-emitter';
 | 
					import { EventEmitterModule } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import brokerConfig from './config/broker.config';
 | 
				
			||||||
 | 
					import carpoolConfig from './config/carpool.config';
 | 
				
			||||||
 | 
					import paginationConfig from './config/pagination.config';
 | 
				
			||||||
 | 
					import serviceConfig from './config/service.config';
 | 
				
			||||||
 | 
					import { RedisModule, RedisModuleOptions } from '@songkeys/nestjs-redis';
 | 
				
			||||||
 | 
					import redisConfig from './config/redis.config';
 | 
				
			||||||
 | 
					import { Transport } from '@nestjs/microservices';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Module({
 | 
					@Module({
 | 
				
			||||||
  imports: [
 | 
					  imports: [
 | 
				
			||||||
    ConfigModule.forRoot({ isGlobal: true }),
 | 
					    ConfigModule.forRoot({
 | 
				
			||||||
 | 
					      isGlobal: true,
 | 
				
			||||||
 | 
					      load: [
 | 
				
			||||||
 | 
					        brokerConfig,
 | 
				
			||||||
 | 
					        carpoolConfig,
 | 
				
			||||||
 | 
					        paginationConfig,
 | 
				
			||||||
 | 
					        redisConfig,
 | 
				
			||||||
 | 
					        serviceConfig,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
    EventEmitterModule.forRoot(),
 | 
					    EventEmitterModule.forRoot(),
 | 
				
			||||||
    HealthModule.forRootAsync({
 | 
					    HealthModule.forRootAsync({
 | 
				
			||||||
      imports: [ConfigurationModule, MessagerModule],
 | 
					      imports: [MessagerModule, ConfigModule],
 | 
				
			||||||
      inject: [CONFIGURATION_REPOSITORY, MESSAGE_PUBLISHER],
 | 
					      inject: [MESSAGE_PUBLISHER, ConfigService],
 | 
				
			||||||
      useFactory: async (
 | 
					      useFactory: async (
 | 
				
			||||||
        configurationRepository: HealthRepositoryPort,
 | 
					 | 
				
			||||||
        messagePublisher: MessagePublisherPort,
 | 
					        messagePublisher: MessagePublisherPort,
 | 
				
			||||||
 | 
					        configService: ConfigService,
 | 
				
			||||||
      ): Promise<HealthModuleOptions> => ({
 | 
					      ): Promise<HealthModuleOptions> => ({
 | 
				
			||||||
        serviceName: SERVICE_NAME,
 | 
					        serviceName: SERVICE_NAME,
 | 
				
			||||||
        criticalLoggingKey: HEALTH_CRITICAL_LOGGING_KEY,
 | 
					        criticalLoggingKey: HEALTH_CRITICAL_LOGGING_KEY,
 | 
				
			||||||
        checkRepositories: [
 | 
					        messagePublisher,
 | 
				
			||||||
 | 
					        checkMicroservices: [
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            name: HEALTH_CONFIGURATION_REPOSITORY,
 | 
					            host: configService.get<string>('redis.host') as string,
 | 
				
			||||||
            repository: configurationRepository,
 | 
					            port: configService.get<string>('redis.port') as string,
 | 
				
			||||||
 | 
					            password: configService.get<string>('redis.password'),
 | 
				
			||||||
 | 
					            name: 'Redis',
 | 
				
			||||||
 | 
					            transport: Transport.REDIS,
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        messagePublisher,
 | 
					 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
 | 
					    RedisModule.forRootAsync({
 | 
				
			||||||
 | 
					      imports: [ConfigModule],
 | 
				
			||||||
 | 
					      inject: [ConfigService],
 | 
				
			||||||
 | 
					      useFactory: async (
 | 
				
			||||||
 | 
					        configService: ConfigService,
 | 
				
			||||||
 | 
					      ): Promise<RedisModuleOptions> => {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          config: {
 | 
				
			||||||
 | 
					            host: configService.get<string>('redis.host') as string,
 | 
				
			||||||
 | 
					            port: configService.get<number>('redis.port') as number,
 | 
				
			||||||
 | 
					            password: configService.get<string>('redis.password'),
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
    ConfigurationModule,
 | 
					    ConfigurationModule,
 | 
				
			||||||
    MessagerModule,
 | 
					    MessagerModule,
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					import { registerAs } from '@nestjs/config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default registerAs('broker', () => ({
 | 
				
			||||||
 | 
					  uri: process.env.MESSAGE_BROKER_URI ?? 'amqp://v3-broker:5672',
 | 
				
			||||||
 | 
					  exchange: process.env.MESSAGE_BROKER_EXCHANGE ?? 'mobicoop',
 | 
				
			||||||
 | 
					  durability: process.env.MESSAGE_BROKER_EXCHANGE_DURABILITY
 | 
				
			||||||
 | 
					    ? process.env.MESSAGE_BROKER_EXCHANGE_DURABILITY === 'false'
 | 
				
			||||||
 | 
					      ? false
 | 
				
			||||||
 | 
					      : true
 | 
				
			||||||
 | 
					    : true,
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,19 @@
 | 
				
			||||||
 | 
					import { registerAs } from '@nestjs/config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default registerAs('carpool', () => ({
 | 
				
			||||||
 | 
					  departureTimeMargin: process.env.DEPARTURE_TIME_MARGIN
 | 
				
			||||||
 | 
					    ? parseInt(process.env.DEPARTURE_TIME_MARGIN, 10)
 | 
				
			||||||
 | 
					    : 900,
 | 
				
			||||||
 | 
					  role: process.env.ROLE ?? 'passenger',
 | 
				
			||||||
 | 
					  seatsProposed: process.env.SEATS_PROPOSED
 | 
				
			||||||
 | 
					    ? parseInt(process.env.SEATS_PROPOSED, 10)
 | 
				
			||||||
 | 
					    : 3,
 | 
				
			||||||
 | 
					  seatsRequested: process.env.SEATS_REQUESTED
 | 
				
			||||||
 | 
					    ? parseInt(process.env.SEATS_REQUESTED, 10)
 | 
				
			||||||
 | 
					    : 1,
 | 
				
			||||||
 | 
					  strictFrequency: process.env.STRICT_FREQUENCY
 | 
				
			||||||
 | 
					    ? process.env.STRICT_FREQUENCY === 'false'
 | 
				
			||||||
 | 
					      ? false
 | 
				
			||||||
 | 
					      : true
 | 
				
			||||||
 | 
					    : false,
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					import { registerAs } from '@nestjs/config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default registerAs('pagination', () => ({
 | 
				
			||||||
 | 
					  perPage: process.env.PER_PAGE ? parseInt(process.env.PER_PAGE, 10) : 10,
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					import { registerAs } from '@nestjs/config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default registerAs('redis', () => ({
 | 
				
			||||||
 | 
					  host: process.env.REDIS_HOST ?? 'v3-redis',
 | 
				
			||||||
 | 
					  port: process.env.PORT ? parseInt(process.env.PORT, 10) : 6379,
 | 
				
			||||||
 | 
					  password: process.env.REDIS_PASSWORD ?? 'redis',
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					import { registerAs } from '@nestjs/config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default registerAs('service', () => ({
 | 
				
			||||||
 | 
					  url: process.env.SERVICE_URL ?? '0.0.0.0',
 | 
				
			||||||
 | 
					  port: process.env.SERVICE_PORT
 | 
				
			||||||
 | 
					    ? parseInt(process.env.SERVICE_PORT, 10)
 | 
				
			||||||
 | 
					    : 5003,
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1 @@
 | 
				
			||||||
export const CONFIGURATION_MESSAGE_PUBLISHER = Symbol(
 | 
					 | 
				
			||||||
  'CONFIGURATION_MESSAGE_PUBLISHER',
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
export const CONFIGURATION_REPOSITORY = Symbol('CONFIGURATION_REPOSITORY');
 | 
					export const CONFIGURATION_REPOSITORY = Symbol('CONFIGURATION_REPOSITORY');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,18 +1,13 @@
 | 
				
			||||||
import { Mapper } from '@mobicoop/ddd-library';
 | 
					import { Mapper } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { Injectable } from '@nestjs/common';
 | 
					import { Injectable } from '@nestjs/common';
 | 
				
			||||||
import { ConfigurationEntity } from './core/domain/configuration.entity';
 | 
					import { ConfigurationEntity } from './core/domain/configuration.entity';
 | 
				
			||||||
 | 
					import { ConfigurationResponseDto } from './interface/dtos/configuration.response.dto';
 | 
				
			||||||
 | 
					import { ConfigurationDomain } from './core/domain/configuration.types';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  ConfigurationReadModel,
 | 
					  ConfigurationReadModel,
 | 
				
			||||||
  ConfigurationWriteModel,
 | 
					  ConfigurationWriteModel,
 | 
				
			||||||
} from './infrastructure/configuration.repository';
 | 
					} from './infrastructure/configuration.repository';
 | 
				
			||||||
import { ConfigurationResponseDto } from './interface/dtos/configuration.response.dto';
 | 
					import { v4 } from 'uuid';
 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 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()
 | 
					@Injectable()
 | 
				
			||||||
export class ConfigurationMapper
 | 
					export class ConfigurationMapper
 | 
				
			||||||
| 
						 | 
					@ -27,9 +22,7 @@ export class ConfigurationMapper
 | 
				
			||||||
  toPersistence = (entity: ConfigurationEntity): ConfigurationWriteModel => {
 | 
					  toPersistence = (entity: ConfigurationEntity): ConfigurationWriteModel => {
 | 
				
			||||||
    const copy = entity.getProps();
 | 
					    const copy = entity.getProps();
 | 
				
			||||||
    const record: ConfigurationWriteModel = {
 | 
					    const record: ConfigurationWriteModel = {
 | 
				
			||||||
      uuid: entity.id,
 | 
					      key: `${copy.identifier.domain}:${copy.identifier.key}`,
 | 
				
			||||||
      domain: copy.domain,
 | 
					 | 
				
			||||||
      key: copy.key,
 | 
					 | 
				
			||||||
      value: copy.value,
 | 
					      value: copy.value,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    return record;
 | 
					    return record;
 | 
				
			||||||
| 
						 | 
					@ -37,12 +30,14 @@ export class ConfigurationMapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  toDomain = (record: ConfigurationReadModel): ConfigurationEntity => {
 | 
					  toDomain = (record: ConfigurationReadModel): ConfigurationEntity => {
 | 
				
			||||||
    const entity = new ConfigurationEntity({
 | 
					    const entity = new ConfigurationEntity({
 | 
				
			||||||
      id: record.uuid,
 | 
					      id: v4(),
 | 
				
			||||||
      createdAt: new Date(record.createdAt),
 | 
					      createdAt: new Date(),
 | 
				
			||||||
      updatedAt: new Date(record.updatedAt),
 | 
					      updatedAt: new Date(),
 | 
				
			||||||
      props: {
 | 
					      props: {
 | 
				
			||||||
        domain: record.domain,
 | 
					        identifier: {
 | 
				
			||||||
        key: record.key,
 | 
					          domain: record.key.split(':')[0] as ConfigurationDomain,
 | 
				
			||||||
 | 
					          key: record.key.split(':')[1],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        value: record.value,
 | 
					        value: record.value,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
| 
						 | 
					@ -52,8 +47,8 @@ export class ConfigurationMapper
 | 
				
			||||||
  toResponse = (entity: ConfigurationEntity): ConfigurationResponseDto => {
 | 
					  toResponse = (entity: ConfigurationEntity): ConfigurationResponseDto => {
 | 
				
			||||||
    const props = entity.getProps();
 | 
					    const props = entity.getProps();
 | 
				
			||||||
    const response = new ConfigurationResponseDto(entity);
 | 
					    const response = new ConfigurationResponseDto(entity);
 | 
				
			||||||
    response.domain = props.domain;
 | 
					    response.domain = props.identifier.domain;
 | 
				
			||||||
    response.key = props.key;
 | 
					    response.key = props.identifier.key;
 | 
				
			||||||
    response.value = props.value;
 | 
					    response.value = props.value;
 | 
				
			||||||
    return response;
 | 
					    return response;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,45 +2,26 @@ import { Module, Provider } from '@nestjs/common';
 | 
				
			||||||
import { CqrsModule } from '@nestjs/cqrs';
 | 
					import { CqrsModule } from '@nestjs/cqrs';
 | 
				
			||||||
import { GetConfigurationGrpcController } from './interface/grpc-controllers/get-configuration.grpc.controller';
 | 
					import { GetConfigurationGrpcController } from './interface/grpc-controllers/get-configuration.grpc.controller';
 | 
				
			||||||
import { SetConfigurationGrpcController } from './interface/grpc-controllers/set-configuration.grpc.controller';
 | 
					import { SetConfigurationGrpcController } from './interface/grpc-controllers/set-configuration.grpc.controller';
 | 
				
			||||||
import { DeleteConfigurationGrpcController } from './interface/grpc-controllers/delete-configuration.grpc.controller';
 | 
					 | 
				
			||||||
import { PropagateConfigurationsGrpcController } from './interface/grpc-controllers/propagate-configurations.grpc.controller';
 | 
					 | 
				
			||||||
import { PublishMessageWhenConfigurationIsSetDomainEventHandler } from './core/application/event-handlers/publish-message-when-configuration-is-set.domain-event-handler';
 | 
					 | 
				
			||||||
import { PublishMessageWhenConfigurationIsDeletedDomainEventHandler } from './core/application/event-handlers/publish-message-when-configuration-is-deleted.domain-event-handler';
 | 
					 | 
				
			||||||
import { SetConfigurationService } from './core/application/commands/set-configuration/set-configuration.service';
 | 
					import { SetConfigurationService } from './core/application/commands/set-configuration/set-configuration.service';
 | 
				
			||||||
import { DeleteConfigurationService } from './core/application/commands/delete-configuration/delete-configuration.service';
 | 
					 | 
				
			||||||
import { GetConfigurationQueryHandler } from './core/application/queries/get-configuration/get-configuration.query-handler';
 | 
					import { GetConfigurationQueryHandler } from './core/application/queries/get-configuration/get-configuration.query-handler';
 | 
				
			||||||
import { ConfigurationMapper } from './configuration.mapper';
 | 
					import { ConfigurationMapper } from './configuration.mapper';
 | 
				
			||||||
import {
 | 
					import { CONFIGURATION_REPOSITORY } from './configuration.di-tokens';
 | 
				
			||||||
  CONFIGURATION_MESSAGE_PUBLISHER,
 | 
					import { PopulateService } from './core/application/services/populate.service';
 | 
				
			||||||
  CONFIGURATION_REPOSITORY,
 | 
					 | 
				
			||||||
} from './configuration.di-tokens';
 | 
					 | 
				
			||||||
import { ConfigurationRepository } from './infrastructure/configuration.repository';
 | 
					import { ConfigurationRepository } from './infrastructure/configuration.repository';
 | 
				
			||||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
					 | 
				
			||||||
import { PrismaService } from './infrastructure/prisma.service';
 | 
					 | 
				
			||||||
import { PropagateConfigurationsService } from './core/application/commands/propagate-configurations/propagate-configurations.service';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const grpcControllers = [
 | 
					const grpcControllers = [
 | 
				
			||||||
  GetConfigurationGrpcController,
 | 
					  GetConfigurationGrpcController,
 | 
				
			||||||
  SetConfigurationGrpcController,
 | 
					  SetConfigurationGrpcController,
 | 
				
			||||||
  DeleteConfigurationGrpcController,
 | 
					 | 
				
			||||||
  PropagateConfigurationsGrpcController,
 | 
					 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const eventHandlers: Provider[] = [
 | 
					const commandHandlers: Provider[] = [SetConfigurationService];
 | 
				
			||||||
  PublishMessageWhenConfigurationIsSetDomainEventHandler,
 | 
					 | 
				
			||||||
  PublishMessageWhenConfigurationIsDeletedDomainEventHandler,
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const commandHandlers: Provider[] = [
 | 
					 | 
				
			||||||
  SetConfigurationService,
 | 
					 | 
				
			||||||
  DeleteConfigurationService,
 | 
					 | 
				
			||||||
  PropagateConfigurationsService,
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const queryHandlers: Provider[] = [GetConfigurationQueryHandler];
 | 
					const queryHandlers: Provider[] = [GetConfigurationQueryHandler];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mappers: Provider[] = [ConfigurationMapper];
 | 
					const mappers: Provider[] = [ConfigurationMapper];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const providers: Provider[] = [PopulateService];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const repositories: Provider[] = [
 | 
					const repositories: Provider[] = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    provide: CONFIGURATION_REPOSITORY,
 | 
					    provide: CONFIGURATION_REPOSITORY,
 | 
				
			||||||
| 
						 | 
					@ -48,26 +29,16 @@ const repositories: Provider[] = [
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messagePublishers: Provider[] = [
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    provide: CONFIGURATION_MESSAGE_PUBLISHER,
 | 
					 | 
				
			||||||
    useExisting: MessageBrokerPublisher,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
const orms: Provider[] = [PrismaService];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Module({
 | 
					@Module({
 | 
				
			||||||
  imports: [CqrsModule],
 | 
					  imports: [CqrsModule],
 | 
				
			||||||
  controllers: [...grpcControllers],
 | 
					  controllers: [...grpcControllers],
 | 
				
			||||||
  providers: [
 | 
					  providers: [
 | 
				
			||||||
    ...eventHandlers,
 | 
					 | 
				
			||||||
    ...commandHandlers,
 | 
					    ...commandHandlers,
 | 
				
			||||||
    ...queryHandlers,
 | 
					    ...queryHandlers,
 | 
				
			||||||
    ...mappers,
 | 
					    ...mappers,
 | 
				
			||||||
 | 
					    ...providers,
 | 
				
			||||||
    ...repositories,
 | 
					    ...repositories,
 | 
				
			||||||
    ...messagePublishers,
 | 
					 | 
				
			||||||
    ...orms,
 | 
					 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  exports: [PrismaService, ConfigurationMapper, CONFIGURATION_REPOSITORY],
 | 
					  exports: [ConfigurationMapper, CONFIGURATION_REPOSITORY],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class ConfigurationModule {}
 | 
					export class ConfigurationModule {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +0,0 @@
 | 
				
			||||||
import { Command, CommandProps } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class DeleteConfigurationCommand extends Command {
 | 
					 | 
				
			||||||
  readonly domain: string;
 | 
					 | 
				
			||||||
  readonly key: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(props: CommandProps<DeleteConfigurationCommand>) {
 | 
					 | 
				
			||||||
    super(props);
 | 
					 | 
				
			||||||
    this.domain = props.domain;
 | 
					 | 
				
			||||||
    this.key = props.key;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,26 +0,0 @@
 | 
				
			||||||
import { Inject } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import { DeleteConfigurationCommand } from './delete-configuration.command';
 | 
					 | 
				
			||||||
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
 | 
					 | 
				
			||||||
import { ConfigurationRepositoryPort } from '../../ports/configuration.repository.port';
 | 
					 | 
				
			||||||
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@CommandHandler(DeleteConfigurationCommand)
 | 
					 | 
				
			||||||
export class DeleteConfigurationService implements ICommandHandler {
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    @Inject(CONFIGURATION_REPOSITORY)
 | 
					 | 
				
			||||||
    private readonly configurationRepository: ConfigurationRepositoryPort,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async execute(command: DeleteConfigurationCommand): Promise<boolean> {
 | 
					 | 
				
			||||||
    const configuration: ConfigurationEntity =
 | 
					 | 
				
			||||||
      await this.configurationRepository.findOne({
 | 
					 | 
				
			||||||
        domain: command.domain,
 | 
					 | 
				
			||||||
        key: command.key,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    configuration.delete();
 | 
					 | 
				
			||||||
    const isDeleted: boolean =
 | 
					 | 
				
			||||||
      await this.configurationRepository.delete(configuration);
 | 
					 | 
				
			||||||
    return isDeleted;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,7 +0,0 @@
 | 
				
			||||||
import { Command, CommandProps } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class PropagateConfigurationsCommand extends Command {
 | 
					 | 
				
			||||||
  constructor(props: CommandProps<PropagateConfigurationsCommand>) {
 | 
					 | 
				
			||||||
    super(props);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,36 +0,0 @@
 | 
				
			||||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import { Inject } from '@nestjs/common';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  CONFIGURATION_MESSAGE_PUBLISHER,
 | 
					 | 
				
			||||||
  CONFIGURATION_REPOSITORY,
 | 
					 | 
				
			||||||
} from '@modules/configuration/configuration.di-tokens';
 | 
					 | 
				
			||||||
import { ConfigurationRepositoryPort } from '../../ports/configuration.repository.port';
 | 
					 | 
				
			||||||
import { PropagateConfigurationsCommand } from './propagate-configurations.command';
 | 
					 | 
				
			||||||
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
					 | 
				
			||||||
import { MessagePublisherPort } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { CONFIGURATION_PROPAGATED_ROUTING_KEY } from '@src/app.constants';
 | 
					 | 
				
			||||||
import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@CommandHandler(PropagateConfigurationsCommand)
 | 
					 | 
				
			||||||
export class PropagateConfigurationsService implements ICommandHandler {
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    @Inject(CONFIGURATION_REPOSITORY)
 | 
					 | 
				
			||||||
    private readonly repository: ConfigurationRepositoryPort,
 | 
					 | 
				
			||||||
    @Inject(CONFIGURATION_MESSAGE_PUBLISHER)
 | 
					 | 
				
			||||||
    private readonly messagePublisher: MessagePublisherPort,
 | 
					 | 
				
			||||||
    private readonly configurationMapper: ConfigurationMapper,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async execute(): Promise<void> {
 | 
					 | 
				
			||||||
    const configurationItems: ConfigurationEntity[] =
 | 
					 | 
				
			||||||
      await this.repository.findAll({});
 | 
					 | 
				
			||||||
    this.messagePublisher.publish(
 | 
					 | 
				
			||||||
      CONFIGURATION_PROPAGATED_ROUTING_KEY,
 | 
					 | 
				
			||||||
      JSON.stringify(
 | 
					 | 
				
			||||||
        configurationItems.map((configuration: ConfigurationEntity) =>
 | 
					 | 
				
			||||||
          this.configurationMapper.toResponse(configuration),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,42 +1,24 @@
 | 
				
			||||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
 | 
					import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
 | 
				
			||||||
import { Inject } from '@nestjs/common';
 | 
					import { Inject } from '@nestjs/common';
 | 
				
			||||||
import { AggregateID, NotFoundException } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { SetConfigurationCommand } from './set-configuration.command';
 | 
					import { SetConfigurationCommand } from './set-configuration.command';
 | 
				
			||||||
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
 | 
					import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
 | 
				
			||||||
 | 
					import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
 | 
				
			||||||
import { ConfigurationRepositoryPort } from '../../ports/configuration.repository.port';
 | 
					import { ConfigurationRepositoryPort } from '../../ports/configuration.repository.port';
 | 
				
			||||||
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@CommandHandler(SetConfigurationCommand)
 | 
					@CommandHandler(SetConfigurationCommand)
 | 
				
			||||||
export class SetConfigurationService implements ICommandHandler {
 | 
					export class SetConfigurationService implements ICommandHandler {
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    @Inject(CONFIGURATION_REPOSITORY)
 | 
					    @Inject(CONFIGURATION_REPOSITORY)
 | 
				
			||||||
    private readonly repository: ConfigurationRepositoryPort,
 | 
					    private readonly configurationRepository: ConfigurationRepositoryPort,
 | 
				
			||||||
  ) {}
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async execute(command: SetConfigurationCommand): Promise<AggregateID> {
 | 
					  async execute(command: SetConfigurationCommand): Promise<void> {
 | 
				
			||||||
    try {
 | 
					    await this.configurationRepository.set(
 | 
				
			||||||
      const existingConfiguration: ConfigurationEntity =
 | 
					      {
 | 
				
			||||||
        await this.repository.findOne({
 | 
					        domain: command.domain as ConfigurationDomain,
 | 
				
			||||||
          domain: command.domain,
 | 
					 | 
				
			||||||
        key: command.key,
 | 
					        key: command.key,
 | 
				
			||||||
        });
 | 
					      },
 | 
				
			||||||
      existingConfiguration.update(command);
 | 
					      command.value,
 | 
				
			||||||
      await this.repository.update(
 | 
					 | 
				
			||||||
        existingConfiguration.id,
 | 
					 | 
				
			||||||
        existingConfiguration,
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
      return existingConfiguration.id;
 | 
					 | 
				
			||||||
    } catch (error: any) {
 | 
					 | 
				
			||||||
      if (error instanceof NotFoundException) {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
          const newConfiguration = ConfigurationEntity.create(command);
 | 
					 | 
				
			||||||
          await this.repository.insert(newConfiguration);
 | 
					 | 
				
			||||||
          return newConfiguration.id;
 | 
					 | 
				
			||||||
        } catch (error: any) {
 | 
					 | 
				
			||||||
          throw error;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      throw error;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,33 +0,0 @@
 | 
				
			||||||
import { Inject, Injectable } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { OnEvent } from '@nestjs/event-emitter';
 | 
					 | 
				
			||||||
import { MessagePublisherPort } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { CONFIGURATION_MESSAGE_PUBLISHER } from '@modules/configuration/configuration.di-tokens';
 | 
					 | 
				
			||||||
import { ConfigurationDeletedDomainEvent } from '../../domain/events/configuration-deleted.domain-event';
 | 
					 | 
				
			||||||
import { ConfigurationDeletedIntegrationEvent } from '../events/configuration-deleted.integration-event';
 | 
					 | 
				
			||||||
import { CONFIGURATION_DELETED_ROUTING_KEY } from '@src/app.constants';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Injectable()
 | 
					 | 
				
			||||||
export class PublishMessageWhenConfigurationIsDeletedDomainEventHandler {
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    @Inject(CONFIGURATION_MESSAGE_PUBLISHER)
 | 
					 | 
				
			||||||
    private readonly messagePublisher: MessagePublisherPort,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @OnEvent(ConfigurationDeletedDomainEvent.name, {
 | 
					 | 
				
			||||||
    async: true,
 | 
					 | 
				
			||||||
    promisify: true,
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
  async handle(event: ConfigurationDeletedDomainEvent): Promise<any> {
 | 
					 | 
				
			||||||
    const configurationDeletedIntegrationEvent =
 | 
					 | 
				
			||||||
      new ConfigurationDeletedIntegrationEvent({
 | 
					 | 
				
			||||||
        id: event.aggregateId,
 | 
					 | 
				
			||||||
        domain: event.domain,
 | 
					 | 
				
			||||||
        key: event.key,
 | 
					 | 
				
			||||||
        metadata: event.metadata,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    this.messagePublisher.publish(
 | 
					 | 
				
			||||||
      CONFIGURATION_DELETED_ROUTING_KEY,
 | 
					 | 
				
			||||||
      JSON.stringify(configurationDeletedIntegrationEvent),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,31 +0,0 @@
 | 
				
			||||||
import { Inject, Injectable } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { OnEvent } from '@nestjs/event-emitter';
 | 
					 | 
				
			||||||
import { MessagePublisherPort } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { ConfigurationSetIntegrationEvent } from '../events/configuration-set.integration-event';
 | 
					 | 
				
			||||||
import { CONFIGURATION_MESSAGE_PUBLISHER } from '@modules/configuration/configuration.di-tokens';
 | 
					 | 
				
			||||||
import { ConfigurationSetDomainEvent } from '../../domain/events/configuration-set.domain-event';
 | 
					 | 
				
			||||||
import { CONFIGURATION_SET_ROUTING_KEY } from '@src/app.constants';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Injectable()
 | 
					 | 
				
			||||||
export class PublishMessageWhenConfigurationIsSetDomainEventHandler {
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    @Inject(CONFIGURATION_MESSAGE_PUBLISHER)
 | 
					 | 
				
			||||||
    private readonly messagePublisher: MessagePublisherPort,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @OnEvent(ConfigurationSetDomainEvent.name, { async: true, promisify: true })
 | 
					 | 
				
			||||||
  async handle(event: ConfigurationSetDomainEvent): Promise<any> {
 | 
					 | 
				
			||||||
    const configurationSetIntegrationEvent =
 | 
					 | 
				
			||||||
      new ConfigurationSetIntegrationEvent({
 | 
					 | 
				
			||||||
        id: event.aggregateId,
 | 
					 | 
				
			||||||
        domain: event.domain,
 | 
					 | 
				
			||||||
        key: event.key,
 | 
					 | 
				
			||||||
        value: event.value,
 | 
					 | 
				
			||||||
        metadata: event.metadata,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    this.messagePublisher.publish(
 | 
					 | 
				
			||||||
      CONFIGURATION_SET_ROUTING_KEY,
 | 
					 | 
				
			||||||
      JSON.stringify(configurationSetIntegrationEvent),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
import { IntegrationEvent, IntegrationEventProps } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class ConfigurationDeletedIntegrationEvent extends IntegrationEvent {
 | 
					 | 
				
			||||||
  readonly domain: string;
 | 
					 | 
				
			||||||
  readonly key: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    props: IntegrationEventProps<ConfigurationDeletedIntegrationEvent>,
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    super(props);
 | 
					 | 
				
			||||||
    this.domain = props.domain;
 | 
					 | 
				
			||||||
    this.key = props.key;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
import { IntegrationEvent, IntegrationEventProps } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class ConfigurationSetIntegrationEvent extends IntegrationEvent {
 | 
					 | 
				
			||||||
  readonly domain: string;
 | 
					 | 
				
			||||||
  readonly key: string;
 | 
					 | 
				
			||||||
  readonly value: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(props: IntegrationEventProps<ConfigurationSetIntegrationEvent>) {
 | 
					 | 
				
			||||||
    super(props);
 | 
					 | 
				
			||||||
    this.domain = props.domain;
 | 
					 | 
				
			||||||
    this.key = props.key;
 | 
					 | 
				
			||||||
    this.value = props.value;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,13 @@
 | 
				
			||||||
import { RepositoryPort } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { ConfigurationEntity } from '../../domain/configuration.entity';
 | 
					import { ConfigurationEntity } from '../../domain/configuration.entity';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ConfigurationIdentifier,
 | 
				
			||||||
 | 
					  ConfigurationValue,
 | 
				
			||||||
 | 
					} from '../../domain/configuration.types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ConfigurationRepositoryPort = RepositoryPort<ConfigurationEntity>;
 | 
					export interface ConfigurationRepositoryPort {
 | 
				
			||||||
 | 
					  get(identifier: ConfigurationIdentifier): Promise<ConfigurationEntity>;
 | 
				
			||||||
 | 
					  set(
 | 
				
			||||||
 | 
					    identifier: ConfigurationIdentifier,
 | 
				
			||||||
 | 
					    value: ConfigurationValue,
 | 
				
			||||||
 | 
					  ): Promise<void>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,8 +2,9 @@ import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
 | 
				
			||||||
import { GetConfigurationQuery } from './get-configuration.query';
 | 
					import { GetConfigurationQuery } from './get-configuration.query';
 | 
				
			||||||
import { Inject } from '@nestjs/common';
 | 
					import { Inject } from '@nestjs/common';
 | 
				
			||||||
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
 | 
					import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
 | 
				
			||||||
import { ConfigurationRepositoryPort } from '../../ports/configuration.repository.port';
 | 
					 | 
				
			||||||
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
					import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
				
			||||||
 | 
					import { ConfigurationRepositoryPort } from '../../ports/configuration.repository.port';
 | 
				
			||||||
 | 
					import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@QueryHandler(GetConfigurationQuery)
 | 
					@QueryHandler(GetConfigurationQuery)
 | 
				
			||||||
export class GetConfigurationQueryHandler implements IQueryHandler {
 | 
					export class GetConfigurationQueryHandler implements IQueryHandler {
 | 
				
			||||||
| 
						 | 
					@ -12,6 +13,9 @@ export class GetConfigurationQueryHandler implements IQueryHandler {
 | 
				
			||||||
    private readonly configurationRepository: ConfigurationRepositoryPort,
 | 
					    private readonly configurationRepository: ConfigurationRepositoryPort,
 | 
				
			||||||
  ) {}
 | 
					  ) {}
 | 
				
			||||||
  async execute(query: GetConfigurationQuery): Promise<ConfigurationEntity> {
 | 
					  async execute(query: GetConfigurationQuery): Promise<ConfigurationEntity> {
 | 
				
			||||||
    return await this.configurationRepository.findOne(query);
 | 
					    return await this.configurationRepository.get({
 | 
				
			||||||
 | 
					      domain: query.domain as ConfigurationDomain,
 | 
				
			||||||
 | 
					      key: query.key,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,62 @@
 | 
				
			||||||
 | 
					import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
 | 
				
			||||||
 | 
					import { Inject, Injectable, OnApplicationBootstrap } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { ConfigService } from '@nestjs/config';
 | 
				
			||||||
 | 
					import { ConfigurationRepositoryPort } from '../ports/configuration.repository.port';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  CarpoolConfig,
 | 
				
			||||||
 | 
					  ConfigurationDomain,
 | 
				
			||||||
 | 
					  PaginationConfig,
 | 
				
			||||||
 | 
					} from '../../domain/configuration.types';
 | 
				
			||||||
 | 
					import { NotFoundException } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class PopulateService implements OnApplicationBootstrap {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(CONFIGURATION_REPOSITORY)
 | 
				
			||||||
 | 
					    private readonly configurationRepository: ConfigurationRepositoryPort,
 | 
				
			||||||
 | 
					    private readonly configService: ConfigService,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onApplicationBootstrap() {
 | 
				
			||||||
 | 
					    this._populate();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _populate = async (): Promise<void> => {
 | 
				
			||||||
 | 
					    const carpoolConfig: CarpoolConfig = this.configService.get<CarpoolConfig>(
 | 
				
			||||||
 | 
					      'carpool',
 | 
				
			||||||
 | 
					    ) as CarpoolConfig;
 | 
				
			||||||
 | 
					    const paginationConfig: PaginationConfig =
 | 
				
			||||||
 | 
					      this.configService.get<PaginationConfig>(
 | 
				
			||||||
 | 
					        'pagination',
 | 
				
			||||||
 | 
					      ) as PaginationConfig;
 | 
				
			||||||
 | 
					    await Promise.all([
 | 
				
			||||||
 | 
					      this._populateConfig(ConfigurationDomain.CARPOOL, carpoolConfig),
 | 
				
			||||||
 | 
					      this._populateConfig(ConfigurationDomain.PAGINATION, paginationConfig),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _populateConfig = async <T>(
 | 
				
			||||||
 | 
					    domain: ConfigurationDomain,
 | 
				
			||||||
 | 
					    config: T,
 | 
				
			||||||
 | 
					  ): Promise<void> => {
 | 
				
			||||||
 | 
					    let key: keyof typeof config;
 | 
				
			||||||
 | 
					    for (key in config) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        await this.configurationRepository.get({
 | 
				
			||||||
 | 
					          domain,
 | 
				
			||||||
 | 
					          key,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      } catch (error: any) {
 | 
				
			||||||
 | 
					        if (error instanceof NotFoundException) {
 | 
				
			||||||
 | 
					          this.configurationRepository.set(
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              domain,
 | 
				
			||||||
 | 
					              key,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            `${config[key]}`,
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -3,10 +3,7 @@ import { v4 } from 'uuid';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  ConfigurationProps,
 | 
					  ConfigurationProps,
 | 
				
			||||||
  CreateConfigurationProps,
 | 
					  CreateConfigurationProps,
 | 
				
			||||||
  UpdateConfigurationProps,
 | 
					 | 
				
			||||||
} from './configuration.types';
 | 
					} from './configuration.types';
 | 
				
			||||||
import { ConfigurationSetDomainEvent } from './events/configuration-set.domain-event';
 | 
					 | 
				
			||||||
import { ConfigurationDeletedDomainEvent } from './events/configuration-deleted.domain-event';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ConfigurationEntity extends AggregateRoot<ConfigurationProps> {
 | 
					export class ConfigurationEntity extends AggregateRoot<ConfigurationProps> {
 | 
				
			||||||
  protected readonly _id: AggregateID;
 | 
					  protected readonly _id: AggregateID;
 | 
				
			||||||
| 
						 | 
					@ -15,39 +12,9 @@ export class ConfigurationEntity extends AggregateRoot<ConfigurationProps> {
 | 
				
			||||||
    const id = v4();
 | 
					    const id = v4();
 | 
				
			||||||
    const props: ConfigurationProps = { ...create };
 | 
					    const props: ConfigurationProps = { ...create };
 | 
				
			||||||
    const configuration = new ConfigurationEntity({ id, props });
 | 
					    const configuration = new ConfigurationEntity({ id, props });
 | 
				
			||||||
    configuration.addEvent(
 | 
					 | 
				
			||||||
      new ConfigurationSetDomainEvent({
 | 
					 | 
				
			||||||
        aggregateId: id,
 | 
					 | 
				
			||||||
        domain: props.domain,
 | 
					 | 
				
			||||||
        key: props.key,
 | 
					 | 
				
			||||||
        value: props.value,
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    return configuration;
 | 
					    return configuration;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  update(props: UpdateConfigurationProps): void {
 | 
					 | 
				
			||||||
    this.props.value = props.value;
 | 
					 | 
				
			||||||
    this.addEvent(
 | 
					 | 
				
			||||||
      new ConfigurationSetDomainEvent({
 | 
					 | 
				
			||||||
        aggregateId: this._id,
 | 
					 | 
				
			||||||
        domain: this.props.domain,
 | 
					 | 
				
			||||||
        key: this.props.key,
 | 
					 | 
				
			||||||
        value: props.value,
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  delete(): void {
 | 
					 | 
				
			||||||
    this.addEvent(
 | 
					 | 
				
			||||||
      new ConfigurationDeletedDomainEvent({
 | 
					 | 
				
			||||||
        aggregateId: this.id,
 | 
					 | 
				
			||||||
        domain: this.props.domain,
 | 
					 | 
				
			||||||
        key: this.props.key,
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  validate(): void {
 | 
					  validate(): void {
 | 
				
			||||||
    // entity business rules validation to protect it's invariant before saving entity to a database
 | 
					    // entity business rules validation to protect it's invariant before saving entity to a database
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,23 +1,36 @@
 | 
				
			||||||
// All properties that a Configuration has
 | 
					// All properties that a Configuration has
 | 
				
			||||||
export interface ConfigurationProps {
 | 
					export interface ConfigurationProps {
 | 
				
			||||||
  domain: string;
 | 
					  identifier: ConfigurationIdentifier;
 | 
				
			||||||
  key: string;
 | 
					  value: ConfigurationValue;
 | 
				
			||||||
  value: string;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Properties that are needed for a Configuration creation
 | 
					// Properties that are needed for a Configuration creation
 | 
				
			||||||
export interface CreateConfigurationProps {
 | 
					export interface CreateConfigurationProps {
 | 
				
			||||||
  domain: string;
 | 
					  identifier: ConfigurationIdentifier;
 | 
				
			||||||
  key: string;
 | 
					  value: ConfigurationValue;
 | 
				
			||||||
  value: string;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface UpdateConfigurationProps {
 | 
					export enum ConfigurationDomain {
 | 
				
			||||||
  value: string;
 | 
					  CARPOOL = 'CARPOOL',
 | 
				
			||||||
 | 
					  PAGINATION = 'PAGINATION',
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum Domain {
 | 
					export type ConfigurationIdentifier = {
 | 
				
			||||||
  AD = 'AD',
 | 
					  domain: ConfigurationDomain;
 | 
				
			||||||
  MATCHER = 'MATCHER',
 | 
					  key: ConfigurationKey;
 | 
				
			||||||
  USER = 'USER',
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ConfigurationKey = string;
 | 
				
			||||||
 | 
					export type ConfigurationValue = string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface CarpoolConfig {
 | 
				
			||||||
 | 
					  departureTimeMargin: number;
 | 
				
			||||||
 | 
					  role: string;
 | 
				
			||||||
 | 
					  seatsProposed: number;
 | 
				
			||||||
 | 
					  seatsRequested: number;
 | 
				
			||||||
 | 
					  strictFrequency: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface PaginationConfig {
 | 
				
			||||||
 | 
					  perPage: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +0,0 @@
 | 
				
			||||||
import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class ConfigurationDeletedDomainEvent extends DomainEvent {
 | 
					 | 
				
			||||||
  readonly domain: string;
 | 
					 | 
				
			||||||
  readonly key: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(props: DomainEventProps<ConfigurationDeletedDomainEvent>) {
 | 
					 | 
				
			||||||
    super(props);
 | 
					 | 
				
			||||||
    this.domain = props.domain;
 | 
					 | 
				
			||||||
    this.key = props.key;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class ConfigurationSetDomainEvent extends DomainEvent {
 | 
					 | 
				
			||||||
  readonly domain: string;
 | 
					 | 
				
			||||||
  readonly key: string;
 | 
					 | 
				
			||||||
  readonly value: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(props: DomainEventProps<ConfigurationSetDomainEvent>) {
 | 
					 | 
				
			||||||
    super(props);
 | 
					 | 
				
			||||||
    this.domain = props.domain;
 | 
					 | 
				
			||||||
    this.key = props.key;
 | 
					 | 
				
			||||||
    this.value = props.value;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,60 +1,47 @@
 | 
				
			||||||
import { Inject, Injectable, Logger } from '@nestjs/common';
 | 
					import { Injectable } from '@nestjs/common';
 | 
				
			||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  LoggerBase,
 | 
					 | 
				
			||||||
  MessagePublisherPort,
 | 
					 | 
				
			||||||
  PrismaRepositoryBase,
 | 
					 | 
				
			||||||
} from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { SERVICE_NAME } from '@src/app.constants';
 | 
					 | 
				
			||||||
import { ConfigurationEntity } from '../core/domain/configuration.entity';
 | 
					 | 
				
			||||||
import { ConfigurationRepositoryPort } from '../core/application/ports/configuration.repository.port';
 | 
					import { ConfigurationRepositoryPort } from '../core/application/ports/configuration.repository.port';
 | 
				
			||||||
import { PrismaService } from './prisma.service';
 | 
					import { InjectRedis } from '@songkeys/nestjs-redis';
 | 
				
			||||||
import { CONFIGURATION_MESSAGE_PUBLISHER } from '../configuration.di-tokens';
 | 
					import { Redis } from 'ioredis';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ConfigurationIdentifier,
 | 
				
			||||||
 | 
					  ConfigurationValue,
 | 
				
			||||||
 | 
					} from '../core/domain/configuration.types';
 | 
				
			||||||
 | 
					import { ConfigurationEntity } from '../core/domain/configuration.entity';
 | 
				
			||||||
import { ConfigurationMapper } from '../configuration.mapper';
 | 
					import { ConfigurationMapper } from '../configuration.mapper';
 | 
				
			||||||
 | 
					import { NotFoundException } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ConfigurationBaseModel = {
 | 
					export type ConfigurationReadModel = {
 | 
				
			||||||
  uuid: string;
 | 
					 | 
				
			||||||
  domain: string;
 | 
					 | 
				
			||||||
  key: string;
 | 
					  key: string;
 | 
				
			||||||
  value: string;
 | 
					  value: string;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					export type ConfigurationWriteModel = ConfigurationReadModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ConfigurationReadModel = ConfigurationBaseModel & {
 | 
					 | 
				
			||||||
  createdAt: Date;
 | 
					 | 
				
			||||||
  updatedAt: Date;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type ConfigurationWriteModel = ConfigurationBaseModel;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 *  Repository is used for retrieving/saving domain entities
 | 
					 | 
				
			||||||
 * */
 | 
					 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class ConfigurationRepository
 | 
					export class ConfigurationRepository implements ConfigurationRepositoryPort {
 | 
				
			||||||
  extends PrismaRepositoryBase<
 | 
					 | 
				
			||||||
    ConfigurationEntity,
 | 
					 | 
				
			||||||
    ConfigurationReadModel,
 | 
					 | 
				
			||||||
    ConfigurationWriteModel
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
  implements ConfigurationRepositoryPort
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    prisma: PrismaService,
 | 
					    @InjectRedis() private readonly redis: Redis,
 | 
				
			||||||
    mapper: ConfigurationMapper,
 | 
					    private readonly mapper: ConfigurationMapper,
 | 
				
			||||||
    eventEmitter: EventEmitter2,
 | 
					  ) {}
 | 
				
			||||||
    @Inject(CONFIGURATION_MESSAGE_PUBLISHER)
 | 
					
 | 
				
			||||||
    protected readonly messagePublisher: MessagePublisherPort,
 | 
					  get = async (
 | 
				
			||||||
  ) {
 | 
					    identifier: ConfigurationIdentifier,
 | 
				
			||||||
    super(
 | 
					  ): Promise<ConfigurationEntity> => {
 | 
				
			||||||
      prisma.configuration,
 | 
					    const key: string = `${identifier.domain}:${identifier.key}`;
 | 
				
			||||||
      prisma,
 | 
					    const value: ConfigurationValue | null = await this.redis.get(key);
 | 
				
			||||||
      mapper,
 | 
					    if (!value)
 | 
				
			||||||
      eventEmitter,
 | 
					      throw new NotFoundException(
 | 
				
			||||||
      new LoggerBase({
 | 
					        `Configuration item not found for key ${key}`,
 | 
				
			||||||
        logger: new Logger(ConfigurationRepository.name),
 | 
					 | 
				
			||||||
        domain: SERVICE_NAME,
 | 
					 | 
				
			||||||
        messagePublisher,
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
  }
 | 
					    return this.mapper.toDomain({
 | 
				
			||||||
 | 
					      key,
 | 
				
			||||||
 | 
					      value,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set = async (
 | 
				
			||||||
 | 
					    identifier: ConfigurationIdentifier,
 | 
				
			||||||
 | 
					    value: ConfigurationValue,
 | 
				
			||||||
 | 
					  ): Promise<void> => {
 | 
				
			||||||
 | 
					    await this.redis.set(`${identifier.domain}:${identifier.key}`, value);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +0,0 @@
 | 
				
			||||||
import { Injectable, OnModuleInit } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { PrismaClient } from '@prisma/client';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Injectable()
 | 
					 | 
				
			||||||
export class PrismaService extends PrismaClient implements OnModuleInit {
 | 
					 | 
				
			||||||
  async onModuleInit() {
 | 
					 | 
				
			||||||
    await this.$connect();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -4,23 +4,18 @@ package configuration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
service ConfigurationService {
 | 
					service ConfigurationService {
 | 
				
			||||||
  rpc Get(ConfigurationByDomainKey) returns (Configuration);
 | 
					  rpc Get(ConfigurationByDomainKey) returns (Configuration);
 | 
				
			||||||
  rpc Set(Configuration) returns (ConfigurationId);
 | 
					  rpc Set(Configuration) returns (Empty);
 | 
				
			||||||
  rpc Delete(ConfigurationByDomainKey) returns (Empty);
 | 
					 | 
				
			||||||
  rpc Propagate(Empty) returns (Empty);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
message ConfigurationId {
 | 
					 | 
				
			||||||
  string id = 1;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message ConfigurationByDomainKey {
 | 
					message ConfigurationByDomainKey {
 | 
				
			||||||
  string domain = 1;
 | 
					  string domain = 1;
 | 
				
			||||||
  string key = 2;
 | 
					  string key = 2;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message Configuration {
 | 
					message Configuration {
 | 
				
			||||||
  string id = 1;
 | 
					  string domain = 1;
 | 
				
			||||||
  string domain = 2;
 | 
					  string key = 2;
 | 
				
			||||||
  string key = 3;
 | 
					  string value = 3;
 | 
				
			||||||
  string value = 4;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message Empty {}
 | 
					message Empty {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,45 +0,0 @@
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  DatabaseErrorException,
 | 
					 | 
				
			||||||
  NotFoundException,
 | 
					 | 
				
			||||||
  RpcExceptionCode,
 | 
					 | 
				
			||||||
  RpcValidationPipe,
 | 
					 | 
				
			||||||
} from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { Controller, UsePipes } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { CommandBus } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
 | 
					 | 
				
			||||||
import { DeleteConfigurationRequestDto } from './dtos/delete-configuration.request.dto';
 | 
					 | 
				
			||||||
import { GRPC_SERVICE_NAME } from '@src/app.constants';
 | 
					 | 
				
			||||||
import { DeleteConfigurationCommand } from '@modules/configuration/core/application/commands/delete-configuration/delete-configuration.command';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@UsePipes(
 | 
					 | 
				
			||||||
  new RpcValidationPipe({
 | 
					 | 
				
			||||||
    whitelist: true,
 | 
					 | 
				
			||||||
    forbidUnknownValues: false,
 | 
					 | 
				
			||||||
  }),
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
@Controller()
 | 
					 | 
				
			||||||
export class DeleteConfigurationGrpcController {
 | 
					 | 
				
			||||||
  constructor(private readonly commandBus: CommandBus) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @GrpcMethod(GRPC_SERVICE_NAME, 'Delete')
 | 
					 | 
				
			||||||
  async delete(data: DeleteConfigurationRequestDto): Promise<void> {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await this.commandBus.execute(new DeleteConfigurationCommand(data));
 | 
					 | 
				
			||||||
    } catch (error: any) {
 | 
					 | 
				
			||||||
      if (error instanceof NotFoundException)
 | 
					 | 
				
			||||||
        throw new RpcException({
 | 
					 | 
				
			||||||
          code: RpcExceptionCode.NOT_FOUND,
 | 
					 | 
				
			||||||
          message: error.message,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      if (error instanceof DatabaseErrorException)
 | 
					 | 
				
			||||||
        throw new RpcException({
 | 
					 | 
				
			||||||
          code: RpcExceptionCode.INTERNAL,
 | 
					 | 
				
			||||||
          message: error.message,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      throw new RpcException({
 | 
					 | 
				
			||||||
        code: RpcExceptionCode.UNKNOWN,
 | 
					 | 
				
			||||||
        message: error.message,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,12 +0,0 @@
 | 
				
			||||||
import { Domain } from '@modules/configuration/core/domain/configuration.types';
 | 
					 | 
				
			||||||
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class DeleteConfigurationRequestDto {
 | 
					 | 
				
			||||||
  @IsEnum(Domain)
 | 
					 | 
				
			||||||
  @IsNotEmpty()
 | 
					 | 
				
			||||||
  domain: Domain;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsString()
 | 
					 | 
				
			||||||
  @IsNotEmpty()
 | 
					 | 
				
			||||||
  key: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,10 @@
 | 
				
			||||||
import { Domain } from '@modules/configuration/core/domain/configuration.types';
 | 
					import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
 | 
				
			||||||
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
 | 
					import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class GetConfigurationRequestDto {
 | 
					export class GetConfigurationRequestDto {
 | 
				
			||||||
  @IsEnum(Domain)
 | 
					  @IsEnum(ConfigurationDomain)
 | 
				
			||||||
  @IsNotEmpty()
 | 
					  @IsNotEmpty()
 | 
				
			||||||
  domain: Domain;
 | 
					  domain: ConfigurationDomain;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @IsString()
 | 
					  @IsString()
 | 
				
			||||||
  @IsNotEmpty()
 | 
					  @IsNotEmpty()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,10 @@
 | 
				
			||||||
import { Domain } from '@modules/configuration/core/domain/configuration.types';
 | 
					import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
 | 
				
			||||||
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
 | 
					import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class SetConfigurationRequestDto {
 | 
					export class SetConfigurationRequestDto {
 | 
				
			||||||
  @IsEnum(Domain)
 | 
					  @IsEnum(ConfigurationDomain)
 | 
				
			||||||
  @IsNotEmpty()
 | 
					  @IsNotEmpty()
 | 
				
			||||||
  domain: Domain;
 | 
					  domain: ConfigurationDomain;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @IsString()
 | 
					  @IsString()
 | 
				
			||||||
  @IsNotEmpty()
 | 
					  @IsNotEmpty()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,33 +0,0 @@
 | 
				
			||||||
import { Controller, UsePipes } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { CommandBus } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
 | 
					 | 
				
			||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { RpcValidationPipe } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { GRPC_SERVICE_NAME } from '@src/app.constants';
 | 
					 | 
				
			||||||
import { PropagateConfigurationsCommand } from '@modules/configuration/core/application/commands/propagate-configurations/propagate-configurations.command';
 | 
					 | 
				
			||||||
import { v4 } from 'uuid';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@UsePipes(
 | 
					 | 
				
			||||||
  new RpcValidationPipe({
 | 
					 | 
				
			||||||
    whitelist: false,
 | 
					 | 
				
			||||||
    forbidUnknownValues: false,
 | 
					 | 
				
			||||||
  }),
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
@Controller()
 | 
					 | 
				
			||||||
export class PropagateConfigurationsGrpcController {
 | 
					 | 
				
			||||||
  constructor(private readonly commandBus: CommandBus) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @GrpcMethod(GRPC_SERVICE_NAME, 'Propagate')
 | 
					 | 
				
			||||||
  async propagate(): Promise<void> {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await this.commandBus.execute(
 | 
					 | 
				
			||||||
        new PropagateConfigurationsCommand({ id: v4() }),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    } catch (error: any) {
 | 
					 | 
				
			||||||
      throw new RpcException({
 | 
					 | 
				
			||||||
        code: RpcExceptionCode.UNKNOWN,
 | 
					 | 
				
			||||||
        message: error.message,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,6 @@
 | 
				
			||||||
import { Controller, UsePipes } from '@nestjs/common';
 | 
					import { Controller, UsePipes } from '@nestjs/common';
 | 
				
			||||||
import { CommandBus } from '@nestjs/cqrs';
 | 
					import { CommandBus } from '@nestjs/cqrs';
 | 
				
			||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
 | 
					import { GrpcMethod, RpcException } from '@nestjs/microservices';
 | 
				
			||||||
import { AggregateID } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { IdResponse } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
					import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { RpcValidationPipe } from '@mobicoop/ddd-library';
 | 
					import { RpcValidationPipe } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { GRPC_SERVICE_NAME } from '@src/app.constants';
 | 
					import { GRPC_SERVICE_NAME } from '@src/app.constants';
 | 
				
			||||||
| 
						 | 
					@ -22,12 +20,11 @@ export class SetConfigurationGrpcController {
 | 
				
			||||||
  @GrpcMethod(GRPC_SERVICE_NAME, 'Set')
 | 
					  @GrpcMethod(GRPC_SERVICE_NAME, 'Set')
 | 
				
			||||||
  async set(
 | 
					  async set(
 | 
				
			||||||
    setConfigurationRequestDto: SetConfigurationRequestDto,
 | 
					    setConfigurationRequestDto: SetConfigurationRequestDto,
 | 
				
			||||||
  ): Promise<IdResponse> {
 | 
					  ): Promise<void> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const aggregateID: AggregateID = await this.commandBus.execute(
 | 
					      await this.commandBus.execute(
 | 
				
			||||||
        new SetConfigurationCommand(setConfigurationRequestDto),
 | 
					        new SetConfigurationCommand(setConfigurationRequestDto),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      return new IdResponse(aggregateID);
 | 
					 | 
				
			||||||
    } catch (error: any) {
 | 
					    } catch (error: any) {
 | 
				
			||||||
      throw new RpcException({
 | 
					      throw new RpcException({
 | 
				
			||||||
        code: RpcExceptionCode.UNKNOWN,
 | 
					        code: RpcExceptionCode.UNKNOWN,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,174 +0,0 @@
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  CONFIGURATION_MESSAGE_PUBLISHER,
 | 
					 | 
				
			||||||
  CONFIGURATION_REPOSITORY,
 | 
					 | 
				
			||||||
} from '@modules/configuration/configuration.di-tokens';
 | 
					 | 
				
			||||||
import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
 | 
					 | 
				
			||||||
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  CreateConfigurationProps,
 | 
					 | 
				
			||||||
  Domain,
 | 
					 | 
				
			||||||
} from '@modules/configuration/core/domain/configuration.types';
 | 
					 | 
				
			||||||
import { ConfigurationRepository } from '@modules/configuration/infrastructure/configuration.repository';
 | 
					 | 
				
			||||||
import { PrismaService } from '@modules/configuration/infrastructure/prisma.service';
 | 
					 | 
				
			||||||
import { ConfigModule } from '@nestjs/config';
 | 
					 | 
				
			||||||
import { EventEmitterModule } from '@nestjs/event-emitter';
 | 
					 | 
				
			||||||
import { Test } from '@nestjs/testing';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('Configuration Repository', () => {
 | 
					 | 
				
			||||||
  let prismaService: PrismaService;
 | 
					 | 
				
			||||||
  let configurationRepository: ConfigurationRepository;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const executeInsertCommand = async (table: string, object: any) => {
 | 
					 | 
				
			||||||
    const command = `INSERT INTO "${table}" ("${Object.keys(object).join(
 | 
					 | 
				
			||||||
      '","',
 | 
					 | 
				
			||||||
    )}") VALUES ('${Object.values(object).join("','")}')`;
 | 
					 | 
				
			||||||
    await prismaService.$executeRawUnsafe(command);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
  const getSeed = (index: number, uuid: string): string => {
 | 
					 | 
				
			||||||
    return `${uuid.slice(0, -2)}${index.toString(16).padStart(2, '0')}`;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const baseUuid = {
 | 
					 | 
				
			||||||
    uuid: 'be459a29-7a41-4c0b-b371-abe90bfb6f00',
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const createConfigurations = async (nbToCreate = 10) => {
 | 
					 | 
				
			||||||
    for (let i = 0; i < nbToCreate; i++) {
 | 
					 | 
				
			||||||
      const configurationToCreate = {
 | 
					 | 
				
			||||||
        uuid: getSeed(i, baseUuid.uuid),
 | 
					 | 
				
			||||||
        domain: Domain.AD,
 | 
					 | 
				
			||||||
        key: `key${i}`,
 | 
					 | 
				
			||||||
        value: `value${i}`,
 | 
					 | 
				
			||||||
        createdAt: '2023-07-24 13:07:05.000',
 | 
					 | 
				
			||||||
        updatedAt: '2023-07-24 13:07:05.000',
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      configurationToCreate.uuid = getSeed(i, baseUuid.uuid);
 | 
					 | 
				
			||||||
      await executeInsertCommand('configuration', configurationToCreate);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const mockMessagePublisher = {
 | 
					 | 
				
			||||||
    publish: jest.fn().mockImplementation(),
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const mockLogger = {
 | 
					 | 
				
			||||||
    log: jest.fn(),
 | 
					 | 
				
			||||||
    warn: jest.fn(),
 | 
					 | 
				
			||||||
    error: jest.fn(),
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      imports: [
 | 
					 | 
				
			||||||
        EventEmitterModule.forRoot(),
 | 
					 | 
				
			||||||
        ConfigModule.forRoot({ isGlobal: true }),
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        PrismaService,
 | 
					 | 
				
			||||||
        ConfigurationMapper,
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: CONFIGURATION_REPOSITORY,
 | 
					 | 
				
			||||||
          useClass: ConfigurationRepository,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: CONFIGURATION_MESSAGE_PUBLISHER,
 | 
					 | 
				
			||||||
          useValue: mockMessagePublisher,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
      // disable logging
 | 
					 | 
				
			||||||
      .setLogger(mockLogger)
 | 
					 | 
				
			||||||
      .compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    prismaService = module.get<PrismaService>(PrismaService);
 | 
					 | 
				
			||||||
    configurationRepository = module.get<ConfigurationRepository>(
 | 
					 | 
				
			||||||
      CONFIGURATION_REPOSITORY,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  afterAll(async () => {
 | 
					 | 
				
			||||||
    await prismaService.$disconnect();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeEach(async () => {
 | 
					 | 
				
			||||||
    await prismaService.configuration.deleteMany();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('findOne', () => {
 | 
					 | 
				
			||||||
    it('should return a configuration', async () => {
 | 
					 | 
				
			||||||
      await createConfigurations(1);
 | 
					 | 
				
			||||||
      const result = await configurationRepository.findOne({
 | 
					 | 
				
			||||||
        domain: Domain.AD,
 | 
					 | 
				
			||||||
        key: 'key0',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      expect(result.getProps().value).toBe('value0');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('findAll', () => {
 | 
					 | 
				
			||||||
    it('should return all configurations', async () => {
 | 
					 | 
				
			||||||
      await createConfigurations(10);
 | 
					 | 
				
			||||||
      const configurations: ConfigurationEntity[] =
 | 
					 | 
				
			||||||
        await configurationRepository.findAll({});
 | 
					 | 
				
			||||||
      expect(configurations).toHaveLength(10);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('create', () => {
 | 
					 | 
				
			||||||
    it('should create a configuration', async () => {
 | 
					 | 
				
			||||||
      const beforeCount = await prismaService.configuration.count();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const createConfigurationProps: CreateConfigurationProps = {
 | 
					 | 
				
			||||||
        domain: Domain.AD,
 | 
					 | 
				
			||||||
        key: 'seatsProposed',
 | 
					 | 
				
			||||||
        value: '3',
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const configurationToCreate: ConfigurationEntity =
 | 
					 | 
				
			||||||
        ConfigurationEntity.create(createConfigurationProps);
 | 
					 | 
				
			||||||
      await configurationRepository.insert(configurationToCreate);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const afterCount = await prismaService.configuration.count();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      expect(afterCount - beforeCount).toBe(1);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('update', () => {
 | 
					 | 
				
			||||||
    it('should update a configuration', async () => {
 | 
					 | 
				
			||||||
      await createConfigurations(1);
 | 
					 | 
				
			||||||
      const configurationToUpdate: ConfigurationEntity =
 | 
					 | 
				
			||||||
        await configurationRepository.findOne({
 | 
					 | 
				
			||||||
          domain: Domain.AD,
 | 
					 | 
				
			||||||
          key: 'key0',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      configurationToUpdate.update({ value: 'newValue' });
 | 
					 | 
				
			||||||
      await configurationRepository.update(
 | 
					 | 
				
			||||||
        configurationToUpdate.id,
 | 
					 | 
				
			||||||
        configurationToUpdate,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      const result: ConfigurationEntity = await configurationRepository.findOne(
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          domain: Domain.AD,
 | 
					 | 
				
			||||||
          key: 'key0',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      expect(result.getProps().value).toBe('newValue');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('delete', () => {
 | 
					 | 
				
			||||||
    it('should delete a configuration', async () => {
 | 
					 | 
				
			||||||
      await createConfigurations(10);
 | 
					 | 
				
			||||||
      const beforeCount = await prismaService.configuration.count();
 | 
					 | 
				
			||||||
      const configurationToDelete: ConfigurationEntity =
 | 
					 | 
				
			||||||
        await configurationRepository.findOne({
 | 
					 | 
				
			||||||
          domain: Domain.AD,
 | 
					 | 
				
			||||||
          key: 'key4',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      await configurationRepository.delete(configurationToDelete);
 | 
					 | 
				
			||||||
      const afterCount = await prismaService.configuration.count();
 | 
					 | 
				
			||||||
      expect(afterCount - beforeCount).toBe(-1);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
 | 
					import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
 | 
				
			||||||
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
					import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
				
			||||||
import { Domain } from '@modules/configuration/core/domain/configuration.types';
 | 
					import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  ConfigurationReadModel,
 | 
					  ConfigurationReadModel,
 | 
				
			||||||
  ConfigurationWriteModel,
 | 
					  ConfigurationWriteModel,
 | 
				
			||||||
| 
						 | 
					@ -12,20 +12,18 @@ const now = new Date('2023-06-21 06:00:00');
 | 
				
			||||||
const configurationEntity: ConfigurationEntity = new ConfigurationEntity({
 | 
					const configurationEntity: ConfigurationEntity = new ConfigurationEntity({
 | 
				
			||||||
  id: 'c160cf8c-f057-4962-841f-3ad68346df44',
 | 
					  id: 'c160cf8c-f057-4962-841f-3ad68346df44',
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
    domain: Domain.AD,
 | 
					    identifier: {
 | 
				
			||||||
 | 
					      domain: ConfigurationDomain.CARPOOL,
 | 
				
			||||||
      key: 'seatsProposed',
 | 
					      key: 'seatsProposed',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    value: '3',
 | 
					    value: '3',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  createdAt: now,
 | 
					  createdAt: now,
 | 
				
			||||||
  updatedAt: now,
 | 
					  updatedAt: now,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
const configurationReadModel: ConfigurationReadModel = {
 | 
					const configurationReadModel: ConfigurationReadModel = {
 | 
				
			||||||
  uuid: 'c160cf8c-f057-4962-841f-3ad68346df44',
 | 
					  key: 'AD:seatsProposed',
 | 
				
			||||||
  domain: 'AD',
 | 
					 | 
				
			||||||
  key: 'seatsProposed',
 | 
					 | 
				
			||||||
  value: '4',
 | 
					  value: '4',
 | 
				
			||||||
  createdAt: now,
 | 
					 | 
				
			||||||
  updatedAt: now,
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Configuration Mapper', () => {
 | 
					describe('Configuration Mapper', () => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,22 +1,17 @@
 | 
				
			||||||
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
					import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  CreateConfigurationProps,
 | 
					  CreateConfigurationProps,
 | 
				
			||||||
  Domain,
 | 
					  ConfigurationDomain,
 | 
				
			||||||
  UpdateConfigurationProps,
 | 
					 | 
				
			||||||
} from '@modules/configuration/core/domain/configuration.types';
 | 
					} from '@modules/configuration/core/domain/configuration.types';
 | 
				
			||||||
import { ConfigurationDeletedDomainEvent } from '@modules/configuration/core/domain/events/configuration-deleted.domain-event';
 | 
					 | 
				
			||||||
import { ConfigurationSetDomainEvent } from '@modules/configuration/core/domain/events/configuration-set.domain-event';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const createConfigurationProps: CreateConfigurationProps = {
 | 
					const createConfigurationProps: CreateConfigurationProps = {
 | 
				
			||||||
  domain: Domain.AD,
 | 
					  identifier: {
 | 
				
			||||||
 | 
					    domain: ConfigurationDomain.CARPOOL,
 | 
				
			||||||
    key: 'seatsProposed',
 | 
					    key: 'seatsProposed',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  value: '3',
 | 
					  value: '3',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const updateConfigurationProps: UpdateConfigurationProps = {
 | 
					 | 
				
			||||||
  value: '2',
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('Configuration entity create', () => {
 | 
					describe('Configuration entity create', () => {
 | 
				
			||||||
  it('should create a new configuration entity', async () => {
 | 
					  it('should create a new configuration entity', async () => {
 | 
				
			||||||
    const configurationEntity: ConfigurationEntity = ConfigurationEntity.create(
 | 
					    const configurationEntity: ConfigurationEntity = ConfigurationEntity.create(
 | 
				
			||||||
| 
						 | 
					@ -24,38 +19,5 @@ describe('Configuration entity create', () => {
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    expect(configurationEntity.id.length).toBe(36);
 | 
					    expect(configurationEntity.id.length).toBe(36);
 | 
				
			||||||
    expect(configurationEntity.getProps().value).toBe('3');
 | 
					    expect(configurationEntity.getProps().value).toBe('3');
 | 
				
			||||||
    expect(configurationEntity.domainEvents.length).toBe(1);
 | 
					 | 
				
			||||||
    expect(configurationEntity.domainEvents[0]).toBeInstanceOf(
 | 
					 | 
				
			||||||
      ConfigurationSetDomainEvent,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('Configuration entity update', () => {
 | 
					 | 
				
			||||||
  it('should update a configuration entity', async () => {
 | 
					 | 
				
			||||||
    const configurationEntity: ConfigurationEntity = ConfigurationEntity.create(
 | 
					 | 
				
			||||||
      createConfigurationProps,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    configurationEntity.update(updateConfigurationProps);
 | 
					 | 
				
			||||||
    expect(configurationEntity.getProps().value).toBe('2');
 | 
					 | 
				
			||||||
    // 2 events because ConfigurationEntity.create sends a ConfigurationSetDomainEvent
 | 
					 | 
				
			||||||
    expect(configurationEntity.domainEvents.length).toBe(2);
 | 
					 | 
				
			||||||
    expect(configurationEntity.domainEvents[1]).toBeInstanceOf(
 | 
					 | 
				
			||||||
      ConfigurationSetDomainEvent,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('Configuration entity delete', () => {
 | 
					 | 
				
			||||||
  it('should delete a configuration entity', async () => {
 | 
					 | 
				
			||||||
    const configurationEntity: ConfigurationEntity = ConfigurationEntity.create(
 | 
					 | 
				
			||||||
      createConfigurationProps,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    configurationEntity.delete();
 | 
					 | 
				
			||||||
    // 2 events because ConfigurationEntity.create sends a ConfigurationSetDomainEvent
 | 
					 | 
				
			||||||
    expect(configurationEntity.domainEvents.length).toBe(2);
 | 
					 | 
				
			||||||
    expect(configurationEntity.domainEvents[1]).toBeInstanceOf(
 | 
					 | 
				
			||||||
      ConfigurationDeletedDomainEvent,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,56 +0,0 @@
 | 
				
			||||||
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
 | 
					 | 
				
			||||||
import { DeleteConfigurationCommand } from '@modules/configuration/core/application/commands/delete-configuration/delete-configuration.command';
 | 
					 | 
				
			||||||
import { DeleteConfigurationService } from '@modules/configuration/core/application/commands/delete-configuration/delete-configuration.service';
 | 
					 | 
				
			||||||
import { Domain } from '@modules/configuration/core/domain/configuration.types';
 | 
					 | 
				
			||||||
import { DeleteConfigurationRequestDto } from '@modules/configuration/interface/grpc-controllers/dtos/delete-configuration.request.dto';
 | 
					 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const deleteConfigurationRequest: DeleteConfigurationRequestDto = {
 | 
					 | 
				
			||||||
  domain: Domain.AD,
 | 
					 | 
				
			||||||
  key: 'seatsProposed',
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockConfigurationEntity = {
 | 
					 | 
				
			||||||
  delete: jest.fn(),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockConfigurationRepository = {
 | 
					 | 
				
			||||||
  findOne: jest.fn().mockImplementation(() => mockConfigurationEntity),
 | 
					 | 
				
			||||||
  delete: jest.fn().mockImplementationOnce(() => true),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('Delete Configuration Service', () => {
 | 
					 | 
				
			||||||
  let deleteConfigurationService: DeleteConfigurationService;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: CONFIGURATION_REPOSITORY,
 | 
					 | 
				
			||||||
          useValue: mockConfigurationRepository,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        DeleteConfigurationService,
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    deleteConfigurationService = module.get<DeleteConfigurationService>(
 | 
					 | 
				
			||||||
      DeleteConfigurationService,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should be defined', () => {
 | 
					 | 
				
			||||||
    expect(deleteConfigurationService).toBeDefined();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('execution', () => {
 | 
					 | 
				
			||||||
    const deleteConfigurationCommand = new DeleteConfigurationCommand(
 | 
					 | 
				
			||||||
      deleteConfigurationRequest,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    it('should delete a configuration item', async () => {
 | 
					 | 
				
			||||||
      const result: boolean = await deleteConfigurationService.execute(
 | 
					 | 
				
			||||||
        deleteConfigurationCommand,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      expect(result).toBeTruthy();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
					import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
				
			||||||
import { Domain } from '@modules/configuration/core/domain/configuration.types';
 | 
					import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
 | 
				
			||||||
import { GetConfigurationQueryHandler } from '@modules/configuration/core/application/queries/get-configuration/get-configuration.query-handler';
 | 
					import { GetConfigurationQueryHandler } from '@modules/configuration/core/application/queries/get-configuration/get-configuration.query-handler';
 | 
				
			||||||
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
 | 
					import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
 | 
				
			||||||
import { GetConfigurationQuery } from '@modules/configuration/core/application/queries/get-configuration/get-configuration.query';
 | 
					import { GetConfigurationQuery } from '@modules/configuration/core/application/queries/get-configuration/get-configuration.query';
 | 
				
			||||||
| 
						 | 
					@ -9,8 +9,10 @@ const now = new Date('2023-06-21 06:00:00');
 | 
				
			||||||
const configuration: ConfigurationEntity = new ConfigurationEntity({
 | 
					const configuration: ConfigurationEntity = new ConfigurationEntity({
 | 
				
			||||||
  id: 'c160cf8c-f057-4962-841f-3ad68346df44',
 | 
					  id: 'c160cf8c-f057-4962-841f-3ad68346df44',
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
    domain: Domain.AD,
 | 
					    identifier: {
 | 
				
			||||||
 | 
					      domain: ConfigurationDomain.CARPOOL,
 | 
				
			||||||
      key: 'seatsProposed',
 | 
					      key: 'seatsProposed',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    value: '3',
 | 
					    value: '3',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  createdAt: now,
 | 
					  createdAt: now,
 | 
				
			||||||
| 
						 | 
					@ -18,7 +20,7 @@ const configuration: ConfigurationEntity = new ConfigurationEntity({
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mockConfigurationRepository = {
 | 
					const mockConfigurationRepository = {
 | 
				
			||||||
  findOne: jest.fn().mockImplementation(() => configuration),
 | 
					  get: jest.fn().mockImplementation(() => configuration),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Get Configuration Query Handler', () => {
 | 
					describe('Get Configuration Query Handler', () => {
 | 
				
			||||||
| 
						 | 
					@ -47,7 +49,7 @@ describe('Get Configuration Query Handler', () => {
 | 
				
			||||||
  describe('execution', () => {
 | 
					  describe('execution', () => {
 | 
				
			||||||
    it('should return a configuration item', async () => {
 | 
					    it('should return a configuration item', async () => {
 | 
				
			||||||
      const getConfigurationQuery = new GetConfigurationQuery(
 | 
					      const getConfigurationQuery = new GetConfigurationQuery(
 | 
				
			||||||
        Domain.AD,
 | 
					        ConfigurationDomain.CARPOOL,
 | 
				
			||||||
        'seatsProposed',
 | 
					        'seatsProposed',
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      const configuration: ConfigurationEntity =
 | 
					      const configuration: ConfigurationEntity =
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,84 @@
 | 
				
			||||||
 | 
					import { NotFoundException } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
 | 
				
			||||||
 | 
					import { PopulateService } from '@modules/configuration/core/application/services/populate.service';
 | 
				
			||||||
 | 
					import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
				
			||||||
 | 
					import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
 | 
				
			||||||
 | 
					import { ConfigService } from '@nestjs/config';
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockConfigurationRepository = {
 | 
				
			||||||
 | 
					  get: jest
 | 
				
			||||||
 | 
					    .fn()
 | 
				
			||||||
 | 
					    .mockImplementationOnce(
 | 
				
			||||||
 | 
					      () =>
 | 
				
			||||||
 | 
					        new ConfigurationEntity({
 | 
				
			||||||
 | 
					          id: '001199d4-7187-4e83-a044-12159cba2e33',
 | 
				
			||||||
 | 
					          props: {
 | 
				
			||||||
 | 
					            identifier: {
 | 
				
			||||||
 | 
					              domain: ConfigurationDomain.CARPOOL,
 | 
				
			||||||
 | 
					              key: 'someKey',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            value: 'someValue',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          createdAt: new Date('2023-10-23'),
 | 
				
			||||||
 | 
					          updatedAt: new Date('2023-10-23'),
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
 | 
					      throw new NotFoundException('Configuration not found');
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  set: jest.fn(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockConfigService = {
 | 
				
			||||||
 | 
					  get: jest.fn().mockImplementation((domain: string) => {
 | 
				
			||||||
 | 
					    switch (domain) {
 | 
				
			||||||
 | 
					      case 'carpool':
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          departureTimeMargin: 900,
 | 
				
			||||||
 | 
					          role: 'passenger',
 | 
				
			||||||
 | 
					          seatsProposed: 3,
 | 
				
			||||||
 | 
					          seatsRequested: 1,
 | 
				
			||||||
 | 
					          strictFrequency: false,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      case 'pagination':
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          perPage: 10,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Populate Service', () => {
 | 
				
			||||||
 | 
					  let populateService: PopulateService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: CONFIGURATION_REPOSITORY,
 | 
				
			||||||
 | 
					          useValue: mockConfigurationRepository,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: ConfigService,
 | 
				
			||||||
 | 
					          useValue: mockConfigService,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        PopulateService,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    populateService = module.get<PopulateService>(PopulateService);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(populateService).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should populate database with default values', () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockConfigurationRepository, 'get');
 | 
				
			||||||
 | 
					    jest.spyOn(mockConfigurationRepository, 'set');
 | 
				
			||||||
 | 
					    populateService.onApplicationBootstrap();
 | 
				
			||||||
 | 
					    expect(mockConfigurationRepository.get).toHaveBeenCalled();
 | 
				
			||||||
 | 
					    expect(mockConfigurationRepository.set).toHaveBeenCalled();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -1,100 +0,0 @@
 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					 | 
				
			||||||
import { Domain } from '@modules/configuration/core/domain/configuration.types';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  CONFIGURATION_MESSAGE_PUBLISHER,
 | 
					 | 
				
			||||||
  CONFIGURATION_REPOSITORY,
 | 
					 | 
				
			||||||
} from '@modules/configuration/configuration.di-tokens';
 | 
					 | 
				
			||||||
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
					 | 
				
			||||||
import { PropagateConfigurationsService } from '@modules/configuration/core/application/commands/propagate-configurations/propagate-configurations.service';
 | 
					 | 
				
			||||||
import { CONFIGURATION_PROPAGATED_ROUTING_KEY } from '@src/app.constants';
 | 
					 | 
				
			||||||
import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockMessagePublisher = {
 | 
					 | 
				
			||||||
  publish: jest.fn().mockImplementation(),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const configurationEntities = [
 | 
					 | 
				
			||||||
  new ConfigurationEntity({
 | 
					 | 
				
			||||||
    id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
					 | 
				
			||||||
    props: {
 | 
					 | 
				
			||||||
      domain: Domain.AD,
 | 
					 | 
				
			||||||
      key: 'seatsProposed',
 | 
					 | 
				
			||||||
      value: '3',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    createdAt: new Date('2023-10-23T07:00:00Z'),
 | 
					 | 
				
			||||||
    updatedAt: new Date('2023-10-23T07:00:00Z'),
 | 
					 | 
				
			||||||
  }),
 | 
					 | 
				
			||||||
  new ConfigurationEntity({
 | 
					 | 
				
			||||||
    id: '047a6ecf-23d4-4d3e-877c-3225d560a8db',
 | 
					 | 
				
			||||||
    props: {
 | 
					 | 
				
			||||||
      domain: Domain.AD,
 | 
					 | 
				
			||||||
      key: 'seatsRequested',
 | 
					 | 
				
			||||||
      value: '1',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    createdAt: new Date('2023-10-23T07:00:00Z'),
 | 
					 | 
				
			||||||
    updatedAt: new Date('2023-10-23T07:00:00Z'),
 | 
					 | 
				
			||||||
  }),
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockConfigurationMapper = {
 | 
					 | 
				
			||||||
  toResponse: jest
 | 
					 | 
				
			||||||
    .fn()
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => ({
 | 
					 | 
				
			||||||
      domain: Domain.AD,
 | 
					 | 
				
			||||||
      key: 'seatsProposed',
 | 
					 | 
				
			||||||
      value: '3',
 | 
					 | 
				
			||||||
    }))
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => ({
 | 
					 | 
				
			||||||
      domain: Domain.AD,
 | 
					 | 
				
			||||||
      key: 'seatsRequested',
 | 
					 | 
				
			||||||
      value: '1',
 | 
					 | 
				
			||||||
    })),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockConfigurationRepository = {
 | 
					 | 
				
			||||||
  findAll: jest.fn().mockImplementationOnce(() => configurationEntities),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('Propagate Configurations Service', () => {
 | 
					 | 
				
			||||||
  let propagateConfigurationsService: PropagateConfigurationsService;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: CONFIGURATION_REPOSITORY,
 | 
					 | 
				
			||||||
          useValue: mockConfigurationRepository,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: CONFIGURATION_MESSAGE_PUBLISHER,
 | 
					 | 
				
			||||||
          useValue: mockMessagePublisher,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: ConfigurationMapper,
 | 
					 | 
				
			||||||
          useValue: mockConfigurationMapper,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        PropagateConfigurationsService,
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    propagateConfigurationsService = module.get<PropagateConfigurationsService>(
 | 
					 | 
				
			||||||
      PropagateConfigurationsService,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should be defined', () => {
 | 
					 | 
				
			||||||
    expect(propagateConfigurationsService).toBeDefined();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('execution', () => {
 | 
					 | 
				
			||||||
    it('should propagate configuration items', async () => {
 | 
					 | 
				
			||||||
      jest.spyOn(mockMessagePublisher, 'publish');
 | 
					 | 
				
			||||||
      await propagateConfigurationsService.execute();
 | 
					 | 
				
			||||||
      expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
 | 
					 | 
				
			||||||
      expect(mockMessagePublisher.publish).toHaveBeenCalledWith(
 | 
					 | 
				
			||||||
        CONFIGURATION_PROPAGATED_ROUTING_KEY,
 | 
					 | 
				
			||||||
        '[{"domain":"AD","key":"seatsProposed","value":"3"},{"domain":"AD","key":"seatsRequested","value":"1"}]',
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,56 +0,0 @@
 | 
				
			||||||
import { CONFIGURATION_MESSAGE_PUBLISHER } from '@modules/configuration/configuration.di-tokens';
 | 
					 | 
				
			||||||
import { PublishMessageWhenConfigurationIsDeletedDomainEventHandler } from '@modules/configuration/core/application/event-handlers/publish-message-when-configuration-is-deleted.domain-event-handler';
 | 
					 | 
				
			||||||
import { Domain } from '@modules/configuration/core/domain/configuration.types';
 | 
					 | 
				
			||||||
import { ConfigurationDeletedDomainEvent } from '@modules/configuration/core/domain/events/configuration-deleted.domain-event';
 | 
					 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					 | 
				
			||||||
import { CONFIGURATION_DELETED_ROUTING_KEY } from '@src/app.constants';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockMessagePublisher = {
 | 
					 | 
				
			||||||
  publish: jest.fn().mockImplementation(),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('Publish message when configuration is deleted domain event handler', () => {
 | 
					 | 
				
			||||||
  let publishMessageWhenConfigurationIsDeletedDomainEventHandler: PublishMessageWhenConfigurationIsDeletedDomainEventHandler;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: CONFIGURATION_MESSAGE_PUBLISHER,
 | 
					 | 
				
			||||||
          useValue: mockMessagePublisher,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        PublishMessageWhenConfigurationIsDeletedDomainEventHandler,
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    publishMessageWhenConfigurationIsDeletedDomainEventHandler =
 | 
					 | 
				
			||||||
      module.get<PublishMessageWhenConfigurationIsDeletedDomainEventHandler>(
 | 
					 | 
				
			||||||
        PublishMessageWhenConfigurationIsDeletedDomainEventHandler,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should publish a message', () => {
 | 
					 | 
				
			||||||
    jest.spyOn(mockMessagePublisher, 'publish');
 | 
					 | 
				
			||||||
    const configurationDeletedDomainEvent: ConfigurationDeletedDomainEvent = {
 | 
					 | 
				
			||||||
      id: 'some-domain-event-id',
 | 
					 | 
				
			||||||
      domain: Domain.AD,
 | 
					 | 
				
			||||||
      key: 'seatsProposed',
 | 
					 | 
				
			||||||
      aggregateId: 'some-aggregate-id',
 | 
					 | 
				
			||||||
      metadata: {
 | 
					 | 
				
			||||||
        timestamp: new Date('2023-06-28T05:00:00Z').getTime(),
 | 
					 | 
				
			||||||
        correlationId: 'some-correlation-id',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    publishMessageWhenConfigurationIsDeletedDomainEventHandler.handle(
 | 
					 | 
				
			||||||
      configurationDeletedDomainEvent,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    expect(
 | 
					 | 
				
			||||||
      publishMessageWhenConfigurationIsDeletedDomainEventHandler,
 | 
					 | 
				
			||||||
    ).toBeDefined();
 | 
					 | 
				
			||||||
    expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
 | 
					 | 
				
			||||||
    expect(mockMessagePublisher.publish).toHaveBeenCalledWith(
 | 
					 | 
				
			||||||
      CONFIGURATION_DELETED_ROUTING_KEY,
 | 
					 | 
				
			||||||
      '{"id":"some-aggregate-id","metadata":{"correlationId":"some-correlation-id","timestamp":1687928400000},"domain":"AD","key":"seatsProposed"}',
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,57 +0,0 @@
 | 
				
			||||||
import { CONFIGURATION_MESSAGE_PUBLISHER } from '@modules/configuration/configuration.di-tokens';
 | 
					 | 
				
			||||||
import { PublishMessageWhenConfigurationIsSetDomainEventHandler } from '@modules/configuration/core/application/event-handlers/publish-message-when-configuration-is-set.domain-event-handler';
 | 
					 | 
				
			||||||
import { Domain } from '@modules/configuration/core/domain/configuration.types';
 | 
					 | 
				
			||||||
import { ConfigurationSetDomainEvent } from '@modules/configuration/core/domain/events/configuration-set.domain-event';
 | 
					 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					 | 
				
			||||||
import { CONFIGURATION_SET_ROUTING_KEY } from '@src/app.constants';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockMessagePublisher = {
 | 
					 | 
				
			||||||
  publish: jest.fn().mockImplementation(),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('Publish message when configuration is set domain event handler', () => {
 | 
					 | 
				
			||||||
  let publishMessageWhenConfigurationIsSetDomainEventHandler: PublishMessageWhenConfigurationIsSetDomainEventHandler;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: CONFIGURATION_MESSAGE_PUBLISHER,
 | 
					 | 
				
			||||||
          useValue: mockMessagePublisher,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        PublishMessageWhenConfigurationIsSetDomainEventHandler,
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    publishMessageWhenConfigurationIsSetDomainEventHandler =
 | 
					 | 
				
			||||||
      module.get<PublishMessageWhenConfigurationIsSetDomainEventHandler>(
 | 
					 | 
				
			||||||
        PublishMessageWhenConfigurationIsSetDomainEventHandler,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should publish a message', () => {
 | 
					 | 
				
			||||||
    jest.spyOn(mockMessagePublisher, 'publish');
 | 
					 | 
				
			||||||
    const configurationSetDomainEvent: ConfigurationSetDomainEvent = {
 | 
					 | 
				
			||||||
      id: 'some-domain-event-id',
 | 
					 | 
				
			||||||
      aggregateId: 'some-aggregate-id',
 | 
					 | 
				
			||||||
      domain: Domain.AD,
 | 
					 | 
				
			||||||
      key: 'seatsProposed',
 | 
					 | 
				
			||||||
      value: '3',
 | 
					 | 
				
			||||||
      metadata: {
 | 
					 | 
				
			||||||
        timestamp: new Date('2023-06-28T05:00:00Z').getTime(),
 | 
					 | 
				
			||||||
        correlationId: 'some-correlation-id',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    publishMessageWhenConfigurationIsSetDomainEventHandler.handle(
 | 
					 | 
				
			||||||
      configurationSetDomainEvent,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    expect(
 | 
					 | 
				
			||||||
      publishMessageWhenConfigurationIsSetDomainEventHandler,
 | 
					 | 
				
			||||||
    ).toBeDefined();
 | 
					 | 
				
			||||||
    expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
 | 
					 | 
				
			||||||
    expect(mockMessagePublisher.publish).toHaveBeenCalledWith(
 | 
					 | 
				
			||||||
      CONFIGURATION_SET_ROUTING_KEY,
 | 
					 | 
				
			||||||
      '{"id":"some-aggregate-id","metadata":{"correlationId":"some-correlation-id","timestamp":1687928400000},"domain":"AD","key":"seatsProposed","value":"3"}',
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,47 +1,19 @@
 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
import { AggregateID, NotFoundException } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { SetConfigurationRequestDto } from '@modules/configuration/interface/grpc-controllers/dtos/set-configuration.request.dto';
 | 
					import { SetConfigurationRequestDto } from '@modules/configuration/interface/grpc-controllers/dtos/set-configuration.request.dto';
 | 
				
			||||||
import { Domain } from '@modules/configuration/core/domain/configuration.types';
 | 
					import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
 | 
				
			||||||
import { SetConfigurationService } from '@modules/configuration/core/application/commands/set-configuration/set-configuration.service';
 | 
					import { SetConfigurationService } from '@modules/configuration/core/application/commands/set-configuration/set-configuration.service';
 | 
				
			||||||
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
 | 
					import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
 | 
				
			||||||
import { SetConfigurationCommand } from '@modules/configuration/core/application/commands/set-configuration/set-configuration.command';
 | 
					import { SetConfigurationCommand } from '@modules/configuration/core/application/commands/set-configuration/set-configuration.command';
 | 
				
			||||||
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
					import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const setConfigurationRequest: SetConfigurationRequestDto = {
 | 
					const setConfigurationRequest: SetConfigurationRequestDto = {
 | 
				
			||||||
  domain: Domain.AD,
 | 
					  domain: ConfigurationDomain.CARPOOL,
 | 
				
			||||||
  key: 'seatsProposed',
 | 
					  key: 'seatsProposed',
 | 
				
			||||||
  value: '3',
 | 
					  value: '3',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const existingConfigurationEntity = new ConfigurationEntity({
 | 
					 | 
				
			||||||
  id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
					 | 
				
			||||||
  props: {
 | 
					 | 
				
			||||||
    domain: Domain.AD,
 | 
					 | 
				
			||||||
    key: 'seatsProposed',
 | 
					 | 
				
			||||||
    value: '2',
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  createdAt: new Date('2023-10-23T07:00:00Z'),
 | 
					 | 
				
			||||||
  updatedAt: new Date('2023-10-23T07:00:00Z'),
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockConfigurationRepository = {
 | 
					const mockConfigurationRepository = {
 | 
				
			||||||
  findOne: jest
 | 
					  set: jest
 | 
				
			||||||
    .fn()
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => {
 | 
					 | 
				
			||||||
      throw new NotFoundException();
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => {
 | 
					 | 
				
			||||||
      throw new NotFoundException();
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => existingConfigurationEntity)
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => existingConfigurationEntity),
 | 
					 | 
				
			||||||
  insert: jest
 | 
					 | 
				
			||||||
    .fn()
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => ({}))
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => {
 | 
					 | 
				
			||||||
      throw new Error();
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
  update: jest
 | 
					 | 
				
			||||||
    .fn()
 | 
					    .fn()
 | 
				
			||||||
    .mockImplementationOnce(() => ({}))
 | 
					    .mockImplementationOnce(() => ({}))
 | 
				
			||||||
    .mockImplementationOnce(() => {
 | 
					    .mockImplementationOnce(() => {
 | 
				
			||||||
| 
						 | 
					@ -76,31 +48,13 @@ describe('Set Configuration Service', () => {
 | 
				
			||||||
    const setConfigurationCommand = new SetConfigurationCommand(
 | 
					    const setConfigurationCommand = new SetConfigurationCommand(
 | 
				
			||||||
      setConfigurationRequest,
 | 
					      setConfigurationRequest,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    it('should create a new configuration item', async () => {
 | 
					    it('should set an existing configuration item', async () => {
 | 
				
			||||||
 | 
					      jest.spyOn(mockConfigurationRepository, 'set');
 | 
				
			||||||
      ConfigurationEntity.create = jest.fn().mockReturnValue({
 | 
					      ConfigurationEntity.create = jest.fn().mockReturnValue({
 | 
				
			||||||
        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
					        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      const result: AggregateID = await setConfigurationService.execute(
 | 
					      await setConfigurationService.execute(setConfigurationCommand);
 | 
				
			||||||
        setConfigurationCommand,
 | 
					      expect(mockConfigurationRepository.set).toHaveBeenCalledTimes(1);
 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      expect(result).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    it('should throw an error if something bad happens on configuration item creation', async () => {
 | 
					 | 
				
			||||||
      ConfigurationEntity.create = jest.fn().mockReturnValue({
 | 
					 | 
				
			||||||
        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      await expect(
 | 
					 | 
				
			||||||
        setConfigurationService.execute(setConfigurationCommand),
 | 
					 | 
				
			||||||
      ).rejects.toBeInstanceOf(Error);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    it('should update an existing configuration item', async () => {
 | 
					 | 
				
			||||||
      ConfigurationEntity.create = jest.fn().mockReturnValue({
 | 
					 | 
				
			||||||
        id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      const result: AggregateID = await setConfigurationService.execute(
 | 
					 | 
				
			||||||
        setConfigurationCommand,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      expect(result).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da');
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    it('should throw an error if something bad happens on configuration item update', async () => {
 | 
					    it('should throw an error if something bad happens on configuration item update', async () => {
 | 
				
			||||||
      ConfigurationEntity.create = jest.fn().mockReturnValue({
 | 
					      ConfigurationEntity.create = jest.fn().mockReturnValue({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,36 +1,93 @@
 | 
				
			||||||
 | 
					import { NotFoundException } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
 | 
					import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
 | 
				
			||||||
 | 
					import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
 | 
				
			||||||
 | 
					import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
 | 
				
			||||||
import { ConfigurationRepository } from '@modules/configuration/infrastructure/configuration.repository';
 | 
					import { ConfigurationRepository } from '@modules/configuration/infrastructure/configuration.repository';
 | 
				
			||||||
import { PrismaService } from '@modules/configuration/infrastructure/prisma.service';
 | 
					 | 
				
			||||||
import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter';
 | 
					 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					import { getRedisToken } from '@songkeys/nestjs-redis';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mockMessagePublisher = {
 | 
					const mockRedis = {
 | 
				
			||||||
  publish: jest.fn().mockImplementation(),
 | 
					  get: jest
 | 
				
			||||||
 | 
					    .fn()
 | 
				
			||||||
 | 
					    .mockImplementationOnce(() => '1')
 | 
				
			||||||
 | 
					    .mockImplementation(() => null),
 | 
				
			||||||
 | 
					  set: jest.fn().mockImplementation(),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Configuration repository', () => {
 | 
					const mockConfigurationMapper = {
 | 
				
			||||||
  let prismaService: PrismaService;
 | 
					  toDomain: jest.fn().mockImplementation(
 | 
				
			||||||
  let configurationMapper: ConfigurationMapper;
 | 
					    () =>
 | 
				
			||||||
  let eventEmitter: EventEmitter2;
 | 
					      new ConfigurationEntity({
 | 
				
			||||||
 | 
					        id: '001199d4-7187-4e83-a044-12159cba2e33',
 | 
				
			||||||
 | 
					        props: {
 | 
				
			||||||
 | 
					          identifier: {
 | 
				
			||||||
 | 
					            domain: ConfigurationDomain.CARPOOL,
 | 
				
			||||||
 | 
					            key: 'seatsProposed',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          value: '1',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        createdAt: new Date('2023-10-23'),
 | 
				
			||||||
 | 
					        updatedAt: new Date('2023-10-23'),
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Configuration Repository', () => {
 | 
				
			||||||
 | 
					  let configurationRepository: ConfigurationRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeAll(async () => {
 | 
					  beforeAll(async () => {
 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
      imports: [EventEmitterModule.forRoot()],
 | 
					      providers: [
 | 
				
			||||||
      providers: [PrismaService, ConfigurationMapper],
 | 
					        {
 | 
				
			||||||
 | 
					          provide: getRedisToken('default'),
 | 
				
			||||||
 | 
					          useValue: mockRedis,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: ConfigurationMapper,
 | 
				
			||||||
 | 
					          useValue: mockConfigurationMapper,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        ConfigurationRepository,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
    }).compile();
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    prismaService = module.get<PrismaService>(PrismaService);
 | 
					    configurationRepository = module.get<ConfigurationRepository>(
 | 
				
			||||||
    configurationMapper = module.get<ConfigurationMapper>(ConfigurationMapper);
 | 
					      ConfigurationRepository,
 | 
				
			||||||
    eventEmitter = module.get<EventEmitter2>(EventEmitter2);
 | 
					    );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should be defined', () => {
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(configurationRepository).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('interact', () => {
 | 
				
			||||||
 | 
					    it('should get a value', async () => {
 | 
				
			||||||
      expect(
 | 
					      expect(
 | 
				
			||||||
      new ConfigurationRepository(
 | 
					        (
 | 
				
			||||||
        prismaService,
 | 
					          await configurationRepository.get({
 | 
				
			||||||
        configurationMapper,
 | 
					            domain: ConfigurationDomain.CARPOOL,
 | 
				
			||||||
        eventEmitter,
 | 
					            key: 'seatsProposed',
 | 
				
			||||||
        mockMessagePublisher,
 | 
					          })
 | 
				
			||||||
 | 
					        ).getProps().value,
 | 
				
			||||||
 | 
					      ).toBe('1');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should throw if configuration is not found', async () => {
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        configurationRepository.get({
 | 
				
			||||||
 | 
					          domain: ConfigurationDomain.CARPOOL,
 | 
				
			||||||
 | 
					          key: 'seatsProposed',
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      ).rejects.toBeInstanceOf(NotFoundException);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('should set a value', async () => {
 | 
				
			||||||
 | 
					      expect(
 | 
				
			||||||
 | 
					        await configurationRepository.set(
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            domain: ConfigurationDomain.CARPOOL,
 | 
				
			||||||
 | 
					            key: 'seatsProposed',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          '3',
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
    ).toBeDefined();
 | 
					      ).toBeUndefined();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,108 +0,0 @@
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  DatabaseErrorException,
 | 
					 | 
				
			||||||
  NotFoundException,
 | 
					 | 
				
			||||||
} from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { Domain } from '@modules/configuration/core/domain/configuration.types';
 | 
					 | 
				
			||||||
import { DeleteConfigurationGrpcController } from '@modules/configuration/interface/grpc-controllers/delete-configuration.grpc.controller';
 | 
					 | 
				
			||||||
import { DeleteConfigurationRequestDto } from '@modules/configuration/interface/grpc-controllers/dtos/delete-configuration.request.dto';
 | 
					 | 
				
			||||||
import { CommandBus } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import { RpcException } from '@nestjs/microservices';
 | 
					 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const deleteConfigurationRequest: DeleteConfigurationRequestDto = {
 | 
					 | 
				
			||||||
  domain: Domain.AD,
 | 
					 | 
				
			||||||
  key: 'seatsProposed',
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockCommandBus = {
 | 
					 | 
				
			||||||
  execute: jest
 | 
					 | 
				
			||||||
    .fn()
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => ({}))
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => {
 | 
					 | 
				
			||||||
      throw new NotFoundException();
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => {
 | 
					 | 
				
			||||||
      throw new DatabaseErrorException();
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => {
 | 
					 | 
				
			||||||
      throw new Error();
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('Delete Configuration Grpc Controller', () => {
 | 
					 | 
				
			||||||
  let deleteConfigurationGrpcController: DeleteConfigurationGrpcController;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: CommandBus,
 | 
					 | 
				
			||||||
          useValue: mockCommandBus,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        DeleteConfigurationGrpcController,
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    deleteConfigurationGrpcController =
 | 
					 | 
				
			||||||
      module.get<DeleteConfigurationGrpcController>(
 | 
					 | 
				
			||||||
        DeleteConfigurationGrpcController,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  afterEach(async () => {
 | 
					 | 
				
			||||||
    jest.clearAllMocks();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should be defined', () => {
 | 
					 | 
				
			||||||
    expect(deleteConfigurationGrpcController).toBeDefined();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should delete a configuration item', async () => {
 | 
					 | 
				
			||||||
    jest.spyOn(mockCommandBus, 'execute');
 | 
					 | 
				
			||||||
    await deleteConfigurationGrpcController.delete(deleteConfigurationRequest);
 | 
					 | 
				
			||||||
    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should throw a dedicated RpcException if configuration item does not exist', async () => {
 | 
					 | 
				
			||||||
    jest.spyOn(mockCommandBus, 'execute');
 | 
					 | 
				
			||||||
    expect.assertions(3);
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await deleteConfigurationGrpcController.delete(
 | 
					 | 
				
			||||||
        deleteConfigurationRequest,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    } catch (e: any) {
 | 
					 | 
				
			||||||
      expect(e).toBeInstanceOf(RpcException);
 | 
					 | 
				
			||||||
      expect(e.error.code).toBe(RpcExceptionCode.NOT_FOUND);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should throw a dedicated RpcException if a database error occurs', async () => {
 | 
					 | 
				
			||||||
    jest.spyOn(mockCommandBus, 'execute');
 | 
					 | 
				
			||||||
    expect.assertions(3);
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await deleteConfigurationGrpcController.delete(
 | 
					 | 
				
			||||||
        deleteConfigurationRequest,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    } catch (e: any) {
 | 
					 | 
				
			||||||
      expect(e).toBeInstanceOf(RpcException);
 | 
					 | 
				
			||||||
      expect(e.error.code).toBe(RpcExceptionCode.INTERNAL);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should throw a generic RpcException', async () => {
 | 
					 | 
				
			||||||
    jest.spyOn(mockCommandBus, 'execute');
 | 
					 | 
				
			||||||
    expect.assertions(3);
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await deleteConfigurationGrpcController.delete(
 | 
					 | 
				
			||||||
        deleteConfigurationRequest,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    } catch (e: any) {
 | 
					 | 
				
			||||||
      expect(e).toBeInstanceOf(RpcException);
 | 
					 | 
				
			||||||
      expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import { NotFoundException } from '@mobicoop/ddd-library';
 | 
					import { NotFoundException } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
					import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
 | 
					import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
 | 
				
			||||||
import { Domain } from '@modules/configuration/core/domain/configuration.types';
 | 
					import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
 | 
				
			||||||
import { GetConfigurationGrpcController } from '@modules/configuration/interface/grpc-controllers/get-configuration.grpc.controller';
 | 
					import { GetConfigurationGrpcController } from '@modules/configuration/interface/grpc-controllers/get-configuration.grpc.controller';
 | 
				
			||||||
import { QueryBus } from '@nestjs/cqrs';
 | 
					import { QueryBus } from '@nestjs/cqrs';
 | 
				
			||||||
import { RpcException } from '@nestjs/microservices';
 | 
					import { RpcException } from '@nestjs/microservices';
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,7 @@ const mockQueryBus = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mockConfigurationMapper = {
 | 
					const mockConfigurationMapper = {
 | 
				
			||||||
  toResponse: jest.fn().mockImplementationOnce(() => ({
 | 
					  toResponse: jest.fn().mockImplementationOnce(() => ({
 | 
				
			||||||
    domain: Domain.AD,
 | 
					    domain: ConfigurationDomain.CARPOOL,
 | 
				
			||||||
    key: 'seatsProposed',
 | 
					    key: 'seatsProposed',
 | 
				
			||||||
    value: '3',
 | 
					    value: '3',
 | 
				
			||||||
  })),
 | 
					  })),
 | 
				
			||||||
| 
						 | 
					@ -62,7 +62,7 @@ describe('Get Configuration Grpc Controller', () => {
 | 
				
			||||||
    jest.spyOn(mockQueryBus, 'execute');
 | 
					    jest.spyOn(mockQueryBus, 'execute');
 | 
				
			||||||
    jest.spyOn(mockConfigurationMapper, 'toResponse');
 | 
					    jest.spyOn(mockConfigurationMapper, 'toResponse');
 | 
				
			||||||
    const response = await getConfigurationGrpcController.get({
 | 
					    const response = await getConfigurationGrpcController.get({
 | 
				
			||||||
      domain: Domain.AD,
 | 
					      domain: ConfigurationDomain.CARPOOL,
 | 
				
			||||||
      key: 'seatsProposed',
 | 
					      key: 'seatsProposed',
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    expect(response.value).toBe('3');
 | 
					    expect(response.value).toBe('3');
 | 
				
			||||||
| 
						 | 
					@ -76,7 +76,7 @@ describe('Get Configuration Grpc Controller', () => {
 | 
				
			||||||
    expect.assertions(4);
 | 
					    expect.assertions(4);
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      await getConfigurationGrpcController.get({
 | 
					      await getConfigurationGrpcController.get({
 | 
				
			||||||
        domain: Domain.AD,
 | 
					        domain: ConfigurationDomain.CARPOOL,
 | 
				
			||||||
        key: 'price',
 | 
					        key: 'price',
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    } catch (e: any) {
 | 
					    } catch (e: any) {
 | 
				
			||||||
| 
						 | 
					@ -93,7 +93,7 @@ describe('Get Configuration Grpc Controller', () => {
 | 
				
			||||||
    expect.assertions(4);
 | 
					    expect.assertions(4);
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      await getConfigurationGrpcController.get({
 | 
					      await getConfigurationGrpcController.get({
 | 
				
			||||||
        domain: Domain.AD,
 | 
					        domain: ConfigurationDomain.CARPOOL,
 | 
				
			||||||
        key: 'someValue',
 | 
					        key: 'someValue',
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    } catch (e: any) {
 | 
					    } catch (e: any) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,60 +0,0 @@
 | 
				
			||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { PropagateConfigurationsGrpcController } from '@modules/configuration/interface/grpc-controllers/propagate-configurations.grpc.controller';
 | 
					 | 
				
			||||||
import { CommandBus } from '@nestjs/cqrs';
 | 
					 | 
				
			||||||
import { RpcException } from '@nestjs/microservices';
 | 
					 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mockCommandBus = {
 | 
					 | 
				
			||||||
  execute: jest
 | 
					 | 
				
			||||||
    .fn()
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => {})
 | 
					 | 
				
			||||||
    .mockImplementationOnce(() => {
 | 
					 | 
				
			||||||
      throw new Error();
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('Propagate Configurations Grpc Controller', () => {
 | 
					 | 
				
			||||||
  let propagateConfigurationsGrpcController: PropagateConfigurationsGrpcController;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					 | 
				
			||||||
      providers: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          provide: CommandBus,
 | 
					 | 
				
			||||||
          useValue: mockCommandBus,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        PropagateConfigurationsGrpcController,
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    }).compile();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    propagateConfigurationsGrpcController =
 | 
					 | 
				
			||||||
      module.get<PropagateConfigurationsGrpcController>(
 | 
					 | 
				
			||||||
        PropagateConfigurationsGrpcController,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  afterEach(async () => {
 | 
					 | 
				
			||||||
    jest.clearAllMocks();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should be defined', () => {
 | 
					 | 
				
			||||||
    expect(propagateConfigurationsGrpcController).toBeDefined();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should propagate configuration items', async () => {
 | 
					 | 
				
			||||||
    jest.spyOn(mockCommandBus, 'execute');
 | 
					 | 
				
			||||||
    await propagateConfigurationsGrpcController.propagate();
 | 
					 | 
				
			||||||
    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should throw a generic RpcException', async () => {
 | 
					 | 
				
			||||||
    jest.spyOn(mockCommandBus, 'execute');
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await propagateConfigurationsGrpcController.propagate();
 | 
					 | 
				
			||||||
    } catch (e: any) {
 | 
					 | 
				
			||||||
      expect(e).toBeInstanceOf(RpcException);
 | 
					 | 
				
			||||||
      expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,5 @@
 | 
				
			||||||
import { IdResponse } from '@mobicoop/ddd-library';
 | 
					 | 
				
			||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
					import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { Domain } from '@modules/configuration/core/domain/configuration.types';
 | 
					import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
 | 
				
			||||||
import { SetConfigurationRequestDto } from '@modules/configuration/interface/grpc-controllers/dtos/set-configuration.request.dto';
 | 
					import { SetConfigurationRequestDto } from '@modules/configuration/interface/grpc-controllers/dtos/set-configuration.request.dto';
 | 
				
			||||||
import { SetConfigurationGrpcController } from '@modules/configuration/interface/grpc-controllers/set-configuration.grpc.controller';
 | 
					import { SetConfigurationGrpcController } from '@modules/configuration/interface/grpc-controllers/set-configuration.grpc.controller';
 | 
				
			||||||
import { CommandBus } from '@nestjs/cqrs';
 | 
					import { CommandBus } from '@nestjs/cqrs';
 | 
				
			||||||
| 
						 | 
					@ -8,7 +7,7 @@ import { RpcException } from '@nestjs/microservices';
 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const setConfigurationRequest: SetConfigurationRequestDto = {
 | 
					const setConfigurationRequest: SetConfigurationRequestDto = {
 | 
				
			||||||
  domain: Domain.AD,
 | 
					  domain: ConfigurationDomain.CARPOOL,
 | 
				
			||||||
  key: 'seatsProposed',
 | 
					  key: 'seatsProposed',
 | 
				
			||||||
  value: '3',
 | 
					  value: '3',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -51,11 +50,7 @@ describe('Set Configuration Grpc Controller', () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should set a configuration item', async () => {
 | 
					  it('should set a configuration item', async () => {
 | 
				
			||||||
    jest.spyOn(mockCommandBus, 'execute');
 | 
					    jest.spyOn(mockCommandBus, 'execute');
 | 
				
			||||||
    const result: IdResponse = await setConfigurationGrpcController.set(
 | 
					    await setConfigurationGrpcController.set(setConfigurationRequest);
 | 
				
			||||||
      setConfigurationRequest,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    expect(result).toBeInstanceOf(IdResponse);
 | 
					 | 
				
			||||||
    expect(result.id).toBe('200d61a8-d878-4378-a609-c19ea71633d2');
 | 
					 | 
				
			||||||
    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,12 +16,10 @@ const imports = [
 | 
				
			||||||
    useFactory: async (
 | 
					    useFactory: async (
 | 
				
			||||||
      configService: ConfigService,
 | 
					      configService: ConfigService,
 | 
				
			||||||
    ): Promise<MessageBrokerModuleOptions> => ({
 | 
					    ): Promise<MessageBrokerModuleOptions> => ({
 | 
				
			||||||
      uri: configService.get<string>('MESSAGE_BROKER_URI') as string,
 | 
					      uri: configService.get<string>('broker.uri') as string,
 | 
				
			||||||
      exchange: {
 | 
					      exchange: {
 | 
				
			||||||
        name: configService.get<string>('MESSAGE_BROKER_EXCHANGE') as string,
 | 
					        name: configService.get<string>('broker.exchange') as string,
 | 
				
			||||||
        durable: configService.get<boolean>(
 | 
					        durable: configService.get<boolean>('broker.durability') as boolean,
 | 
				
			||||||
          'MESSAGE_BROKER_EXCHANGE_DURABILITY',
 | 
					 | 
				
			||||||
        ) as boolean,
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      name: SERVICE_NAME,
 | 
					      name: SERVICE_NAME,
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue