diff --git a/.env.dist b/.env.dist
new file mode 100644
index 0000000..8d86528
--- /dev/null
+++ b/.env.dist
@@ -0,0 +1,12 @@
+# SERVICE
+SERVICE_URL=0.0.0.0
+SERVICE_PORT=5003
+
+# PRISMA
+DATABASE_URL="postgresql://configuration:configuration@v3-configuration-db:5432/configuration?schema=public"
+
+# RABBIT MQ
+RMQ_URI=amqp://v3-broker:5672
+
+# POSTGRES
+POSTGRES_IMAGE=postgres:15.0
diff --git a/.gitignore b/.gitignore
index 22f55ad..72abbee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,7 @@
+# custom
+.postgresql
+.env
+
# compiled output
/dist
/node_modules
@@ -32,4 +36,4 @@ lerna-debug.log*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
-!.vscode/extensions.json
\ No newline at end of file
+!.vscode/extensions.json
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..04662d2
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,45 @@
+image: docker:20.10.22
+
+stages:
+ - test
+ - build
+
+##############
+# TEST STAGE #
+##############
+
+test:
+ stage: test
+ image: docker/compose:latest
+ variables:
+ DOCKER_TLS_CERTDIR: ""
+ services:
+ - docker:dind
+ script:
+ - docker-compose -f docker-compose.ci.yml --env-file ci/.env.ci up -d
+ - sh ci/wait-up.sh
+ - docker exec -t v3-configuration sh -c "npm run test:integration:ci"
+ coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
+ rules:
+ - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH || $CI_COMMIT_MESSAGE =~ /--check/ || $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
+ when: always
+
+###############
+# BUILD STAGE #
+###############
+
+build:
+ stage: build
+ image: docker:20.10.22
+ variables:
+ DOCKER_TLS_CERTDIR: ""
+ services:
+ - docker:dind
+ before_script:
+ - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
+ script:
+ - docker build --pull -t "$CI_REGISTRY_IMAGE" .
+ - docker push "$CI_REGISTRY_IMAGE"
+ only:
+ - main
+ when: manual
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..67d373d
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,72 @@
+###################
+# BUILD FOR LOCAL DEVELOPMENT
+###################
+
+FROM node:18-alpine3.16 As development
+
+# Create app directory
+WORKDIR /usr/src/app
+
+# Copy application dependency manifests to the container image.
+# A wildcard is used to ensure copying both package.json AND package-lock.json (when available).
+# Copying this first prevents re-running npm install on every code change.
+COPY --chown=node:node package*.json ./
+
+# Install app dependencies using the `npm ci` command instead of `npm install`
+RUN npm ci
+
+# Bundle app source
+COPY --chown=node:node . .
+
+# Use the node user from the image (instead of the root user)
+USER node
+
+###################
+# BUILD FOR PRODUCTION
+###################
+
+FROM node:18-alpine3.16 As build
+
+WORKDIR /usr/src/app
+
+COPY --chown=node:node package*.json ./
+
+# In order to run `npm run build` we need access to the Nest CLI.
+# The Nest CLI is a dev dependency,
+# In the previous development stage we ran `npm ci` which installed all dependencies.
+# So we can copy over the node_modules directory from the development image into this build image.
+COPY --chown=node:node --from=development /usr/src/app/node_modules ./node_modules
+
+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 npm run build
+
+# Set NODE_ENV environment variable
+ENV NODE_ENV production
+# Running `npm ci` removes the existing node_modules directory.
+# Passing in --only=production ensures that only the production dependencies are installed.
+# This ensures that the node_modules directory is as optimized as possible.
+RUN npm ci --only=production && npm cache clean --force
+
+USER node
+
+###################
+# PRODUCTION
+###################
+
+FROM node:18-alpine3.16 As production
+
+# Copy package.json to be able to execute migration command
+COPY --chown=node:node package*.json ./
+
+# 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/prisma ./prisma
+COPY --chown=node:node --from=build /usr/src/app/dist ./dist
+
+# Start the server using the production build
+CMD [ "node", "dist/main.js" ]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d3e9117
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ Mobicoop Interop
+ Copyright (C) 2022 Mobicoop / V3
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
diff --git a/ci/.env.ci b/ci/.env.ci
new file mode 100644
index 0000000..bc9fdb7
--- /dev/null
+++ b/ci/.env.ci
@@ -0,0 +1,15 @@
+# SERVICE
+SERVICE_URL=0.0.0.0
+SERVICE_PORT=5001
+
+# PRISMA
+DATABASE_URL="postgresql://user:user@db:5432/user?schema=public"
+
+# RABBIT MQ
+RMQ_URI=amqp://broker:5672
+
+# MESSAGE BROKER
+BROKER_IMAGE=rabbitmq:3-alpine
+
+# POSTGRES
+POSTGRES_IMAGE=postgres:15.0
diff --git a/ci/Dockerfile b/ci/Dockerfile
new file mode 100644
index 0000000..4b2a20c
--- /dev/null
+++ b/ci/Dockerfile
@@ -0,0 +1,29 @@
+###################
+# BUILD FOR CI TESTING
+###################
+
+FROM node:18-alpine3.16
+
+# Create app directory
+WORKDIR /usr/src/app
+
+# A wildcard is used to ensure both package.json AND package-lock.json are copied
+COPY package*.json ./
+
+# Install app dependencies
+RUN npm ci
+
+# Bundle app source
+COPY . .
+
+# Generate prisma client
+RUN npx prisma generate
+
+# Create a "dist" folder
+RUN npm run build
+
+# Run unit tests
+RUN npm run test:unit:ci
+
+# Start the server
+CMD [ "node", "dist/main.js" ]
diff --git a/ci/wait-up.sh b/ci/wait-up.sh
new file mode 100644
index 0000000..50146ef
--- /dev/null
+++ b/ci/wait-up.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+testlog() {
+ docker logs v3-configuration-db-test | grep -q "database system is ready to accept connections"
+}
+
+testlog 2> /dev/null
+while [ $? -ne 0 ];
+do
+ sleep 5
+ echo "Waiting for Test DB to be up..."
+ testlog 2> /dev/null
+done
diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml
new file mode 100644
index 0000000..2e41f0c
--- /dev/null
+++ b/docker-compose.ci.yml
@@ -0,0 +1,41 @@
+version: '3.8'
+
+services:
+ api:
+ container_name: v3-configuration
+ build:
+ dockerfile: ci/Dockerfile
+ context: .
+ env_file:
+ - ci/.env.ci
+ ports:
+ - 5003:5003
+ depends_on:
+ - db
+ - broker
+ networks:
+ - v3-network
+
+ db:
+ container_name: v3-configuration-db-test
+ image: ${POSTGRES_IMAGE}
+ environment:
+ POSTGRES_DB: configuration
+ POSTGRES_USER: configuration
+ POSTGRES_PASSWORD: configuration
+ ports:
+ - 5603:5432
+ networks:
+ - v3-network
+
+ broker:
+ container_name: v3-broker
+ image: ${BROKER_IMAGE}
+ ports:
+ - 5672:5672
+ networks:
+ - v3-network
+
+networks:
+ v3-network:
+ name: v3-network
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..7f2a7a0
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,57 @@
+version: '3.8'
+
+services:
+ api:
+ container_name: v3-configuration
+ build:
+ dockerfile: Dockerfile
+ context: .
+ target: development
+ volumes:
+ - .:/usr/src/app
+ env_file:
+ - .env
+ command: npm run start:dev
+ ports:
+ - ${SERVICE_PORT:-5003}:${SERVICE_PORT:-5003}
+ depends_on:
+ - db
+ networks:
+ v3-network:
+ aliases:
+ - v3-configuration-api
+
+ db:
+ container_name: v3-configuration-db
+ image: ${POSTGRES_IMAGE}
+ environment:
+ POSTGRES_DB: configuration
+ POSTGRES_USER: configuration
+ POSTGRES_PASSWORD: configuration
+ ports:
+ - 5503:5432
+ volumes:
+ - .postgresql:/var/lib/postgresql/data:rw
+ networks:
+ v3-network:
+ aliases:
+ - v3-configuration-db
+
+ db-test:
+ container_name: v3-configuration-db-test
+ image: ${POSTGRES_IMAGE}
+ environment:
+ POSTGRES_DB: configuration
+ POSTGRES_USER: configuration
+ POSTGRES_PASSWORD: configuration
+ ports:
+ - 5603:5432
+ networks:
+ v3-network:
+ aliases:
+ - v3-configuration-db-test
+
+networks:
+ v3-network:
+ name: v3-network
+ external: true
diff --git a/test/jest-e2e.json b/jest-e2e.json
similarity index 100%
rename from test/jest-e2e.json
rename to jest-e2e.json
diff --git a/nest-cli.json b/nest-cli.json
index 2566481..5142bb1 100644
--- a/nest-cli.json
+++ b/nest-cli.json
@@ -1,5 +1,9 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
- "sourceRoot": "src"
+ "sourceRoot": "src",
+ "compilerOptions": {
+ "assets": ["**/*.proto"],
+ "watchAssets": true
+ }
}
diff --git a/package-lock.json b/package-lock.json
index bcdee19..1b02548 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,17 +1,28 @@
{
- "name": "configuration",
+ "name": "@mobicoop/configuration",
"version": "0.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "name": "configuration",
+ "name": "@mobicoop/configuration",
"version": "0.0.1",
- "license": "UNLICENSED",
+ "license": "AGPL",
"dependencies": {
+ "@automapper/classes": "^8.7.7",
+ "@automapper/core": "^8.7.7",
+ "@automapper/nestjs": "^8.7.7",
+ "@grpc/grpc-js": "^1.8.5",
+ "@grpc/proto-loader": "^0.7.4",
"@nestjs/common": "^9.0.0",
+ "@nestjs/config": "^2.2.0",
"@nestjs/core": "^9.0.0",
+ "@nestjs/cqrs": "^9.0.1",
+ "@nestjs/microservices": "^9.2.1",
"@nestjs/platform-express": "^9.0.0",
+ "@prisma/client": "^4.9.0",
+ "class-transformer": "^0.5.1",
+ "class-validator": "^0.14.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0"
@@ -31,6 +42,7 @@
"eslint-plugin-prettier": "^4.0.0",
"jest": "28.1.3",
"prettier": "^2.3.2",
+ "prisma": "^4.9.0",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "28.0.8",
@@ -197,6 +209,39 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"dev": true
},
+ "node_modules/@automapper/classes": {
+ "version": "8.7.7",
+ "resolved": "https://registry.npmjs.org/@automapper/classes/-/classes-8.7.7.tgz",
+ "integrity": "sha512-FSbvt6QE8XnhKKQZA3kpKLuLrr9x1iW+lNYTrawVLjxQ05zsCGccLxe7moMNrg1wFAVAouQKupFgCGQ7XRjmJw==",
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "@automapper/core": "8.7.7",
+ "reflect-metadata": "~0.1.13"
+ }
+ },
+ "node_modules/@automapper/core": {
+ "version": "8.7.7",
+ "resolved": "https://registry.npmjs.org/@automapper/core/-/core-8.7.7.tgz",
+ "integrity": "sha512-YfpDJ/xqwUuC0S+BLNk81ZJfeL7CmjirUX/Gk9eQyx146DKvneBZgeZ9v5rDB51Ti14jTxVHis+5JuT7W/q0TA==",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@automapper/nestjs": {
+ "version": "8.7.7",
+ "resolved": "https://registry.npmjs.org/@automapper/nestjs/-/nestjs-8.7.7.tgz",
+ "integrity": "sha512-9/uYY2cmN7SJjr2QxnfyXsteHrn/RHD+Dg0VMBflzK/e8Bh/KWyOve7+kaFixlUoyHe44aXs2LVaCslqt8wnhQ==",
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "@automapper/core": "8.7.7",
+ "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0",
+ "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
@@ -852,6 +897,71 @@
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
+ "node_modules/@grpc/grpc-js": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.5.tgz",
+ "integrity": "sha512-+hpNMAz+RdeckGdGRSHj8gjBEv8crcrTZjsagOZtYqCoWu4JXuNqRlxniwxhKFWxNrxZpSMQLmAjIkoDIgFjyQ==",
+ "dependencies": {
+ "@grpc/proto-loader": "^0.7.0",
+ "@types/node": ">=12.12.47"
+ },
+ "engines": {
+ "node": "^8.13.0 || >=10.10.0"
+ }
+ },
+ "node_modules/@grpc/proto-loader": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz",
+ "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==",
+ "dependencies": {
+ "@types/long": "^4.0.1",
+ "lodash.camelcase": "^4.3.0",
+ "long": "^4.0.0",
+ "protobufjs": "^7.0.0",
+ "yargs": "^16.2.0"
+ },
+ "bin": {
+ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@grpc/proto-loader/node_modules/cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "node_modules/@grpc/proto-loader/node_modules/yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dependencies": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@grpc/proto-loader/node_modules/yargs-parser": {
+ "version": "20.2.9",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
@@ -1559,6 +1669,30 @@
}
}
},
+ "node_modules/@nestjs/config": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-2.2.0.tgz",
+ "integrity": "sha512-78Eg6oMbCy3D/YvqeiGBTOWei1Jwi3f2pSIZcZ1QxY67kYsJzTRTkwRT8Iv30DbK0sGKc1mcloDLD5UXgZAZtg==",
+ "dependencies": {
+ "dotenv": "16.0.1",
+ "dotenv-expand": "8.0.3",
+ "lodash": "4.17.21",
+ "uuid": "8.3.2"
+ },
+ "peerDependencies": {
+ "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0",
+ "reflect-metadata": "^0.1.13",
+ "rxjs": "^6.0.0 || ^7.2.0"
+ }
+ },
+ "node_modules/@nestjs/config/node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/@nestjs/core": {
"version": "9.2.1",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-9.2.1.tgz",
@@ -1597,6 +1731,85 @@
}
}
},
+ "node_modules/@nestjs/cqrs": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/@nestjs/cqrs/-/cqrs-9.0.1.tgz",
+ "integrity": "sha512-DsqkAaIHy9+Ot4FJybmlDQk0HBDTGztz0F3LcMnatFDQv1X56W0Y2+R/7jjwHsmHzQfbffT2FGyCOx2/9apFCg==",
+ "dependencies": {
+ "uuid": "8.3.2"
+ },
+ "peerDependencies": {
+ "@nestjs/common": "^9.0.0",
+ "@nestjs/core": "^9.0.0",
+ "reflect-metadata": "0.1.13",
+ "rxjs": "^7.2.0"
+ }
+ },
+ "node_modules/@nestjs/cqrs/node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/@nestjs/microservices": {
+ "version": "9.2.1",
+ "resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-9.2.1.tgz",
+ "integrity": "sha512-ve4dqgoIaUG9VtinHHGCvf6SvtPjd3I44Mj8z8mSkcB8BEPfJnf4FNOShFBgsedtxLaiZyjpv8NCLcNVv5KKGg==",
+ "dependencies": {
+ "iterare": "1.2.1",
+ "tslib": "2.4.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nest"
+ },
+ "peerDependencies": {
+ "@grpc/grpc-js": "*",
+ "@nestjs/common": "^9.0.0",
+ "@nestjs/core": "^9.0.0",
+ "@nestjs/websockets": "^9.0.0",
+ "amqp-connection-manager": "*",
+ "amqplib": "*",
+ "cache-manager": "*",
+ "ioredis": "*",
+ "kafkajs": "*",
+ "mqtt": "*",
+ "nats": "*",
+ "reflect-metadata": "^0.1.12",
+ "rxjs": "^7.1.0"
+ },
+ "peerDependenciesMeta": {
+ "@grpc/grpc-js": {
+ "optional": true
+ },
+ "@nestjs/websockets": {
+ "optional": true
+ },
+ "amqp-connection-manager": {
+ "optional": true
+ },
+ "amqplib": {
+ "optional": true
+ },
+ "cache-manager": {
+ "optional": true
+ },
+ "ioredis": {
+ "optional": true
+ },
+ "kafkajs": {
+ "optional": true
+ },
+ "mqtt": {
+ "optional": true
+ },
+ "nats": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@nestjs/platform-express": {
"version": "9.2.1",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-9.2.1.tgz",
@@ -1727,6 +1940,92 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/@prisma/client": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.9.0.tgz",
+ "integrity": "sha512-bz6QARw54sWcbyR1lLnF2QHvRW5R/Jxnbbmwh3u+969vUKXtBkXgSgjDA85nji31ZBlf7+FrHDy5x+5ydGyQDg==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@prisma/engines-version": "4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5"
+ },
+ "engines": {
+ "node": ">=14.17"
+ },
+ "peerDependencies": {
+ "prisma": "*"
+ },
+ "peerDependenciesMeta": {
+ "prisma": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@prisma/engines": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.9.0.tgz",
+ "integrity": "sha512-t1pt0Gsp+HcgPJrHFc+d/ZSAaKKWar2G/iakrE07yeKPNavDP3iVKPpfXP22OTCHZUWf7OelwKJxQgKAm5hkgw==",
+ "devOptional": true,
+ "hasInstallScript": true
+ },
+ "node_modules/@prisma/engines-version": {
+ "version": "4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5.tgz",
+ "integrity": "sha512-M16aibbxi/FhW7z1sJCX8u+0DriyQYY5AyeTH7plQm9MLnURoiyn3CZBqAyIoQ+Z1pS77usCIibYJWSgleBMBA=="
+ },
+ "node_modules/@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
+ },
+ "node_modules/@protobufjs/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
+ },
+ "node_modules/@protobufjs/codegen": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+ "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
+ },
+ "node_modules/@protobufjs/eventemitter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+ "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
+ },
+ "node_modules/@protobufjs/fetch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+ "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.1",
+ "@protobufjs/inquire": "^1.1.0"
+ }
+ },
+ "node_modules/@protobufjs/float": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
+ },
+ "node_modules/@protobufjs/inquire": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+ "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
+ },
+ "node_modules/@protobufjs/path": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
+ },
+ "node_modules/@protobufjs/pool": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
+ },
+ "node_modules/@protobufjs/utf8": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
+ },
"node_modules/@sinclair/typebox": {
"version": "0.24.51",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz",
@@ -1939,6 +2238,11 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
+ "node_modules/@types/long": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
+ "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="
+ },
"node_modules/@types/mime": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
@@ -1948,8 +2252,7 @@
"node_modules/@types/node": {
"version": "16.18.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.11.tgz",
- "integrity": "sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==",
- "dev": true
+ "integrity": "sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA=="
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
@@ -2016,6 +2319,11 @@
"@types/superagent": "*"
}
},
+ "node_modules/@types/validator": {
+ "version": "13.7.11",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.11.tgz",
+ "integrity": "sha512-WqTos+CnAKN64YwyBMhgUYhb5VsTNKwUY6AuzG5qu9/pFZJar/RJFMZBXwX7VS+uzYi+lIAr3WkvuWqEI9F2eg=="
+ },
"node_modules/@types/yargs": {
"version": "17.0.20",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.20.tgz",
@@ -2500,7 +2808,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -3026,6 +3333,21 @@
"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==",
"dev": true
},
+ "node_modules/class-transformer": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
+ "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="
+ },
+ "node_modules/class-validator": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.0.tgz",
+ "integrity": "sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==",
+ "dependencies": {
+ "@types/validator": "^13.7.10",
+ "libphonenumber-js": "^1.10.14",
+ "validator": "^13.7.0"
+ }
+ },
"node_modules/cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@@ -3414,6 +3736,22 @@
"node": ">=6.0.0"
}
},
+ "node_modules/dotenv": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
+ "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/dotenv-expand": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.3.tgz",
+ "integrity": "sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -3440,8 +3778,7 @@
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/encodeurl": {
"version": "1.0.2",
@@ -3492,7 +3829,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
- "dev": true,
"engines": {
"node": ">=6"
}
@@ -4294,7 +4630,6 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
- "dev": true,
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
@@ -4702,7 +5037,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -5783,6 +6117,11 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/libphonenumber-js": {
+ "version": "1.10.18",
+ "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.18.tgz",
+ "integrity": "sha512-NS4ZEgNhwbcPz1gfSXCGFnQm0xEiyTSPRthIuWytDzOiEG9xnZ2FbLyfJC4tI2BMAAXpoWbNxHYH75pa3Dq9og=="
+ },
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -5816,8 +6155,12 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
- "dev": true
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
},
"node_modules/lodash.memoize": {
"version": "4.1.2",
@@ -5863,6 +6206,11 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/long": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -6612,6 +6960,23 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/prisma": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.9.0.tgz",
+ "integrity": "sha512-bS96oZ5oDFXYgoF2l7PJ3Mp1wWWfLOo8B/jAfbA2Pn0Wm5Z/owBHzaMQKS3i1CzVBDWWPVnOohmbJmjvkcHS5w==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "@prisma/engines": "4.9.0"
+ },
+ "bin": {
+ "prisma": "build/index.js",
+ "prisma2": "build/index.js"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -6630,6 +6995,34 @@
"node": ">= 6"
}
},
+ "node_modules/protobufjs": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.0.tgz",
+ "integrity": "sha512-hYCqTDuII4iJ4stZqiuGCSU8xxWl5JeXYpwARGtn/tWcKCAro6h3WQz+xpsNbXW0UYqpmTQFEyFWO0G0Kjt64g==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.4",
+ "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/fetch": "^1.1.0",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/inquire": "^1.1.0",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.0",
+ "@types/node": ">=13.7.0",
+ "long": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/protobufjs/node_modules/long": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz",
+ "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A=="
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -6796,7 +7189,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -7298,7 +7690,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -7312,7 +7703,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -7996,6 +8386,14 @@
"node": ">=10.12.0"
}
},
+ "node_modules/validator": {
+ "version": "13.7.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
+ "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -8204,7 +8602,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
@@ -8247,7 +8644,6 @@
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
- "dev": true,
"engines": {
"node": ">=10"
}
@@ -8436,6 +8832,23 @@
}
}
},
+ "@automapper/classes": {
+ "version": "8.7.7",
+ "resolved": "https://registry.npmjs.org/@automapper/classes/-/classes-8.7.7.tgz",
+ "integrity": "sha512-FSbvt6QE8XnhKKQZA3kpKLuLrr9x1iW+lNYTrawVLjxQ05zsCGccLxe7moMNrg1wFAVAouQKupFgCGQ7XRjmJw==",
+ "requires": {}
+ },
+ "@automapper/core": {
+ "version": "8.7.7",
+ "resolved": "https://registry.npmjs.org/@automapper/core/-/core-8.7.7.tgz",
+ "integrity": "sha512-YfpDJ/xqwUuC0S+BLNk81ZJfeL7CmjirUX/Gk9eQyx146DKvneBZgeZ9v5rDB51Ti14jTxVHis+5JuT7W/q0TA=="
+ },
+ "@automapper/nestjs": {
+ "version": "8.7.7",
+ "resolved": "https://registry.npmjs.org/@automapper/nestjs/-/nestjs-8.7.7.tgz",
+ "integrity": "sha512-9/uYY2cmN7SJjr2QxnfyXsteHrn/RHD+Dg0VMBflzK/e8Bh/KWyOve7+kaFixlUoyHe44aXs2LVaCslqt8wnhQ==",
+ "requires": {}
+ },
"@babel/code-frame": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
@@ -8941,6 +9354,58 @@
}
}
},
+ "@grpc/grpc-js": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.5.tgz",
+ "integrity": "sha512-+hpNMAz+RdeckGdGRSHj8gjBEv8crcrTZjsagOZtYqCoWu4JXuNqRlxniwxhKFWxNrxZpSMQLmAjIkoDIgFjyQ==",
+ "requires": {
+ "@grpc/proto-loader": "^0.7.0",
+ "@types/node": ">=12.12.47"
+ }
+ },
+ "@grpc/proto-loader": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz",
+ "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==",
+ "requires": {
+ "@types/long": "^4.0.1",
+ "lodash.camelcase": "^4.3.0",
+ "long": "^4.0.0",
+ "protobufjs": "^7.0.0",
+ "yargs": "^16.2.0"
+ },
+ "dependencies": {
+ "cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "requires": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "requires": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ }
+ },
+ "yargs-parser": {
+ "version": "20.2.9",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="
+ }
+ }
+ },
"@humanwhocodes/config-array": {
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
@@ -9478,6 +9943,24 @@
"uuid": "9.0.0"
}
},
+ "@nestjs/config": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-2.2.0.tgz",
+ "integrity": "sha512-78Eg6oMbCy3D/YvqeiGBTOWei1Jwi3f2pSIZcZ1QxY67kYsJzTRTkwRT8Iv30DbK0sGKc1mcloDLD5UXgZAZtg==",
+ "requires": {
+ "dotenv": "16.0.1",
+ "dotenv-expand": "8.0.3",
+ "lodash": "4.17.21",
+ "uuid": "8.3.2"
+ },
+ "dependencies": {
+ "uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
+ }
+ }
+ },
"@nestjs/core": {
"version": "9.2.1",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-9.2.1.tgz",
@@ -9492,6 +9975,30 @@
"uuid": "9.0.0"
}
},
+ "@nestjs/cqrs": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/@nestjs/cqrs/-/cqrs-9.0.1.tgz",
+ "integrity": "sha512-DsqkAaIHy9+Ot4FJybmlDQk0HBDTGztz0F3LcMnatFDQv1X56W0Y2+R/7jjwHsmHzQfbffT2FGyCOx2/9apFCg==",
+ "requires": {
+ "uuid": "8.3.2"
+ },
+ "dependencies": {
+ "uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
+ }
+ }
+ },
+ "@nestjs/microservices": {
+ "version": "9.2.1",
+ "resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-9.2.1.tgz",
+ "integrity": "sha512-ve4dqgoIaUG9VtinHHGCvf6SvtPjd3I44Mj8z8mSkcB8BEPfJnf4FNOShFBgsedtxLaiZyjpv8NCLcNVv5KKGg==",
+ "requires": {
+ "iterare": "1.2.1",
+ "tslib": "2.4.1"
+ }
+ },
"@nestjs/platform-express": {
"version": "9.2.1",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-9.2.1.tgz",
@@ -9573,6 +10080,79 @@
}
}
},
+ "@prisma/client": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.9.0.tgz",
+ "integrity": "sha512-bz6QARw54sWcbyR1lLnF2QHvRW5R/Jxnbbmwh3u+969vUKXtBkXgSgjDA85nji31ZBlf7+FrHDy5x+5ydGyQDg==",
+ "requires": {
+ "@prisma/engines-version": "4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5"
+ }
+ },
+ "@prisma/engines": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.9.0.tgz",
+ "integrity": "sha512-t1pt0Gsp+HcgPJrHFc+d/ZSAaKKWar2G/iakrE07yeKPNavDP3iVKPpfXP22OTCHZUWf7OelwKJxQgKAm5hkgw==",
+ "devOptional": true
+ },
+ "@prisma/engines-version": {
+ "version": "4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5.tgz",
+ "integrity": "sha512-M16aibbxi/FhW7z1sJCX8u+0DriyQYY5AyeTH7plQm9MLnURoiyn3CZBqAyIoQ+Z1pS77usCIibYJWSgleBMBA=="
+ },
+ "@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
+ },
+ "@protobufjs/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
+ },
+ "@protobufjs/codegen": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+ "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
+ },
+ "@protobufjs/eventemitter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+ "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
+ },
+ "@protobufjs/fetch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+ "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+ "requires": {
+ "@protobufjs/aspromise": "^1.1.1",
+ "@protobufjs/inquire": "^1.1.0"
+ }
+ },
+ "@protobufjs/float": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
+ },
+ "@protobufjs/inquire": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+ "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
+ },
+ "@protobufjs/path": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
+ },
+ "@protobufjs/pool": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
+ },
+ "@protobufjs/utf8": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
+ },
"@sinclair/typebox": {
"version": "0.24.51",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz",
@@ -9785,6 +10365,11 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
+ "@types/long": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
+ "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="
+ },
"@types/mime": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
@@ -9794,8 +10379,7 @@
"@types/node": {
"version": "16.18.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.11.tgz",
- "integrity": "sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==",
- "dev": true
+ "integrity": "sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA=="
},
"@types/parse-json": {
"version": "4.0.0",
@@ -9862,6 +10446,11 @@
"@types/superagent": "*"
}
},
+ "@types/validator": {
+ "version": "13.7.11",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.11.tgz",
+ "integrity": "sha512-WqTos+CnAKN64YwyBMhgUYhb5VsTNKwUY6AuzG5qu9/pFZJar/RJFMZBXwX7VS+uzYi+lIAr3WkvuWqEI9F2eg=="
+ },
"@types/yargs": {
"version": "17.0.20",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.20.tgz",
@@ -10215,8 +10804,7 @@
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"ansi-styles": {
"version": "4.3.0",
@@ -10592,6 +11180,21 @@
"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==",
"dev": true
},
+ "class-transformer": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
+ "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="
+ },
+ "class-validator": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.0.tgz",
+ "integrity": "sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==",
+ "requires": {
+ "@types/validator": "^13.7.10",
+ "libphonenumber-js": "^1.10.14",
+ "validator": "^13.7.0"
+ }
+ },
"cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@@ -10884,6 +11487,16 @@
"esutils": "^2.0.2"
}
},
+ "dotenv": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
+ "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ=="
+ },
+ "dotenv-expand": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.3.tgz",
+ "integrity": "sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg=="
+ },
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -10904,8 +11517,7 @@
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"encodeurl": {
"version": "1.0.2",
@@ -10949,8 +11561,7 @@
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
- "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
- "dev": true
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
},
"escape-html": {
"version": "1.0.3",
@@ -11562,8 +12173,7 @@
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
- "dev": true
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"get-intrinsic": {
"version": "1.2.0",
@@ -11850,8 +12460,7 @@
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"is-generator-fn": {
"version": "2.1.0",
@@ -12666,6 +13275,11 @@
"type-check": "~0.4.0"
}
},
+ "libphonenumber-js": {
+ "version": "1.10.18",
+ "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.18.tgz",
+ "integrity": "sha512-NS4ZEgNhwbcPz1gfSXCGFnQm0xEiyTSPRthIuWytDzOiEG9xnZ2FbLyfJC4tI2BMAAXpoWbNxHYH75pa3Dq9og=="
+ },
"lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -12690,8 +13304,12 @@
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
- "dev": true
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
},
"lodash.memoize": {
"version": "4.1.2",
@@ -12727,6 +13345,11 @@
}
}
},
+ "long": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+ },
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -13269,6 +13892,15 @@
}
}
},
+ "prisma": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.9.0.tgz",
+ "integrity": "sha512-bS96oZ5oDFXYgoF2l7PJ3Mp1wWWfLOo8B/jAfbA2Pn0Wm5Z/owBHzaMQKS3i1CzVBDWWPVnOohmbJmjvkcHS5w==",
+ "devOptional": true,
+ "requires": {
+ "@prisma/engines": "4.9.0"
+ }
+ },
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -13284,6 +13916,32 @@
"sisteransi": "^1.0.5"
}
},
+ "protobufjs": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.0.tgz",
+ "integrity": "sha512-hYCqTDuII4iJ4stZqiuGCSU8xxWl5JeXYpwARGtn/tWcKCAro6h3WQz+xpsNbXW0UYqpmTQFEyFWO0G0Kjt64g==",
+ "requires": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.4",
+ "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/fetch": "^1.1.0",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/inquire": "^1.1.0",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.0",
+ "@types/node": ">=13.7.0",
+ "long": "^5.0.0"
+ },
+ "dependencies": {
+ "long": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz",
+ "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A=="
+ }
+ }
+ },
"proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -13407,8 +14065,7 @@
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
- "dev": true
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
},
"require-from-string": {
"version": "2.0.2",
@@ -13790,7 +14447,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -13801,7 +14457,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
"requires": {
"ansi-regex": "^5.0.1"
}
@@ -14258,6 +14913,11 @@
"convert-source-map": "^1.6.0"
}
},
+ "validator": {
+ "version": "13.7.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
+ "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw=="
+ },
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -14411,7 +15071,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
@@ -14441,8 +15100,7 @@
"y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
- "dev": true
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
},
"yallist": {
"version": "3.1.1",
diff --git a/package.json b/package.json
index 380cb45..eb9fdae 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,10 @@
{
- "name": "configuration",
+ "name": "@mobicoop/configuration",
"version": "0.0.1",
- "description": "",
- "author": "",
+ "description": "Mobicoop V3 Configuration Service",
+ "author": "sbriat",
"private": true,
- "license": "UNLICENSED",
+ "license": "AGPL",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
@@ -14,16 +14,33 @@
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
- "test": "jest",
- "test:watch": "jest --watch",
- "test:cov": "jest --coverage",
- "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
- "test:e2e": "jest --config ./test/jest-e2e.json"
+ "test": "npm run migrate:test && dotenv -e .env.test jest",
+ "test:unit": "jest --testPathPattern 'tests/unit/' --verbose",
+ "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:e2e": "jest --config ./test/jest-e2e.json",
+ "generate": "docker exec v3-configuration sh -c 'npx prisma generate'",
+ "migrate": "docker exec v3-configuration 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"
},
"dependencies": {
+ "@automapper/classes": "^8.7.7",
+ "@automapper/core": "^8.7.7",
+ "@automapper/nestjs": "^8.7.7",
+ "@grpc/grpc-js": "^1.8.5",
+ "@grpc/proto-loader": "^0.7.4",
"@nestjs/common": "^9.0.0",
+ "@nestjs/config": "^2.2.0",
"@nestjs/core": "^9.0.0",
+ "@nestjs/cqrs": "^9.0.1",
+ "@nestjs/microservices": "^9.2.1",
"@nestjs/platform-express": "^9.0.0",
+ "@prisma/client": "^4.9.0",
+ "class-transformer": "^0.5.1",
+ "class-validator": "^0.14.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0"
@@ -43,6 +60,7 @@
"eslint-plugin-prettier": "^4.0.0",
"jest": "28.1.3",
"prettier": "^2.3.2",
+ "prisma": "^4.9.0",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "28.0.8",
@@ -57,6 +75,11 @@
"json",
"ts"
],
+ "modulePathIgnorePatterns": [
+ ".controller.ts",
+ ".module.ts",
+ "main.ts"
+ ],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
diff --git a/prisma/migrations/20230125112738_init/migration.sql b/prisma/migrations/20230125112738_init/migration.sql
new file mode 100644
index 0000000..ee7399b
--- /dev/null
+++ b/prisma/migrations/20230125112738_init/migration.sql
@@ -0,0 +1,17 @@
+-- CreateEnum
+CREATE TYPE "Domain" AS ENUM ('USER');
+
+-- CreateTable
+CREATE TABLE "configuration" (
+ "uuid" UUID NOT NULL,
+ "domain" "Domain" 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");
diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml
new file mode 100644
index 0000000..fbffa92
--- /dev/null
+++ b/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "postgresql"
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
new file mode 100644
index 0000000..fc5c0b6
--- /dev/null
+++ b/prisma/schema.prisma
@@ -0,0 +1,27 @@
+// 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"
+}
+
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+}
+
+model Configuration {
+ uuid String @id @default(uuid()) @db.Uuid
+ domain Domain
+ key String
+ value String
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ @@unique([domain, key])
+ @@map("configuration")
+}
+
+enum Domain {
+ USER
+}
diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts
deleted file mode 100644
index d22f389..0000000
--- a/src/app.controller.spec.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Test, TestingModule } from '@nestjs/testing';
-import { AppController } from './app.controller';
-import { AppService } from './app.service';
-
-describe('AppController', () => {
- let appController: AppController;
-
- beforeEach(async () => {
- const app: TestingModule = await Test.createTestingModule({
- controllers: [AppController],
- providers: [AppService],
- }).compile();
-
- appController = app.get(AppController);
- });
-
- describe('root', () => {
- it('should return "Hello World!"', () => {
- expect(appController.getHello()).toBe('Hello World!');
- });
- });
-});
diff --git a/src/app.controller.ts b/src/app.controller.ts
deleted file mode 100644
index cce879e..0000000
--- a/src/app.controller.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Controller, Get } from '@nestjs/common';
-import { AppService } from './app.service';
-
-@Controller()
-export class AppController {
- constructor(private readonly appService: AppService) {}
-
- @Get()
- getHello(): string {
- return this.appService.getHello();
- }
-}
diff --git a/src/app.module.ts b/src/app.module.ts
index 8662803..31d35cd 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -1,10 +1,16 @@
+import { classes } from '@automapper/classes';
+import { AutomapperModule } from '@automapper/nestjs';
import { Module } from '@nestjs/common';
-import { AppController } from './app.controller';
-import { AppService } from './app.service';
+import { ConfigModule } from '@nestjs/config';
+import { ConfigurationModule } from './modules/configuration/configuration.module';
@Module({
- imports: [],
- controllers: [AppController],
- providers: [AppService],
+ imports: [
+ ConfigModule.forRoot({ isGlobal: true }),
+ AutomapperModule.forRoot({ strategyInitializer: classes() }),
+ ConfigurationModule,
+ ],
+ controllers: [],
+ providers: [],
})
export class AppModule {}
diff --git a/src/app.service.ts b/src/app.service.ts
deleted file mode 100644
index 927d7cc..0000000
--- a/src/app.service.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { Injectable } from '@nestjs/common';
-
-@Injectable()
-export class AppService {
- getHello(): string {
- return 'Hello World!';
- }
-}
diff --git a/src/main.ts b/src/main.ts
index 13cad38..db4f1f8 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,8 +1,24 @@
import { NestFactory } from '@nestjs/core';
+import { MicroserviceOptions, Transport } from '@nestjs/microservices';
+import { join } from 'path';
import { AppModule } from './app.module';
async function bootstrap() {
- const app = await NestFactory.create(AppModule);
- await app.listen(3000);
+ const app = await NestFactory.createMicroservice(
+ AppModule,
+ {
+ transport: Transport.GRPC,
+ options: {
+ package: 'configuration',
+ protoPath: join(
+ __dirname,
+ 'modules/configuration/adapters/primaries/configuration.proto',
+ ),
+ url: process.env.SERVICE_URL + ':' + process.env.SERVICE_PORT,
+ loader: { keepCase: true },
+ },
+ },
+ );
+ await app.listen();
}
bootstrap();
diff --git a/src/modules/configuration/adapters/primaries/configuration.controller.ts b/src/modules/configuration/adapters/primaries/configuration.controller.ts
new file mode 100644
index 0000000..a83d4c6
--- /dev/null
+++ b/src/modules/configuration/adapters/primaries/configuration.controller.ts
@@ -0,0 +1,144 @@
+import { Mapper } from '@automapper/core';
+import { InjectMapper } from '@automapper/nestjs';
+import { Controller, UsePipes } from '@nestjs/common';
+import { CommandBus, QueryBus } from '@nestjs/cqrs';
+import { GrpcMethod, RpcException } from '@nestjs/microservices';
+import { DatabaseException } from 'src/modules/database/src/exceptions/database.exception';
+import { ICollection } from 'src/modules/database/src/interfaces/collection.interface';
+import { RpcValidationPipe } from 'src/utils/pipes/rpc.validation-pipe';
+import { CreateConfigurationCommand } from '../../commands/create-configuration.command';
+import { DeleteConfigurationCommand } from '../../commands/delete-configuration.command';
+import { UpdateConfigurationCommand } from '../../commands/update-configuration.command';
+import { CreateConfigurationRequest } from '../../domain/dtos/create-configuration.request';
+import { FindAllConfigurationsRequest } from '../../domain/dtos/find-all-configurations.request';
+import { FindConfigurationByUuidRequest } from '../../domain/dtos/find-configuration-by-uuid.request';
+import { UpdateConfigurationRequest } from '../../domain/dtos/update-configuration.request';
+import { Configuration } from '../../domain/entities/configuration';
+import { FindAllConfigurationsQuery } from '../../queries/find-all-configurations.query';
+import { FindConfigurationByUuidQuery } from '../../queries/find-configuration-by-uuid.query';
+import { ConfigurationPresenter } from './configuration.presenter';
+
+@UsePipes(
+ new RpcValidationPipe({
+ whitelist: true,
+ forbidUnknownValues: false,
+ }),
+)
+@Controller()
+export class ConfigurationController {
+ constructor(
+ private readonly _commandBus: CommandBus,
+ private readonly _queryBus: QueryBus,
+ @InjectMapper() private readonly _mapper: Mapper,
+ ) {}
+
+ @GrpcMethod('ConfigurationService', 'FindAll')
+ async findAll(
+ data: FindAllConfigurationsRequest,
+ ): Promise> {
+ const configurationCollection = await this._queryBus.execute(
+ new FindAllConfigurationsQuery(data),
+ );
+ return Promise.resolve({
+ data: configurationCollection.data.map((configuration: Configuration) =>
+ this._mapper.map(configuration, Configuration, ConfigurationPresenter),
+ ),
+ total: configurationCollection.total,
+ });
+ }
+
+ @GrpcMethod('ConfigurationService', 'FindOneByUuid')
+ async findOneByUuid(
+ data: FindConfigurationByUuidRequest,
+ ): Promise {
+ try {
+ const configuration = await this._queryBus.execute(
+ new FindConfigurationByUuidQuery(data),
+ );
+ return this._mapper.map(
+ configuration,
+ Configuration,
+ ConfigurationPresenter,
+ );
+ } catch (error) {
+ throw new RpcException({
+ code: 5,
+ message: 'Configuration not found',
+ });
+ }
+ }
+
+ @GrpcMethod('ConfigurationService', 'Create')
+ async createConfiguration(
+ data: CreateConfigurationRequest,
+ ): Promise {
+ try {
+ const configuration = await this._commandBus.execute(
+ new CreateConfigurationCommand(data),
+ );
+ return this._mapper.map(
+ configuration,
+ Configuration,
+ ConfigurationPresenter,
+ );
+ } catch (e) {
+ if (e instanceof DatabaseException) {
+ if (e.message.includes('Already exists')) {
+ throw new RpcException({
+ code: 6,
+ message: 'Configuration already exists',
+ });
+ }
+ }
+ throw new RpcException({});
+ }
+ }
+
+ @GrpcMethod('ConfigurationService', 'Update')
+ async updateConfiguration(
+ data: UpdateConfigurationRequest,
+ ): Promise {
+ try {
+ const configuration = await this._commandBus.execute(
+ new UpdateConfigurationCommand(data),
+ );
+
+ return this._mapper.map(
+ configuration,
+ Configuration,
+ ConfigurationPresenter,
+ );
+ } catch (e) {
+ if (e instanceof DatabaseException) {
+ if (e.message.includes('not found')) {
+ throw new RpcException({
+ code: 5,
+ message: 'Configuration not found',
+ });
+ }
+ }
+ throw new RpcException({});
+ }
+ }
+
+ @GrpcMethod('ConfigurationService', 'Delete')
+ async deleteConfiguration(
+ data: FindConfigurationByUuidRequest,
+ ): Promise {
+ try {
+ await this._commandBus.execute(new DeleteConfigurationCommand(data.uuid));
+
+ return Promise.resolve();
+ } catch (e) {
+ if (e instanceof DatabaseException) {
+ if (e.message.includes('not found')) {
+ throw new RpcException({
+ code: 5,
+ message: 'Configuration not found',
+ });
+ }
+ }
+ throw new RpcException({});
+ }
+ }
+}
diff --git a/src/modules/configuration/adapters/primaries/configuration.presenter.ts b/src/modules/configuration/adapters/primaries/configuration.presenter.ts
new file mode 100644
index 0000000..6846d40
--- /dev/null
+++ b/src/modules/configuration/adapters/primaries/configuration.presenter.ts
@@ -0,0 +1,6 @@
+import { AutoMap } from '@automapper/classes';
+
+export class ConfigurationPresenter {
+ @AutoMap()
+ value: string;
+}
diff --git a/src/modules/configuration/adapters/primaries/configuration.proto b/src/modules/configuration/adapters/primaries/configuration.proto
new file mode 100644
index 0000000..0397587
--- /dev/null
+++ b/src/modules/configuration/adapters/primaries/configuration.proto
@@ -0,0 +1,38 @@
+syntax = "proto3";
+
+package configuration;
+
+service ConfigurationService {
+ rpc FindOneByUuid(ConfigurationByUuid) returns (Configuration);
+ rpc FindAll(ConfigurationFilter) returns (Configurations);
+ rpc Create(Configuration) returns (Configuration);
+ rpc Update(Configuration) returns (Configuration);
+ rpc Delete(ConfigurationByUuid) returns (Empty);
+}
+
+message ConfigurationByUuid {
+ string uuid = 1;
+}
+
+message Configuration {
+ string uuid = 1;
+ Domain domain = 2;
+ string key = 3;
+ string value = 4;
+}
+
+enum Domain {
+ user = 0;
+}
+
+message ConfigurationFilter {
+ optional int32 page = 1;
+ optional int32 perPage = 2;
+}
+
+message Configurations {
+ repeated Configuration data = 1;
+ int32 total = 2;
+}
+
+message Empty {}
diff --git a/src/modules/configuration/adapters/secondaries/configuration.repository.ts b/src/modules/configuration/adapters/secondaries/configuration.repository.ts
new file mode 100644
index 0000000..5259497
--- /dev/null
+++ b/src/modules/configuration/adapters/secondaries/configuration.repository.ts
@@ -0,0 +1,8 @@
+import { Injectable } from '@nestjs/common';
+import { ConfigRepository } from '../../../database/src/domain/configuration.repository';
+import { Configuration } from '../../domain/entities/configuration';
+
+@Injectable()
+export class ConfigurationRepository extends ConfigRepository {
+ protected _model = 'configuration';
+}
diff --git a/src/modules/configuration/commands/create-configuration.command.ts b/src/modules/configuration/commands/create-configuration.command.ts
new file mode 100644
index 0000000..2cd2ff3
--- /dev/null
+++ b/src/modules/configuration/commands/create-configuration.command.ts
@@ -0,0 +1,9 @@
+import { CreateConfigurationRequest } from '../domain/dtos/create-configuration.request';
+
+export class CreateConfigurationCommand {
+ readonly createConfigurationRequest: CreateConfigurationRequest;
+
+ constructor(request: CreateConfigurationRequest) {
+ this.createConfigurationRequest = request;
+ }
+}
diff --git a/src/modules/configuration/commands/delete-configuration.command.ts b/src/modules/configuration/commands/delete-configuration.command.ts
new file mode 100644
index 0000000..772a897
--- /dev/null
+++ b/src/modules/configuration/commands/delete-configuration.command.ts
@@ -0,0 +1,7 @@
+export class DeleteConfigurationCommand {
+ readonly uuid: string;
+
+ constructor(uuid: string) {
+ this.uuid = uuid;
+ }
+}
diff --git a/src/modules/configuration/commands/update-configuration.command.ts b/src/modules/configuration/commands/update-configuration.command.ts
new file mode 100644
index 0000000..56d18b9
--- /dev/null
+++ b/src/modules/configuration/commands/update-configuration.command.ts
@@ -0,0 +1,9 @@
+import { UpdateConfigurationRequest } from '../domain/dtos/update-configuration.request';
+
+export class UpdateConfigurationCommand {
+ readonly updateConfigurationRequest: UpdateConfigurationRequest;
+
+ constructor(request: UpdateConfigurationRequest) {
+ this.updateConfigurationRequest = request;
+ }
+}
diff --git a/src/modules/configuration/configuration.module.ts b/src/modules/configuration/configuration.module.ts
new file mode 100644
index 0000000..ab63e64
--- /dev/null
+++ b/src/modules/configuration/configuration.module.ts
@@ -0,0 +1,14 @@
+import { Module } from '@nestjs/common';
+import { CqrsModule } from '@nestjs/cqrs';
+import { DatabaseModule } from '../database/database.module';
+import { ConfigRepository } from '../database/src/domain/configuration.repository';
+import { ConfigurationController } from './adapters/primaries/configuration.controller';
+import { ConfigurationProfile } from './mappers/configuration.profile';
+
+@Module({
+ imports: [DatabaseModule, CqrsModule],
+ exports: [],
+ controllers: [ConfigurationController],
+ providers: [ConfigurationProfile, ConfigRepository],
+})
+export class ConfigurationModule {}
diff --git a/src/modules/configuration/domain/dtos/create-configuration.request.ts b/src/modules/configuration/domain/dtos/create-configuration.request.ts
new file mode 100644
index 0000000..0e8b416
--- /dev/null
+++ b/src/modules/configuration/domain/dtos/create-configuration.request.ts
@@ -0,0 +1,25 @@
+import { AutoMap } from '@automapper/classes';
+import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
+import { Domain } from './domain.enum';
+
+export class CreateConfigurationRequest {
+ @IsString()
+ @IsOptional()
+ @AutoMap()
+ uuid?: string;
+
+ @IsString()
+ @IsNotEmpty()
+ @AutoMap()
+ domain: Domain;
+
+ @IsString()
+ @IsNotEmpty()
+ @AutoMap()
+ key: string;
+
+ @IsEmail()
+ @IsNotEmpty()
+ @AutoMap()
+ value: string;
+}
diff --git a/src/modules/configuration/domain/dtos/domain.enum.ts b/src/modules/configuration/domain/dtos/domain.enum.ts
new file mode 100644
index 0000000..6cb9b92
--- /dev/null
+++ b/src/modules/configuration/domain/dtos/domain.enum.ts
@@ -0,0 +1,3 @@
+export enum Domain {
+ user = 'user',
+}
diff --git a/src/modules/configuration/domain/dtos/find-all-configurations.request.ts b/src/modules/configuration/domain/dtos/find-all-configurations.request.ts
new file mode 100644
index 0000000..e41015e
--- /dev/null
+++ b/src/modules/configuration/domain/dtos/find-all-configurations.request.ts
@@ -0,0 +1,11 @@
+import { IsInt, IsOptional } from 'class-validator';
+
+export class FindAllConfigurationsRequest {
+ @IsInt()
+ @IsOptional()
+ page?: number;
+
+ @IsInt()
+ @IsOptional()
+ perPage?: number;
+}
diff --git a/src/modules/configuration/domain/dtos/find-configuration-by-uuid.request.ts b/src/modules/configuration/domain/dtos/find-configuration-by-uuid.request.ts
new file mode 100644
index 0000000..d116c84
--- /dev/null
+++ b/src/modules/configuration/domain/dtos/find-configuration-by-uuid.request.ts
@@ -0,0 +1,7 @@
+import { IsNotEmpty, IsString } from 'class-validator';
+
+export class FindConfigurationByUuidRequest {
+ @IsString()
+ @IsNotEmpty()
+ uuid: string;
+}
diff --git a/src/modules/configuration/domain/dtos/update-configuration.request.ts b/src/modules/configuration/domain/dtos/update-configuration.request.ts
new file mode 100644
index 0000000..77ec611
--- /dev/null
+++ b/src/modules/configuration/domain/dtos/update-configuration.request.ts
@@ -0,0 +1,25 @@
+import { AutoMap } from '@automapper/classes';
+import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
+import { Domain } from './domain.enum';
+
+export class UpdateConfigurationRequest {
+ @IsString()
+ @IsNotEmpty()
+ @AutoMap()
+ uuid: string;
+
+ @IsString()
+ @IsOptional()
+ @AutoMap()
+ domain?: Domain;
+
+ @IsString()
+ @IsOptional()
+ @AutoMap()
+ key?: string;
+
+ @IsString()
+ @IsOptional()
+ @AutoMap()
+ value?: string;
+}
diff --git a/src/modules/configuration/domain/entities/configuration.ts b/src/modules/configuration/domain/entities/configuration.ts
new file mode 100644
index 0000000..1b1d5a6
--- /dev/null
+++ b/src/modules/configuration/domain/entities/configuration.ts
@@ -0,0 +1,16 @@
+import { AutoMap } from '@automapper/classes';
+import { Domain } from '../dtos/domain.enum';
+
+export class Configuration {
+ @AutoMap()
+ uuid: string;
+
+ @AutoMap()
+ domain: Domain;
+
+ @AutoMap()
+ key: string;
+
+ @AutoMap()
+ value: string;
+}
diff --git a/src/modules/configuration/domain/usecases/create-configuration.usecase.ts b/src/modules/configuration/domain/usecases/create-configuration.usecase.ts
new file mode 100644
index 0000000..0ff0ffb
--- /dev/null
+++ b/src/modules/configuration/domain/usecases/create-configuration.usecase.ts
@@ -0,0 +1,30 @@
+import { Mapper } from '@automapper/core';
+import { InjectMapper } from '@automapper/nestjs';
+import { CommandHandler } from '@nestjs/cqrs';
+import { ConfigurationRepository } from '../../adapters/secondaries/configuration.repository';
+import { CreateConfigurationCommand } from '../../commands/create-configuration.command';
+import { CreateConfigurationRequest } from '../dtos/create-configuration.request';
+import { Configuration } from '../entities/configuration';
+
+@CommandHandler(CreateConfigurationCommand)
+export class CreateConfigurationUseCase {
+ constructor(
+ private readonly _repository: ConfigurationRepository,
+ @InjectMapper() private readonly _mapper: Mapper,
+ ) {}
+
+ async execute(command: CreateConfigurationCommand): Promise {
+ const entity = this._mapper.map(
+ command.createConfigurationRequest,
+ CreateConfigurationRequest,
+ Configuration,
+ );
+
+ try {
+ const configuration = await this._repository.create(entity);
+ return configuration;
+ } catch (error) {
+ throw error;
+ }
+ }
+}
diff --git a/src/modules/configuration/domain/usecases/delete-configuration.usecase.ts b/src/modules/configuration/domain/usecases/delete-configuration.usecase.ts
new file mode 100644
index 0000000..89472aa
--- /dev/null
+++ b/src/modules/configuration/domain/usecases/delete-configuration.usecase.ts
@@ -0,0 +1,18 @@
+import { CommandHandler } from '@nestjs/cqrs';
+import { ConfigurationRepository } from '../../adapters/secondaries/configuration.repository';
+import { DeleteConfigurationCommand } from '../../commands/delete-configuration.command';
+import { Configuration } from '../entities/configuration';
+
+@CommandHandler(DeleteConfigurationCommand)
+export class DeleteConfigurationUseCase {
+ constructor(private readonly _repository: ConfigurationRepository) {}
+
+ async execute(command: DeleteConfigurationCommand): Promise {
+ try {
+ const configuration = await this._repository.delete(command.uuid);
+ return configuration;
+ } catch (error) {
+ throw error;
+ }
+ }
+}
diff --git a/src/modules/configuration/domain/usecases/find-all-configurations.usecase.ts b/src/modules/configuration/domain/usecases/find-all-configurations.usecase.ts
new file mode 100644
index 0000000..84ebd31
--- /dev/null
+++ b/src/modules/configuration/domain/usecases/find-all-configurations.usecase.ts
@@ -0,0 +1,19 @@
+import { QueryHandler } from '@nestjs/cqrs';
+import { ICollection } from 'src/modules/database/src/interfaces/collection.interface';
+import { ConfigurationRepository } from '../../adapters/secondaries/configuration.repository';
+import { FindAllConfigurationsQuery } from '../../queries/find-all-configurations.query';
+import { Configuration } from '../entities/configuration';
+
+@QueryHandler(FindAllConfigurationsQuery)
+export class FindAllConfigurationsUseCase {
+ constructor(private readonly _repository: ConfigurationRepository) {}
+
+ async execute(
+ findAllConfigurationsQuery: FindAllConfigurationsQuery,
+ ): Promise> {
+ return this._repository.findAll(
+ findAllConfigurationsQuery.page,
+ findAllConfigurationsQuery.perPage,
+ );
+ }
+}
diff --git a/src/modules/configuration/domain/usecases/find-configuration-by-uuid.usecase.ts b/src/modules/configuration/domain/usecases/find-configuration-by-uuid.usecase.ts
new file mode 100644
index 0000000..c81dde0
--- /dev/null
+++ b/src/modules/configuration/domain/usecases/find-configuration-by-uuid.usecase.ts
@@ -0,0 +1,24 @@
+import { NotFoundException } from '@nestjs/common';
+import { QueryHandler } from '@nestjs/cqrs';
+import { ConfigurationRepository } from '../../adapters/secondaries/configuration.repository';
+import { FindConfigurationByUuidQuery } from '../../queries/find-configuration-by-uuid.query';
+import { Configuration } from '../entities/configuration';
+
+@QueryHandler(FindConfigurationByUuidQuery)
+export class FindConfigurationByUuidUseCase {
+ constructor(private readonly _repository: ConfigurationRepository) {}
+
+ async execute(
+ findConfigurationByUuid: FindConfigurationByUuidQuery,
+ ): Promise {
+ try {
+ const configuration = await this._repository.findOneByUuid(
+ findConfigurationByUuid.uuid,
+ );
+ if (!configuration) throw new NotFoundException();
+ return configuration;
+ } catch (error) {
+ throw error;
+ }
+ }
+}
diff --git a/src/modules/configuration/domain/usecases/update-configuration.usecase.ts b/src/modules/configuration/domain/usecases/update-configuration.usecase.ts
new file mode 100644
index 0000000..7103c0b
--- /dev/null
+++ b/src/modules/configuration/domain/usecases/update-configuration.usecase.ts
@@ -0,0 +1,33 @@
+import { Mapper } from '@automapper/core';
+import { InjectMapper } from '@automapper/nestjs';
+import { CommandHandler } from '@nestjs/cqrs';
+import { ConfigurationRepository } from '../../adapters/secondaries/configuration.repository';
+import { UpdateConfigurationCommand } from '../../commands/update-configuration.command';
+import { UpdateConfigurationRequest } from '../dtos/update-configuration.request';
+import { Configuration } from '../entities/configuration';
+
+@CommandHandler(UpdateConfigurationCommand)
+export class UpdateConfigurationUseCase {
+ constructor(
+ private readonly _repository: ConfigurationRepository,
+ @InjectMapper() private readonly _mapper: Mapper,
+ ) {}
+
+ async execute(command: UpdateConfigurationCommand): Promise {
+ const entity = this._mapper.map(
+ command.updateConfigurationRequest,
+ UpdateConfigurationRequest,
+ Configuration,
+ );
+
+ try {
+ const configuration = await this._repository.update(
+ command.updateConfigurationRequest.uuid,
+ entity,
+ );
+ return configuration;
+ } catch (error) {
+ throw error;
+ }
+ }
+}
diff --git a/src/modules/configuration/mappers/configuration.profile.ts b/src/modules/configuration/mappers/configuration.profile.ts
new file mode 100644
index 0000000..374b862
--- /dev/null
+++ b/src/modules/configuration/mappers/configuration.profile.ts
@@ -0,0 +1,29 @@
+import { createMap, forMember, ignore, Mapper } from '@automapper/core';
+import { AutomapperProfile, InjectMapper } from '@automapper/nestjs';
+import { Injectable } from '@nestjs/common';
+import { ConfigurationPresenter } from '../adapters/primaries/configuration.presenter';
+import { CreateConfigurationRequest } from '../domain/dtos/create-configuration.request';
+import { UpdateConfigurationRequest } from '../domain/dtos/update-configuration.request';
+import { Configuration } from '../domain/entities/configuration';
+
+@Injectable()
+export class ConfigurationProfile extends AutomapperProfile {
+ constructor(@InjectMapper() mapper: Mapper) {
+ super(mapper);
+ }
+
+ override get profile() {
+ return (mapper: any) => {
+ createMap(mapper, Configuration, ConfigurationPresenter);
+
+ createMap(mapper, CreateConfigurationRequest, Configuration);
+
+ createMap(
+ mapper,
+ UpdateConfigurationRequest,
+ Configuration,
+ forMember((dest) => dest.uuid, ignore()),
+ );
+ };
+ }
+}
diff --git a/src/modules/configuration/queries/find-all-configurations.query.ts b/src/modules/configuration/queries/find-all-configurations.query.ts
new file mode 100644
index 0000000..aa47297
--- /dev/null
+++ b/src/modules/configuration/queries/find-all-configurations.query.ts
@@ -0,0 +1,11 @@
+import { FindAllConfigurationsRequest } from '../domain/dtos/find-all-configurations.request';
+
+export class FindAllConfigurationsQuery {
+ page: number;
+ perPage: number;
+
+ constructor(findAllConfigurationsRequest?: FindAllConfigurationsRequest) {
+ this.page = findAllConfigurationsRequest?.page ?? 1;
+ this.perPage = findAllConfigurationsRequest?.perPage ?? 10;
+ }
+}
diff --git a/src/modules/configuration/queries/find-configuration-by-uuid.query.ts b/src/modules/configuration/queries/find-configuration-by-uuid.query.ts
new file mode 100644
index 0000000..744b66a
--- /dev/null
+++ b/src/modules/configuration/queries/find-configuration-by-uuid.query.ts
@@ -0,0 +1,9 @@
+import { FindConfigurationByUuidRequest } from '../domain/dtos/find-configuration-by-uuid.request';
+
+export class FindConfigurationByUuidQuery {
+ readonly uuid: string;
+
+ constructor(findConfigurationByUuidRequest: FindConfigurationByUuidRequest) {
+ this.uuid = findConfigurationByUuidRequest.uuid;
+ }
+}
diff --git a/src/modules/configuration/tests/unit/create-configuration.usecase.spec.ts b/src/modules/configuration/tests/unit/create-configuration.usecase.spec.ts
new file mode 100644
index 0000000..dc5260c
--- /dev/null
+++ b/src/modules/configuration/tests/unit/create-configuration.usecase.spec.ts
@@ -0,0 +1,74 @@
+import { classes } from '@automapper/classes';
+import { AutomapperModule } from '@automapper/nestjs';
+import { Test, TestingModule } from '@nestjs/testing';
+import { ConfigurationRepository } from '../../adapters/secondaries/configuration.repository';
+import { CreateConfigurationCommand } from '../../commands/create-configuration.command';
+import { CreateConfigurationRequest } from '../../domain/dtos/create-configuration.request';
+import { Domain } from '../../domain/dtos/domain.enum';
+import { Configuration } from '../../domain/entities/configuration';
+import { CreateConfigurationUseCase } from '../../domain/usecases/create-configuration.usecase';
+import { ConfigurationProfile } from '../../mappers/configuration.profile';
+
+const newConfigurationRequest: CreateConfigurationRequest = {
+ domain: Domain.user,
+ key: 'minAge',
+ value: '16',
+};
+const newConfigurationCommand: CreateConfigurationCommand =
+ new CreateConfigurationCommand(newConfigurationRequest);
+
+const mockConfigurationRepository = {
+ create: jest
+ .fn()
+ .mockImplementationOnce(() => {
+ return Promise.resolve({
+ ...newConfigurationRequest,
+ uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
+ });
+ })
+ .mockImplementation(() => {
+ throw new Error('Already exists');
+ }),
+};
+
+describe('CreateConfigurationUseCase', () => {
+ let createConfigurationUseCase: CreateConfigurationUseCase;
+
+ beforeAll(async () => {
+ const module: TestingModule = await Test.createTestingModule({
+ imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })],
+ providers: [
+ {
+ provide: ConfigurationRepository,
+ useValue: mockConfigurationRepository,
+ },
+ CreateConfigurationUseCase,
+ ConfigurationProfile,
+ ],
+ }).compile();
+
+ createConfigurationUseCase = module.get(
+ CreateConfigurationUseCase,
+ );
+ });
+
+ it('should be defined', () => {
+ expect(createConfigurationUseCase).toBeDefined();
+ });
+
+ describe('execute', () => {
+ it('should create and return a new configuration', async () => {
+ const newConfiguration: Configuration =
+ await createConfigurationUseCase.execute(newConfigurationCommand);
+
+ expect(newConfiguration.key).toBe(newConfigurationRequest.key);
+ expect(newConfiguration.uuid).toBeDefined();
+ });
+
+ it('should throw an error if configuration already exists', async () => {
+ await expect(
+ createConfigurationUseCase.execute(newConfigurationCommand),
+ ).rejects.toBeInstanceOf(Error);
+ });
+ });
+});
diff --git a/src/modules/configuration/tests/unit/delete-configuration.usecase.spec.ts b/src/modules/configuration/tests/unit/delete-configuration.usecase.spec.ts
new file mode 100644
index 0000000..491b7da
--- /dev/null
+++ b/src/modules/configuration/tests/unit/delete-configuration.usecase.spec.ts
@@ -0,0 +1,90 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { ConfigurationRepository } from '../../adapters/secondaries/configuration.repository';
+import { DeleteConfigurationCommand } from '../../commands/delete-configuration.command';
+import { Domain } from '../../domain/dtos/domain.enum';
+import { DeleteConfigurationUseCase } from '../../domain/usecases/delete-configuration.usecase';
+
+const mockConfigurations = [
+ {
+ uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
+ domain: Domain.user,
+ key: 'key1',
+ value: 'value1',
+ },
+ {
+ uuid: 'bb281075-1b98-4456-89d6-c643d3044a92',
+ domain: Domain.user,
+ key: 'key2',
+ value: 'value2',
+ },
+ {
+ uuid: 'bb281075-1b98-4456-89d6-c643d3044a93',
+ domain: Domain.user,
+ key: 'key3',
+ value: 'value3',
+ },
+];
+
+const mockConfigurationRepository = {
+ delete: jest
+ .fn()
+ .mockImplementationOnce((uuid: string) => {
+ let savedConfiguration = {};
+ mockConfigurations.forEach((configuration, index) => {
+ if (configuration.uuid === uuid) {
+ savedConfiguration = { ...configuration };
+ mockConfigurations.splice(index, 1);
+ }
+ });
+ return savedConfiguration;
+ })
+ .mockImplementation(() => {
+ throw new Error('Error');
+ }),
+};
+
+describe('DeleteConfigurationUseCase', () => {
+ let deleteConfigurationUseCase: DeleteConfigurationUseCase;
+
+ beforeAll(async () => {
+ const module: TestingModule = await Test.createTestingModule({
+ providers: [
+ {
+ provide: ConfigurationRepository,
+ useValue: mockConfigurationRepository,
+ },
+ DeleteConfigurationUseCase,
+ ],
+ }).compile();
+
+ deleteConfigurationUseCase = module.get(
+ DeleteConfigurationUseCase,
+ );
+ });
+
+ it('should be defined', () => {
+ expect(deleteConfigurationUseCase).toBeDefined();
+ });
+
+ describe('execute', () => {
+ it('should delete a configuration', async () => {
+ const savedUuid = mockConfigurations[0].uuid;
+ const deleteConfigurationCommand = new DeleteConfigurationCommand(
+ savedUuid,
+ );
+ await deleteConfigurationUseCase.execute(deleteConfigurationCommand);
+
+ const deletedConfiguration = mockConfigurations.find(
+ (configuration) => configuration.uuid === savedUuid,
+ );
+ expect(deletedConfiguration).toBeUndefined();
+ });
+ it('should throw an error if configuration does not exist', async () => {
+ await expect(
+ deleteConfigurationUseCase.execute(
+ new DeleteConfigurationCommand('wrong uuid'),
+ ),
+ ).rejects.toBeInstanceOf(Error);
+ });
+ });
+});
diff --git a/src/modules/configuration/tests/unit/find-all-configurations.usecase.spec.ts b/src/modules/configuration/tests/unit/find-all-configurations.usecase.spec.ts
new file mode 100644
index 0000000..ccc089d
--- /dev/null
+++ b/src/modules/configuration/tests/unit/find-all-configurations.usecase.spec.ts
@@ -0,0 +1,78 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { ConfigurationRepository } from '../../adapters/secondaries/configuration.repository';
+import { Domain } from '../../domain/dtos/domain.enum';
+import { FindAllConfigurationsRequest } from '../../domain/dtos/find-all-configurations.request';
+import { FindAllConfigurationsUseCase } from '../../domain/usecases/find-all-configurations.usecase';
+import { FindAllConfigurationsQuery } from '../../queries/find-all-configurations.query';
+
+const findAllConfigurationsRequest: FindAllConfigurationsRequest =
+ new FindAllConfigurationsRequest();
+findAllConfigurationsRequest.page = 1;
+findAllConfigurationsRequest.perPage = 10;
+
+const findAllConfigurationsQuery: FindAllConfigurationsQuery =
+ new FindAllConfigurationsQuery(findAllConfigurationsRequest);
+
+const mockConfigurations = [
+ {
+ uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
+ domain: Domain.user,
+ key: 'key1',
+ value: 'value1',
+ },
+ {
+ uuid: 'bb281075-1b98-4456-89d6-c643d3044a92',
+ domain: Domain.user,
+ key: 'key2',
+ value: 'value2',
+ },
+ {
+ uuid: 'bb281075-1b98-4456-89d6-c643d3044a93',
+ domain: Domain.user,
+ key: 'key3',
+ value: 'value3',
+ },
+];
+
+const mockConfigurationRepository = {
+ findAll: jest
+ .fn()
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ .mockImplementation((query?: FindAllConfigurationsQuery) => {
+ return Promise.resolve(mockConfigurations);
+ }),
+};
+
+describe('FindAllConfigurationsUseCase', () => {
+ let findAllConfigurationsUseCase: FindAllConfigurationsUseCase;
+
+ beforeAll(async () => {
+ const module: TestingModule = await Test.createTestingModule({
+ providers: [
+ {
+ provide: ConfigurationRepository,
+ useValue: mockConfigurationRepository,
+ },
+ FindAllConfigurationsUseCase,
+ ],
+ }).compile();
+
+ findAllConfigurationsUseCase = module.get(
+ FindAllConfigurationsUseCase,
+ );
+ });
+
+ it('should be defined', () => {
+ expect(findAllConfigurationsUseCase).toBeDefined();
+ });
+
+ describe('execute', () => {
+ it('should return an array filled with configurations', async () => {
+ const configurations = await findAllConfigurationsUseCase.execute(
+ findAllConfigurationsQuery,
+ );
+
+ expect(configurations).toBe(mockConfigurations);
+ });
+ });
+});
diff --git a/src/modules/configuration/tests/unit/find-configuration-by-uuid.usecase.spec.ts b/src/modules/configuration/tests/unit/find-configuration-by-uuid.usecase.spec.ts
new file mode 100644
index 0000000..20071a8
--- /dev/null
+++ b/src/modules/configuration/tests/unit/find-configuration-by-uuid.usecase.spec.ts
@@ -0,0 +1,75 @@
+import { NotFoundException } from '@nestjs/common';
+import { Test, TestingModule } from '@nestjs/testing';
+import { ConfigurationRepository } from '../../adapters/secondaries/configuration.repository';
+import { Domain } from '../../domain/dtos/domain.enum';
+import { FindConfigurationByUuidRequest } from '../../domain/dtos/find-configuration-by-uuid.request';
+import { FindConfigurationByUuidUseCase } from '../../domain/usecases/find-configuration-by-uuid.usecase';
+import { FindConfigurationByUuidQuery } from '../../queries/find-configuration-by-uuid.query';
+
+const mockConfiguration = {
+ uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
+ domain: Domain.user,
+ key: 'key1',
+ value: 'value1',
+};
+
+const mockConfigurationRepository = {
+ findOneByUuid: jest
+ .fn()
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ .mockImplementationOnce((query?: FindConfigurationByUuidQuery) => {
+ return Promise.resolve(mockConfiguration);
+ })
+ .mockImplementation(() => {
+ return Promise.resolve(undefined);
+ }),
+};
+
+describe('FindConfigurationByUuidUseCase', () => {
+ let findConfigurationByUuidUseCase: FindConfigurationByUuidUseCase;
+
+ beforeAll(async () => {
+ const module: TestingModule = await Test.createTestingModule({
+ imports: [],
+ providers: [
+ {
+ provide: ConfigurationRepository,
+ useValue: mockConfigurationRepository,
+ },
+ FindConfigurationByUuidUseCase,
+ ],
+ }).compile();
+
+ findConfigurationByUuidUseCase = module.get(
+ FindConfigurationByUuidUseCase,
+ );
+ });
+
+ it('should be defined', () => {
+ expect(findConfigurationByUuidUseCase).toBeDefined();
+ });
+
+ describe('execute', () => {
+ it('should return a Configuration', async () => {
+ const findConfigurationByUuidRequest: FindConfigurationByUuidRequest =
+ new FindConfigurationByUuidRequest();
+ findConfigurationByUuidRequest.uuid =
+ 'bb281075-1b98-4456-89d6-c643d3044a91';
+ const Configuration = await findConfigurationByUuidUseCase.execute(
+ new FindConfigurationByUuidQuery(findConfigurationByUuidRequest),
+ );
+ expect(Configuration).toBe(mockConfiguration);
+ });
+ it('should throw an error if Configuration does not exist', async () => {
+ const findConfigurationByUuidRequest: FindConfigurationByUuidRequest =
+ new FindConfigurationByUuidRequest();
+ findConfigurationByUuidRequest.uuid =
+ 'bb281075-1b98-4456-89d6-c643d3044a90';
+ await expect(
+ findConfigurationByUuidUseCase.execute(
+ new FindConfigurationByUuidQuery(findConfigurationByUuidRequest),
+ ),
+ ).rejects.toBeInstanceOf(NotFoundException);
+ });
+ });
+});
diff --git a/src/modules/configuration/tests/unit/update-configuration.usecase.spec.ts b/src/modules/configuration/tests/unit/update-configuration.usecase.spec.ts
new file mode 100644
index 0000000..fcfb8ad
--- /dev/null
+++ b/src/modules/configuration/tests/unit/update-configuration.usecase.spec.ts
@@ -0,0 +1,75 @@
+import { classes } from '@automapper/classes';
+import { AutomapperModule } from '@automapper/nestjs';
+import { Test, TestingModule } from '@nestjs/testing';
+import { ConfigurationRepository } from '../../adapters/secondaries/configuration.repository';
+import { UpdateConfigurationCommand } from '../../commands/update-configuration.command';
+import { UpdateConfigurationRequest } from '../../domain/dtos/update-configuration.request';
+import { Configuration } from '../../domain/entities/configuration';
+import { UpdateConfigurationUseCase } from '../../domain/usecases/update-configuration.usecase';
+import { ConfigurationProfile } from '../../mappers/configuration.profile';
+
+const originalConfiguration: Configuration = new Configuration();
+originalConfiguration.uuid = 'bb281075-1b98-4456-89d6-c643d3044a91';
+originalConfiguration.key = 'key1';
+
+const updateConfigurationRequest: UpdateConfigurationRequest = {
+ uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
+ key: 'Dane',
+};
+
+const updateConfigurationCommand: UpdateConfigurationCommand =
+ new UpdateConfigurationCommand(updateConfigurationRequest);
+
+const mockConfigurationRepository = {
+ update: jest
+ .fn()
+ .mockImplementationOnce((uuid: string, params: any) => {
+ originalConfiguration.key = params.key;
+
+ return Promise.resolve(originalConfiguration);
+ })
+ .mockImplementation(() => {
+ throw new Error('Error');
+ }),
+};
+
+describe('UpdateConfigurationUseCase', () => {
+ let updateConfigurationUseCase: UpdateConfigurationUseCase;
+
+ beforeAll(async () => {
+ const module: TestingModule = await Test.createTestingModule({
+ imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })],
+
+ providers: [
+ {
+ provide: ConfigurationRepository,
+ useValue: mockConfigurationRepository,
+ },
+ UpdateConfigurationUseCase,
+ ConfigurationProfile,
+ ],
+ }).compile();
+
+ updateConfigurationUseCase = module.get(
+ UpdateConfigurationUseCase,
+ );
+ });
+
+ it('should be defined', () => {
+ expect(updateConfigurationUseCase).toBeDefined();
+ });
+
+ describe('execute', () => {
+ it('should update a configuration', async () => {
+ const updatedConfiguration: Configuration =
+ await updateConfigurationUseCase.execute(updateConfigurationCommand);
+
+ expect(updatedConfiguration.key).toBe(updateConfigurationRequest.key);
+ });
+ it('should throw an error if configuration does not exist', async () => {
+ await expect(
+ updateConfigurationUseCase.execute(updateConfigurationCommand),
+ ).rejects.toBeInstanceOf(Error);
+ });
+ });
+});
diff --git a/src/modules/database/database.module.ts b/src/modules/database/database.module.ts
new file mode 100644
index 0000000..6cf2419
--- /dev/null
+++ b/src/modules/database/database.module.ts
@@ -0,0 +1,9 @@
+import { Module } from '@nestjs/common';
+import { PrismaService } from './src/adapters/secondaries/prisma-service';
+import { ConfigRepository } from './src/domain/configuration.repository';
+
+@Module({
+ providers: [PrismaService, ConfigRepository],
+ exports: [PrismaService, ConfigRepository],
+})
+export class DatabaseModule {}
diff --git a/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts b/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts
new file mode 100644
index 0000000..0c7fbb2
--- /dev/null
+++ b/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts
@@ -0,0 +1,163 @@
+import { Injectable } from '@nestjs/common';
+import { PrismaClientKnownRequestError } from '@prisma/client/runtime';
+import { DatabaseException } from '../../exceptions/database.exception';
+import { ICollection } from '../../interfaces/collection.interface';
+import { IRepository } from '../../interfaces/repository.interface';
+import { PrismaService } from './prisma-service';
+
+/**
+ * Child classes MUST redefined _model property with appropriate model name
+ */
+@Injectable()
+export abstract class PrismaRepository implements IRepository {
+ protected _model: string;
+
+ constructor(protected readonly _prisma: PrismaService) {}
+
+ async findAll(
+ page = 1,
+ perPage = 10,
+ where?: any,
+ include?: any,
+ ): Promise> {
+ const [data, total] = await this._prisma.$transaction([
+ this._prisma[this._model].findMany({
+ where,
+ include,
+ skip: (page - 1) * perPage,
+ take: perPage,
+ }),
+ this._prisma[this._model].count({
+ where,
+ }),
+ ]);
+ return Promise.resolve({
+ data,
+ total,
+ });
+ }
+
+ async findOneByUuid(uuid: string): Promise {
+ try {
+ const entity = await this._prisma[this._model].findUnique({
+ where: { uuid },
+ });
+
+ return entity;
+ } catch (e) {
+ if (e instanceof PrismaClientKnownRequestError) {
+ throw new DatabaseException(
+ PrismaClientKnownRequestError.name,
+ e.code,
+ e.message,
+ );
+ } else {
+ throw new DatabaseException();
+ }
+ }
+ }
+
+ async findOne(where: any, include?: any): Promise {
+ try {
+ const entity = await this._prisma[this._model].findFirst({
+ where: where,
+ include: include,
+ });
+
+ return entity;
+ } catch (e) {
+ if (e instanceof PrismaClientKnownRequestError) {
+ throw new DatabaseException(PrismaClientKnownRequestError.name, e.code);
+ } else {
+ throw new DatabaseException();
+ }
+ }
+ }
+
+ async create(entity: Partial | any, include?: any): Promise {
+ try {
+ const res = await this._prisma[this._model].create({
+ data: entity,
+ include: include,
+ });
+
+ return res;
+ } catch (e) {
+ if (e instanceof PrismaClientKnownRequestError) {
+ throw new DatabaseException(
+ PrismaClientKnownRequestError.name,
+ e.code,
+ e.message,
+ );
+ } else {
+ throw new DatabaseException();
+ }
+ }
+ }
+
+ async update(uuid: string, entity: Partial): Promise {
+ try {
+ const updatedEntity = await this._prisma[this._model].update({
+ where: { uuid },
+ data: entity,
+ });
+
+ return updatedEntity;
+ } catch (e) {
+ if (e instanceof PrismaClientKnownRequestError) {
+ throw new DatabaseException(
+ PrismaClientKnownRequestError.name,
+ e.code,
+ e.message,
+ );
+ } else {
+ throw new DatabaseException();
+ }
+ }
+ }
+
+ async updateWhere(
+ where: any,
+ entity: Partial | any,
+ include?: any,
+ ): Promise {
+ try {
+ const updatedEntity = await this._prisma[this._model].update({
+ where: where,
+ data: entity,
+ include: include,
+ });
+
+ return updatedEntity;
+ } catch (e) {
+ if (e instanceof PrismaClientKnownRequestError) {
+ throw new DatabaseException(
+ PrismaClientKnownRequestError.name,
+ e.code,
+ e.message,
+ );
+ } else {
+ throw new DatabaseException();
+ }
+ }
+ }
+
+ async delete(uuid: string): Promise {
+ try {
+ const entity = await this._prisma[this._model].delete({
+ where: { uuid },
+ });
+ return entity;
+ } catch (e) {
+ if (e instanceof PrismaClientKnownRequestError) {
+ throw new DatabaseException(
+ PrismaClientKnownRequestError.name,
+ e.code,
+ e.message,
+ );
+ } else {
+ throw new DatabaseException();
+ }
+ }
+ }
+}
diff --git a/src/modules/database/src/adapters/secondaries/prisma-service.ts b/src/modules/database/src/adapters/secondaries/prisma-service.ts
new file mode 100644
index 0000000..edf6532
--- /dev/null
+++ b/src/modules/database/src/adapters/secondaries/prisma-service.ts
@@ -0,0 +1,15 @@
+import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
+import { PrismaClient } from '@prisma/client';
+
+@Injectable()
+export class PrismaService extends PrismaClient implements OnModuleInit {
+ async onModuleInit() {
+ await this.$connect();
+ }
+
+ async enableShutdownHooks(app: INestApplication) {
+ this.$on('beforeExit', async () => {
+ await app.close();
+ });
+ }
+}
diff --git a/src/modules/database/src/domain/configuration.repository.ts b/src/modules/database/src/domain/configuration.repository.ts
new file mode 100644
index 0000000..05bc368
--- /dev/null
+++ b/src/modules/database/src/domain/configuration.repository.ts
@@ -0,0 +1,3 @@
+import { PrismaRepository } from '../adapters/secondaries/prisma-repository.abstract';
+
+export class ConfigRepository extends PrismaRepository {}
diff --git a/src/modules/database/src/exceptions/database.exception.ts b/src/modules/database/src/exceptions/database.exception.ts
new file mode 100644
index 0000000..b0782a6
--- /dev/null
+++ b/src/modules/database/src/exceptions/database.exception.ts
@@ -0,0 +1,24 @@
+export class DatabaseException implements Error {
+ name: string;
+ message: string;
+
+ constructor(
+ private _type: string = 'unknown',
+ private _code: string = '',
+ message?: string,
+ ) {
+ this.name = 'DatabaseException';
+ this.message = message ?? 'An error occured with the database.';
+ if (this.message.includes('Unique constraint failed')) {
+ this.message = 'Already exists.';
+ }
+ }
+
+ get type(): string {
+ return this._type;
+ }
+
+ get code(): string {
+ return this._code;
+ }
+}
diff --git a/src/modules/database/src/interfaces/collection.interface.ts b/src/modules/database/src/interfaces/collection.interface.ts
new file mode 100644
index 0000000..6e9a96d
--- /dev/null
+++ b/src/modules/database/src/interfaces/collection.interface.ts
@@ -0,0 +1,4 @@
+export interface ICollection {
+ data: T[];
+ total: number;
+}
diff --git a/src/modules/database/src/interfaces/repository.interface.ts b/src/modules/database/src/interfaces/repository.interface.ts
new file mode 100644
index 0000000..9228712
--- /dev/null
+++ b/src/modules/database/src/interfaces/repository.interface.ts
@@ -0,0 +1,16 @@
+import { ICollection } from './collection.interface';
+
+export interface IRepository {
+ findAll(
+ page: number,
+ perPage: number,
+ params?: any,
+ include?: any,
+ ): Promise>;
+ findOne(where: any, include?: any): Promise;
+ findOneByUuid(uuid: string, include?: any): Promise;
+ create(entity: Partial | any, include?: any): Promise;
+ update(uuid: string, entity: Partial, include?: any): Promise;
+ updateWhere(where: any, entity: Partial | any, include?: any): Promise;
+ delete(uuid: string): Promise;
+}
diff --git a/src/modules/database/tests/unit/prisma-repository.spec.ts b/src/modules/database/tests/unit/prisma-repository.spec.ts
new file mode 100644
index 0000000..064ba6e
--- /dev/null
+++ b/src/modules/database/tests/unit/prisma-repository.spec.ts
@@ -0,0 +1,244 @@
+import { Injectable } from '@nestjs/common';
+import { Test, TestingModule } from '@nestjs/testing';
+import { PrismaService } from '../../src/adapters/secondaries/prisma-service';
+import { PrismaRepository } from '../../src/adapters/secondaries/prisma-repository.abstract';
+import { DatabaseException } from '../../src/exceptions/database.exception';
+
+class FakeEntity {
+ uuid?: string;
+ name: string;
+}
+
+let entityId = 2;
+const entityUuid = 'uuid-';
+const entityName = 'name-';
+
+const createRandomEntity = (): FakeEntity => {
+ const entity: FakeEntity = {
+ uuid: `${entityUuid}${entityId}`,
+ name: `${entityName}${entityId}`,
+ };
+
+ entityId++;
+
+ return entity;
+};
+
+const fakeEntityToCreate: FakeEntity = {
+ name: 'test',
+};
+
+const fakeEntityCreated: FakeEntity = {
+ ...fakeEntityToCreate,
+ uuid: 'some-uuid',
+};
+
+const fakeEntities: FakeEntity[] = [];
+Array.from({ length: 10 }).forEach(() => {
+ fakeEntities.push(createRandomEntity());
+});
+
+@Injectable()
+class FakePrismaRepository extends PrismaRepository {
+ protected _model = 'fake';
+}
+
+class FakePrismaService extends PrismaService {
+ fake: any;
+}
+
+const mockPrismaService = {
+ $transaction: jest.fn().mockImplementation(async (data: any) => {
+ const entities = await data[0];
+ if (entities.length == 1) {
+ return Promise.resolve([[fakeEntityCreated], 1]);
+ }
+
+ return Promise.resolve([fakeEntities, fakeEntities.length]);
+ }),
+ fake: {
+ create: jest.fn().mockResolvedValue(fakeEntityCreated),
+
+ findMany: jest.fn().mockImplementation((params?: any) => {
+ if (params?.where?.limit == 1) {
+ return Promise.resolve([fakeEntityCreated]);
+ }
+
+ return Promise.resolve(fakeEntities);
+ }),
+ count: jest.fn().mockResolvedValue(fakeEntities.length),
+
+ findUnique: jest.fn().mockImplementation(async (params?: any) => {
+ let entity;
+
+ if (params?.where?.uuid) {
+ entity = fakeEntities.find(
+ (entity) => entity.uuid === params?.where?.uuid,
+ );
+ }
+
+ if (!entity) {
+ throw new Error('no entity');
+ }
+
+ return entity;
+ }),
+
+ findFirst: jest.fn().mockImplementation((params?: any) => {
+ if (params?.where?.name) {
+ return Promise.resolve(
+ fakeEntities.find((entity) => entity.name === params?.where?.name),
+ );
+ }
+ }),
+
+ update: jest.fn().mockImplementation((params: any) => {
+ const entity = fakeEntities.find(
+ (entity) => entity.uuid === params.where.uuid,
+ );
+ Object.entries(params.data).map(([key, value]) => {
+ entity[key] = value;
+ });
+
+ return Promise.resolve(entity);
+ }),
+
+ delete: jest.fn().mockImplementation((params: any) => {
+ let found = false;
+
+ fakeEntities.forEach((entity, index) => {
+ if (entity.uuid === params?.where?.uuid) {
+ found = true;
+ fakeEntities.splice(index, 1);
+ }
+ });
+
+ if (!found) {
+ throw new Error();
+ }
+ }),
+ },
+};
+
+describe('PrismaRepository', () => {
+ let fakeRepository: FakePrismaRepository;
+ let prisma: FakePrismaService;
+
+ beforeAll(async () => {
+ const module: TestingModule = await Test.createTestingModule({
+ providers: [
+ FakePrismaRepository,
+ {
+ provide: PrismaService,
+ useValue: mockPrismaService,
+ },
+ ],
+ }).compile();
+
+ fakeRepository = module.get(FakePrismaRepository);
+ prisma = module.get(PrismaService) as FakePrismaService;
+ });
+
+ it('should be defined', () => {
+ expect(fakeRepository).toBeDefined();
+ expect(prisma).toBeDefined();
+ });
+
+ describe('findAll', () => {
+ it('should return an array of entities', async () => {
+ jest.spyOn(prisma.fake, 'findMany');
+ jest.spyOn(prisma.fake, 'count');
+ jest.spyOn(prisma, '$transaction');
+
+ const entities = await fakeRepository.findAll();
+ expect(entities).toStrictEqual({
+ data: fakeEntities,
+ total: fakeEntities.length,
+ });
+ });
+
+ it('should return an array containing only one entity', async () => {
+ const entities = await fakeRepository.findAll(1, 10, { limit: 1 });
+
+ expect(prisma.fake.findMany).toHaveBeenCalledWith({
+ skip: 0,
+ take: 10,
+ where: { limit: 1 },
+ });
+ expect(entities).toEqual({
+ data: [fakeEntityCreated],
+ total: 1,
+ });
+ });
+ });
+
+ describe('create', () => {
+ it('should create an entity', async () => {
+ jest.spyOn(prisma.fake, 'create');
+
+ const newEntity = await fakeRepository.create(fakeEntityToCreate);
+ expect(newEntity).toBe(fakeEntityCreated);
+ expect(prisma.fake.create).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('findOne', () => {
+ it('should find an entity by uuid', async () => {
+ const entity = await fakeRepository.findOneByUuid(fakeEntities[0].uuid);
+ expect(entity).toBe(fakeEntities[0]);
+ });
+
+ it('should throw a DatabaseException if uuid is not found', async () => {
+ await expect(
+ fakeRepository.findOneByUuid('wrong-uuid'),
+ ).rejects.toBeInstanceOf(DatabaseException);
+ });
+ });
+
+ describe('update', () => {
+ it('should update an entity', async () => {
+ const newName = 'random-name';
+
+ await fakeRepository.update(fakeEntities[0].uuid, {
+ name: newName,
+ });
+ expect(fakeEntities[0].name).toBe(newName);
+ });
+
+ it("should throw an exception if an entity doesn't exist", async () => {
+ await expect(
+ fakeRepository.update('fake-uuid', { name: 'error' }),
+ ).rejects.toBeInstanceOf(DatabaseException);
+ });
+ });
+
+ describe('delete', () => {
+ it('should delete an entity', async () => {
+ const savedUuid = fakeEntities[0].uuid;
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const res = await fakeRepository.delete(savedUuid);
+
+ const deletedEntity = fakeEntities.find(
+ (entity) => entity.uuid === savedUuid,
+ );
+ expect(deletedEntity).toBeUndefined();
+ });
+
+ it("should throw an exception if an entity doesn't exist", async () => {
+ await expect(fakeRepository.delete('fake-uuid')).rejects.toBeInstanceOf(
+ DatabaseException,
+ );
+ });
+ });
+
+ describe('findOne', () => {
+ it('should find one entity', async () => {
+ const entity = await fakeRepository.findOne({
+ name: fakeEntities[0].name,
+ });
+
+ expect(entity.name).toBe(fakeEntities[0].name);
+ });
+ });
+});
diff --git a/src/utils/pipes/rpc.validation-pipe.ts b/src/utils/pipes/rpc.validation-pipe.ts
new file mode 100644
index 0000000..f2b8c19
--- /dev/null
+++ b/src/utils/pipes/rpc.validation-pipe.ts
@@ -0,0 +1,14 @@
+import { Injectable, ValidationPipe } from '@nestjs/common';
+import { RpcException } from '@nestjs/microservices';
+
+@Injectable()
+export class RpcValidationPipe extends ValidationPipe {
+ createExceptionFactory() {
+ return (validationErrors = []) => {
+ return new RpcException({
+ code: 3,
+ message: this.flattenValidationErrors(validationErrors),
+ });
+ };
+ }
+}
diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts
deleted file mode 100644
index 50cda62..0000000
--- a/test/app.e2e-spec.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Test, TestingModule } from '@nestjs/testing';
-import { INestApplication } from '@nestjs/common';
-import * as request from 'supertest';
-import { AppModule } from './../src/app.module';
-
-describe('AppController (e2e)', () => {
- let app: INestApplication;
-
- beforeEach(async () => {
- const moduleFixture: TestingModule = await Test.createTestingModule({
- imports: [AppModule],
- }).compile();
-
- app = moduleFixture.createNestApplication();
- await app.init();
- });
-
- it('/ (GET)', () => {
- return request(app.getHttpServer())
- .get('/')
- .expect(200)
- .expect('Hello World!');
- });
-});