diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..dedc710c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,18 @@ +{ + "dockerComposeFile": "../docker-compose.yml", + "service": "api", + "workspaceFolder": "/workspace", + "forwardPorts": [8080], + "postCreateCommand": "yarn install --frozen-lockfile", + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "ms-azuretools.vscode-docker", + "mutantdino.resourcemonitor", + "eamodio.gitlens", + "mongodb.mongodb-vscode" + ] + } + } +} diff --git a/.dockerignore b/.dockerignore index 13f2eafe..0ef146e8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,5 +5,3 @@ !nest-cli.json !tsconfig.json !tsconfig.build.json -!docker/entrypoint.local.sh -!docker/wait-for.sh diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 71c8904e..9ffdc494 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -80,6 +80,7 @@ jobs: run: yarn install --frozen-lockfile - name: Tests run: | + yarn queue:init yarn test:ci --shard=${{ matrix.shard }}/${{ strategy.job-total }} env: DB_HOST: database @@ -104,7 +105,5 @@ jobs: - name: Build and push docker image uses: docker/build-push-action@v5 with: - context: . - file: ./docker/Dockerfile push: false tags: nestjs:latest diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index f29b7371..64ef6fd0 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -51,12 +51,11 @@ jobs: container: node:lts timeout-minutes: 10 services: - database: + db: image: mongo:6.0.15-jammy env: MONGO_INITDB_ROOT_USERNAME: acid MONGO_INITDB_ROOT_PASSWORD: password - MONGO_INITDB_DATABASE: develop options: >- --health-cmd "echo 'dgit stb.runCommand("ping").ok' | mongosh --quiet" --health-interval 10s @@ -85,17 +84,12 @@ jobs: run: yarn install --frozen-lockfile - name: Tests run: | + yarn queue:init yarn test:ci --shard=${{ matrix.shard }}/${{ strategy.job-total }} env: - DB_HOST: database - DB_USERNAME: acid - DB_PASSWORD: password - DB_DATABASE: develop - QUEUE_HOST: queue - QUEUE_USERNAME: acid - QUEUE_PASSWORD: password + DB_HOST: db AWS_ENDPOINT: http://aws:4566 - AWS_SQS_URL: http://sqs.eu-west-1.aws.localstack.cloud:4566/000000000000/localstack-queue + AWS_SQS_URL: http://sqs.eu-west-1.aws:4566/000000000000/localstack-queue docker-build: needs: skip-draft @@ -112,7 +106,5 @@ jobs: - name: Build and push docker image uses: docker/build-push-action@v5 with: - context: . - file: ./docker/Dockerfile push: false tags: nestjs:latest diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 31c7b204..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,24 +0,0 @@ -services: - - postgres:14.12-alpine - -variables: - POSTGRES_DB: develop - POSTGRES_USER: acid - POSTGRES_PASSWORD: password - -stages: - - test - -test: - stage: test - image: node:lts-alpine - variables: - DB_HOST: postgres - script: - - yarn install --frozen-lockfile - - yarn build - - yarn typecheck - - yarn lint:ci - - yarn format:ci - - yarn migration:run - - yarn test:ci diff --git a/.nvmrc b/.nvmrc index 805efa9f..b8e593f5 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.14.0 \ No newline at end of file +20.15.1 diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 132c6603..00000000 --- a/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -src/database/migrations -docker-compose.yml \ No newline at end of file diff --git a/.swcrc b/.swcrc index 3bb69b3f..170c7605 100644 --- a/.swcrc +++ b/.swcrc @@ -1,18 +1,18 @@ { - "jsc":{ - "parser":{ - "syntax":"typescript", - "tsx":false, - "decorators":true - }, - "transform":{ - "legacyDecorator":true, - "decoratorMetadata":true - }, - "target":"es2018" - }, - "module":{ - "type":"commonjs", - "noInterop":true - } + "jsc": { + "parser": { + "syntax": "typescript", + "tsx": false, + "decorators": true + }, + "transform": { + "legacyDecorator": true, + "decoratorMetadata": true + }, + "target": "es2018" + }, + "module": { + "type": "commonjs", + "noInterop": true + } } diff --git a/docker/Dockerfile b/Dockerfile similarity index 100% rename from docker/Dockerfile rename to Dockerfile diff --git a/README.md b/README.md index ab7bb56d..b0f1e9e8 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,16 @@ by event organizers and approved or rejected. ## 💻 I'm a dev, how do I get started? +### Run using [dev containers](https://containers.dev/) + +``` +git clone git@github.com:acidtango/boilerplate-nestjs.git +``` + +Open the devcontainer using your favourite IDE and you are ready to go!! + +### Run in local environment + Prerequisites: - [Node.js](https://nodejs.org/es/download) @@ -19,11 +29,10 @@ Prerequisites: Now: ```bash -git clone git@github.com:DanielRamosAcosta/codetalk.git +git clone git@github.com:acidtango/boilerplate-nestjs.git cd codetalk yarn install -docker-compose up -d database # starts DDBB -yarn start:dev # opens the server in development mode +docker compose up -d db aws # starts DDBB & queue ``` You are now good ready to go!! 👯 @@ -31,7 +40,6 @@ You are now good ready to go!! 👯 ### `yarn` scripts - `build`: Compiles the project for later using `yarn start` -- `initialize:db`: Initializes a local database - `start`: Opens the server by compiling the sources on the fly - `start:dev`: Opens the server compiling the project on the fly in watch mode - `start:prod`: Opens the server in production mode using the compiled sources @@ -49,28 +57,28 @@ You are now good ready to go!! 👯 ### Docker -We use Docker as a utility tool, mainly for running MongoDB. In the `docker-compose.yml` you have two wservuces services: +We use Docker as a utility tool, mainly for running MongoDB and LocalStack. In the `docker-compose.yml` you have two services: -- `codetalk`: The API if you want to open it as a docker container -- `database`: A mongodb database that we use for starting the API in development mode and running the integration tests locally. +- `api`: The API if you want to open it as a docker container +- `db`: A mongodb database that we use for starting the API in development mode and running the integration tests locally. +- `aws`: This is LocalStack, which is an environment simulation of AWS. We use it for the SQS service. ### Project management - [Trello board](https://example.org/) -- [Github repo](https://github.com/DanielRamosAcosta/codetalk) -- [Github Actions](https://github.com/DanielRamosAcosta/codetalk/actions) +- [Github repo](https://github.com/acidtango/boilerplate-nestjs) +- [Github Actions](https://github.com/acidtango/boilerplate-nestjs/actions) - [Figma](https://example.org/) - [Firebase console](https://example.org/) ## 🛠 Which technologies are you using? -- Node +- [Node](https://nodejs.org/en) - [Nestjs](https://nestjs.com/) - - Validations with [Class Validator & Class Transformer](https://docs.nestjs.com/techniques/validation) - - [OpenAPI docs](https://docs.nestjs.com/openapi/introduction) - - Mainly used as dependency injection container -- TypeScript -- [Stripe](https://stripe.com/es) + - Validations with [Class Validator & Class Transformer](https://docs.nestjs.com/techniques/validation) + - [OpenAPI docs](https://docs.nestjs.com/openapi/introduction) + - Mainly used as dependency injection container +- [TypeScript](https://github.com/AgileCraftsmanshipCanarias/kata-setup-typescript) ## 🏘 How is the code organized? @@ -126,8 +134,8 @@ You can read more about dependency inversion [here](https://en.wikipedia.org/wik - Do not import third-parties or side effect methods into the domain/use cases layer - Instead, create an interface that represent that interaction - Create two implementations of that interface: - - A "real" implementation (calling TypeORM, Stripe, Fetch HTTP API Call...). - - A "fake" implementation just for testing purposes. + - A "real" implementation (calling TypeORM, Stripe, Fetch HTTP API Call...). + - A "fake" implementation just for testing purposes. ### Dependency injection container @@ -140,16 +148,16 @@ The interfaces are a compile-time thing of Typescript, so when we need to inject ```typescript // TalkRepository.interface.ts export interface TalkRepository { - save(talk: Talk): Promise - findBy(talkId: TalkId): Promise + save(talk: Talk): Promise + findBy(talkId: TalkId): Promise } ``` ```typescript // Token.ts export enum Token { - TALK_REPOSITORY = 'TALK_REPOSITORY', - // ... + TALK_REPOSITORY = 'TALK_REPOSITORY', + // ... } ``` @@ -168,8 +176,8 @@ Later on, we need to wire up these dependencies from a Nestjs module: @Global() @Module({ - providers: [{ provide: Token.TALK_REPOSITORY, useClass: TalkRepositoryMongo }], - exports: [Token.TALK_REPOSITORY], + providers: [{ provide: Token.TALK_REPOSITORY, useClass: TalkRepositoryMongo }], + exports: [Token.TALK_REPOSITORY], }) export class TalkRepositoryModule {} ``` @@ -185,16 +193,22 @@ In general, this inheritance does not have any logic. It's just like a explanato Examples: ```typescript -class ReservationLister extends UseCase { /* ... */} -class Reservation extends AggregateRoot { /* ... */ } -class ReservationTitle extends ValueObject { /* ... */ } +class ReservationLister extends UseCase { + /* ... */ +} +class Reservation extends AggregateRoot { + /* ... */ +} +class ReservationTitle extends ValueObject { + /* ... */ +} ``` ## ✅ Tests - We are using [Jest](https://jestjs.io/) and [tepper](https://github.com/DanielRamosAcosta/tepper) for acceptance tests. - Unitary Tests are paired with the element that tests. For example `Talk.spec.ts` is next to `Talk.ts`. -- Acceptance tests lives under the `tests` directory. These tests crosses the framework layers and we interact with the API as a black box. These are the main tests that we use for TDD by performing outside-in. +- E2E tests lives under the `tests` directory. These tests crosses the framework layers and we interact with the API as a black box. These are the main tests that we use for TDD by performing outside-in. We write them in an acceptance test fashion. ### CI/CD @@ -202,7 +216,3 @@ class ReservationTitle extends ValueObject { /* ... */ } - We run the precommit script before each commit using Husky. - The CI runs for both acceptance/unitary and integration tests with a real database. - After all tests passed, then the API is re-deployed - -## 📲 Contact - -The project was mainly developed by [Alberto González](https://github.com/AlberTJ97) and [Daniel Ramos](https://github.com/DanielRamosAcosta) from [Acid Tango](https://acidtango.com/) with ❤️ and 💪 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 1bfd7417..8765d4ca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: "3.7" +version: '3.7' services: db: image: mongo:6.0.15-jammy @@ -6,38 +6,29 @@ services: MONGO_INITDB_ROOT_USERNAME: acid MONGO_INITDB_ROOT_PASSWORD: password ports: - - "27017:27017" + - 27017:27017 + volumes: + - db-data:/data/db + aws: image: localstack/localstack environment: SERVICES: sqs AWS_DEFAULT_REGION: eu-west-1 ports: - - '4566:4566' + - 4566:4566 + volumes: + - ./init-aws.sh:/etc/localstack/init/ready.d/init-aws.sh + api: - build: - context: . - dockerfile: ./docker/Dockerfile.dev + image: mcr.microsoft.com/devcontainers/typescript-node:20 + command: sleep infinity environment: DB_HOST: db - DB_PORT: 5432 - DB_USERNAME: acid - DB_PASSWORD: password - DB_DATABASE: develop - DEPLOY_ENV: dev - AWS_ENDPOINT: aws - - working_dir: /home/node/ + AWS_ENDPOINT: http://aws:4566 + AWS_SQS_URL: http://sqs.eu-west-1.aws:4566/000000000000/localstack-queue volumes: - - "./src:/home/node/src" - - "./test:/home/node/test" - entrypoint: - - /bin/sh - - -c - - | - ./entrypoint.sh - ports: - - "8080:8080" - networks: - - default + - .:/workspace:cached +volumes: + db-data: diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev deleted file mode 100644 index a2673a93..00000000 --- a/docker/Dockerfile.dev +++ /dev/null @@ -1,18 +0,0 @@ -FROM node:lts-alpine -ARG SSH_KEY=${SSH_KEY} - -RUN apk add build-base git - -WORKDIR /home/node -ADD ./package.json ./package.json -ADD ./yarn.lock ./yarn.lock -RUN yarn install --frozen-lockfile - -COPY . . - -COPY ./docker/entrypoint.local.sh ./entrypoint.sh -COPY ./docker/wait-for.sh ./wait-for.sh - -EXPOSE 8080 - -ENTRYPOINT [ "./entrypoint.sh" ] diff --git a/docker/entrypoint.local.sh b/docker/entrypoint.local.sh deleted file mode 100755 index 71007948..00000000 --- a/docker/entrypoint.local.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -echo "*** WAITING FOR DB TO BE READY ***" -PORT="${DB_PORT:-5432}" -./wait-for.sh -t 0 $DB_HOST:$PORT - -exitCode=$? -if [ $exitCode -ne 0 ] ; then - exit $exitCode -fi - -echo "*** RUNNING MIGRATIONS ***" -yarn migration:run - -exitCode=$? -if [ $exitCode -ne 0 ] ; then - exit $exitCode -fi - -echo "*** STARTING SERVICE ***" -yarn start:dev diff --git a/docker/wait-for.sh b/docker/wait-for.sh deleted file mode 100755 index 3107adf2..00000000 --- a/docker/wait-for.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/sh - -TIMEOUT=15 -QUIET=0 - -echoerr() { - if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi -} - -usage() { - exitcode="$1" - cat << USAGE >&2 -Usage: - $cmdname host:port [-t timeout] [-- command args] - -q | --quiet Do not output any status messages - -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout - -- COMMAND ARGS Execute command with args after the test finishes -USAGE - exit "$exitcode" -} - -wait_for() { - nc -z "$HOST" "$PORT" > /dev/null 2>&1 - result=$? - if [ $result -eq 0 ] ; then - if [ $# -gt 0 ] ; then - exec "$@" - fi - exit 0 - fi - sleep 1 -} - -main() { - if [ $TIMEOUT -eq 0 ] ; then - while true ; do - wait_for "$@" - done - else - for i in `seq $TIMEOUT` ; do - wait_for "$@" - done - fi - echo "Operation timed out" >&2 - exit 1 -} - -while [ $# -gt 0 ] -do - case "$1" in - *:* ) - HOST=$(printf "%s\n" "$1"| cut -d : -f 1) - PORT=$(printf "%s\n" "$1"| cut -d : -f 2) - shift 1 - ;; - -q | --quiet) - QUIET=1 - shift 1 - ;; - -t) - TIMEOUT="$2" - if [ "$TIMEOUT" = "" ]; then break; fi - shift 2 - ;; - --timeout=*) - TIMEOUT="${1#*=}" - shift 1 - ;; - --) - shift - break - ;; - --help) - usage 0 - ;; - *) - echoerr "Unknown argument: $1" - usage 1 - ;; - esac -done - -if [ "$HOST" = "" -o "$PORT" = "" ]; then - echoerr "Error: you need to provide a host and port to test." - usage 2 -fi - -main "$@" \ No newline at end of file diff --git a/docs/code-review-guidelines.md b/docs/code-review-guidelines.md deleted file mode 100644 index e8eddbc5..00000000 --- a/docs/code-review-guidelines.md +++ /dev/null @@ -1,73 +0,0 @@ -# Code Review Guidelines - -These guidelines are extracted from the [Gitlab's Code Review Guidelines](https://docs.gitlab.com/ee/development/code_review.html). - -- [Best practices](#best-practices) -- [Having your code reviewed](#having-your-code-reviewed) -- [Reviewing code](#reviewing-code) - -## Best practices - -Be kind. - -Accept that many programming decisions are opinions. Discuss tradeoffs, which you prefer, and reach a resolution quickly. - -Ask questions; don’t make demands. (“What do you think about naming this :user_id?”) - -Ask for clarification. (“I didn’t understand. Can you clarify?”) - -Avoid selective ownership of code. (“mine”, “not mine”, “yours”) - -Avoid using terms that could be seen as referring to personal traits. (“dumb”, “stupid”). Assume everyone is intelligent and well-meaning. - -Be explicit. Remember people don’t always understand your intentions online. - -Be humble. (“I’m not sure - let’s look it up.”) - -Don’t use hyperbole. (“always”, “never”, “endlessly”, “nothing”) - -Be careful about the use of sarcasm. Everything we do is public; what seems like good-natured ribbing to you and a long-time colleague might come off as mean and unwelcoming to a person new to the project. - -Consider one-on-one chats or video calls if there are too many “I didn’t understand” or “Alternative solution:” comments. Post a follow-up comment summarizing one-on-one discussion. - -If you ask a question to a specific person, always start the comment by mentioning them; this will ensure they see it if their notification level is set to “mentioned” and other people will understand they don’t have to respond. - -## Having your code reviewed - -The first reviewer of your code is you. Before you perform that first push of your shiny new branch, read through the entire diff. Does it make sense? Did you include something unrelated to the overall purpose of the changes? Did you forget to remove any debugging code? - -Be grateful for the reviewer’s suggestions. (“Good call. I’ll make that change.”) - -Don’t take it personally. The review is of the code, not of you. - -Explain why the code exists. (“It’s like that because of these reasons. Would it be more clear if I rename this class/file/method/variable?”) - -Don't be defensive. There sure is an explanation why you made a mistake, but we're here to solve it, not to blame external factors. Too many explanations on the 'why did this happen?' might make the 'how are we going to solve it?' less readable. - -Seek to understand the reviewer’s perspective. - -Try to respond to every comment. - -Let the reviewer select the “Resolve discussion” buttons. - -Push commits based on earlier rounds of feedback as isolated commits to the branch. Reviewers should be able to read individual updates based on their earlier feedback. - -## Reviewing code - -Understand why the change is necessary (fixes a bug, improves the user experience, refactors the existing code). Then: - -Try to be thorough in your reviews to reduce the number of iterations. - -Communicate which ideas you feel strongly about and those you don’t. - -Identify ways to simplify the code while still solving the problem. - -Offer alternative implementations, but assume the author already considered them. (“What do you think about using a custom validator here?”) - -Seek to understand the author’s perspective. - -If you don’t understand a piece of code, say so. There’s a good chance someone else would be confused by it as well. - -After a round of line notes, it can be helpful to post a summary note such as “LGTM :thumbsup:”, or “Just a couple things to address.” - -Avoid accepting a pull request before the job succeeds. \ No newline at end of file diff --git a/docs/conventions.md b/docs/conventions.md deleted file mode 100644 index 38fabf64..00000000 --- a/docs/conventions.md +++ /dev/null @@ -1,83 +0,0 @@ -# Project Conventions - -- [Architecture](#architecture) -- [Name Conventions](#name-conventions) - - [Files](#files) - - [Tests](#tests) - - [Enums](#enums) - - [Constants](#constants) - - [Timestamps](#timestamps) - - [Database](#database) - -## Architecture - -We are applying [Domain-Driven Design](https://martinfowler.com/bliki/DomainDrivenDesign.html) patterns in the project. Our domain directories can be found in the `src/application` folder have the following folders: -- domain -- infrastructure -- use-cases - -We are using plural nouns for domain directories and subfolders (apart from the domain and infrastructure ones). For instance, we may have a `books` domain directory that could look like: - -- books - - domain - - types - - infrastructure - - entities - - repositories - - use-cases - -Unit tests are placed next to their corresponding files but end-to-end tests are placed in the `test` directory. - -## Name Conventions - -### Files - -We are using `PascalCase` for name files and we are separating the file extension with a dot. Example: - -``` -FileName.ts -``` - -### Tests - -For unit tests we are applying the same file naming rules with the `spec` type before the extension. Example: -``` -FunctionName.spec.ts -``` - -For end-to-end tests, the type we are using is `e2e-spec`. Example: -``` -endpoint-name.e2e-spec.ts -``` - -### Enums - -We are using `PascalCase` and singular nouns for enum names and capitalised words for enum options. Example: -```ts -const enum City { - MADRID = 'MADRID', - TENERIFE = 'TENERIFE' -} -``` - -### Constants - -We are using capitalised words for constants. Example: -```ts -const COMPANY = 1 -``` - -### Timestamps - -We are using [Unix time](https://www.unixtimestamp.com/) whenever is possible to deal with time zones. - -### Database - -We are using `snake_case` for database tables and columns. - -### Currencies - -We are using integer numbers for currencies, with the last two digits representing two decimal places (please note that JavaScript may not work as expected when operating with large numbers). Example: -``` -5.56€ would be represented as 556 -``` diff --git a/docs/ide-settings.md b/docs/ide-settings.md deleted file mode 100644 index 260074e9..00000000 --- a/docs/ide-settings.md +++ /dev/null @@ -1,52 +0,0 @@ -# IDE settings - -- [Visual Code](#visual-code) - -## Visual Code - -### settings.json - -- typescript.tsdk: Could select plugin or workspace SDK. Could be useful to avoid version mismatch while developing. - -```json -{ - "typescript.preferences.importModuleSpecifier": "relative", - "typescript.tsdk": "node_modules/typescript/lib" -} -``` - -### launch.json - -```json -{ - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Jest", - "program": "${workspaceFolder}/node_modules/jest/bin/jest", - "args": ["--inspect-brk", "--coverage=false", "--watch", "${relativeFile}"], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" - }, - { - "type": "node", - "request": "launch", - "name": "Jest - Coverage", - "program": "${workspaceFolder}/node_modules/jest/bin/jest", - "args": ["--inspect-brk", "--coverage", "--watch", "${relativeFile}"], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" - }, - { - "type": "node", - "request": "launch", - "name": "Jest all", - "program": "${workspaceFolder}/node_modules/jest/bin/jest", - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" - } - ], - "compounds": [] -} -``` diff --git a/docs/workflow.md b/docs/workflow.md deleted file mode 100644 index 0fd94157..00000000 --- a/docs/workflow.md +++ /dev/null @@ -1,106 +0,0 @@ -# Project workflow - -- [🛠 Local Setup](#-local-setup) -- [🩺 Health Check](#-health-check) -- [🔑 Environment Variables](#-environment-variables) -- [📊 Database](#-database) -- [📜 Swagger](#-swagger) -- [📘 Documentation](#-documentation) - -## 🛠 Local Setup - -To set up a local development environment, we'll only need to have [Docker](https://www.docker.com/) installed and use the `docker-compose.yml` file. Here are some useful commands: - -```sh -# Build services. -docker-compose build - -# Create and start containers. -docker-compose up - -# Stop and remove started containers, remove unused volumes and start new containers. -docker-compose down && docker volume prune -f && docker-compose up --build - -# Remove all unused containers, networks, images (both dangling and unreferenced) and volumes. -docker system prune --all --volumes -``` - -### Prettier and ESLint - -We are using `Prettier` for code formatting and `ESLint` as our code linter with the Airbnb's rules for TypeScript. Configure your editor so it formats your code automatically or use the following commands: - -```sh -# Format the project code. -yarn format - -#Run the linter. -yarn lint - -# Fix linter errors automatically. -yarn lint:fix -``` - -### Run checks before a commit - -Remember to always execute the following command before any commit to make sure that the format of your files are correct and the linter rules are applied correctly: - -```sh -yarn run-checks -``` - -## 🩺 Health Check - -The health endpoint `GET /health` should be maintained for monitoring and alerting purposes. This endpoint returns an OK response if the API is running correctly and lists the status of dependencies. For instance, the health endpoint may need to check that: - -- database connection is established -- indispensable microservices are reachable -- external APIs are responding - -## 🔑 Environment Variables - -Our environment variables are set in the `.env` file, which is present in the `.gitignore` file so it is never pushed to the repositories, you'll need to ask for the credentials to other team members. - -There are two rules to consider when working with environment variables: - -- They should not be used in the code directly. don't use the `process.env.ENV_VARIABLE` hack. Instead, use the configuration file: `src/config.ts`. -- Remember to update the `.env.example` file whenever you add or remove any environment variables so other developers can keep track of the required variables. - -## 📊 Database - -We are using [PostgreSQL](https://www.postgresql.org/) as our main database and [TypeORM](https://typeorm.io/) as our ORM. - -### Migrations - -Configuration file `database/ormconfig.ts` is configured to use TypeORM's migrations. - -To generate a migration based on the entities modifications: - -```sh -yarn migration:generate -``` - -To run the migrations: - -```sh -yarn migration:run -``` - -To revert the last migration: - -```sh -yarn migration:revert -``` - -## 📜 Swagger - -NestJS includes a Swagger module to programatically generate the API Swagger documentation following the [OpenApi Specification](https://swagger.io/specification/). For detailed usage instructions check the [NestJS documentation](https://docs.nestjs.com/recipes/swagger). We are using the plugin provided by NestJS to generate the documentation automatically with `nest-cli.json`. - -There is a document builder and a `SwaggerModule` in file `main.ts`. The `SwaggerModule.setup()` method sets the documentation to endpoint `/docs`. - -- To visit the live Swagger doc: [http://localhost:8080/docs](http://localhost:8080/docs). - -- To download the Swagger documentation as a JSON file: [http://localhost:8080/docs-json](http://localhost:8080/docs-json) - -## 📘 Documentation - -The `docs` folder contains the project's documentation. Please remember to keep it updated and add new files to the README.md index. diff --git a/init-aws.sh b/init-aws.sh new file mode 100755 index 00000000..9a475f32 --- /dev/null +++ b/init-aws.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +awslocal sqs create-queue --queue-name localstack-queue diff --git a/package.json b/package.json index 0a1ea493..efcb41da 100644 --- a/package.json +++ b/package.json @@ -7,18 +7,16 @@ "scripts": { "prebuild": "rimraf dist", "build": "nest build", - "containers:restart": "docker-compose down && docker volume prune -f && docker-compose up -d db", - "initialize:db": "yarn containers:restart && sleep 4 && yarn migration:run", - "format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\" \"**/*.json\" \"**/*.mjs\"", - "format:fix": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"**/*.json\" \"**/*.mjs\"", + "format:check": "prettier --check .", + "format:fix": "prettier --write .", "format:ci": "yarn format:check", "start": "rimraf dist && nest start", "start:dev": "rimraf dist && nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/src/main", "typecheck": "tsc", - "lint:check": "eslint --max-warnings 0 \"{src,apps,libs,test}/**/*.ts\"", - "lint:fix": "eslint --fix \"{src,apps,libs,test}/**/*.ts\"", + "lint:check": "eslint --max-warnings 0 \"{src,test}/**/*.ts\"", + "lint:fix": "eslint --fix \"{src,test}/**/*.ts\"", "lint:ci": "yarn lint:check", "test": "yarn test:unitary && yarn test:e2e:memory && yarn test:integration && yarn test:e2e:db", "test:ci": "yarn test:coverage", @@ -29,11 +27,12 @@ "test:integration:third-party": "jest -c test/config/integration-third-party.json", "test:e2e:memory": "jest --config ./test/config/e2e-memory.json", "test:e2e:db": "jest --config ./test/config/e2e-db.json", + "queue:init": "ts-node src/shared/infrastructure/events/EventBus/EventBusSQSInit.ts", "precommit": "yarn typecheck && yarn format:fix && yarn lint:fix", "run-checks": "yarn typecheck && yarn format:check && yarn lint:check" }, "engines": { - "node": "^20.11.0", + "node": "^20.15.1", "yarn": "~1.22.19" }, "dependencies": { diff --git a/src/shared/infrastructure/config.ts b/src/shared/infrastructure/config.ts index 9f674595..0f24a255 100644 --- a/src/shared/infrastructure/config.ts +++ b/src/shared/infrastructure/config.ts @@ -21,7 +21,6 @@ export const config = { 'http://sqs.eu-west-1.localhost.localstack.cloud:4566/000000000000/localstack-queue', }, }, - deployEnvironment: process.env.DEPLOY_ENV || 'dev', forceEnableORMRepositories: process.env.ENABLE_TEST_ORM_REPOSITORIES === 'true', listeningPort: parseInt(process.env.APP_PORT || '8080', 10), apiPrefix: process.env.API_PREFIX || 'api/', diff --git a/src/shared/infrastructure/events/EventBus/EventBusSQS.integration-spec.ts b/src/shared/infrastructure/events/EventBus/EventBusSQS.integration-spec.ts index 35308ff9..88751cfe 100644 --- a/src/shared/infrastructure/events/EventBus/EventBusSQS.integration-spec.ts +++ b/src/shared/infrastructure/events/EventBus/EventBusSQS.integration-spec.ts @@ -13,14 +13,9 @@ describe('EventBusSQS', () => { domainEventSubscriber = new DomainEventSubscriberFake() const domainEventMapper = new DomainEventMapperFake(domainEventSubscriber) eventBus = new EventBusSQS(domainEventMapper) - await eventBus.createQueue() await eventBus.onModuleInit() }) - afterEach(async () => { - await eventBus.destroyQueue() - }) - afterAll(async () => { await eventBus.onModuleDestroy() }) diff --git a/src/shared/infrastructure/events/EventBus/EventBusSQS.ts b/src/shared/infrastructure/events/EventBus/EventBusSQS.ts index 60998272..ae82b7f5 100644 --- a/src/shared/infrastructure/events/EventBus/EventBusSQS.ts +++ b/src/shared/infrastructure/events/EventBus/EventBusSQS.ts @@ -1,9 +1,4 @@ -import { - CreateQueueCommand, - PurgeQueueCommand, - SendMessageCommand, - SQSClient, -} from '@aws-sdk/client-sqs' +import { CreateQueueCommand, SendMessageCommand, SQSClient } from '@aws-sdk/client-sqs' import { Inject, Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common' import { Consumer } from 'sqs-consumer' import { DomainEvent } from '../../../domain/events/DomainEvent' @@ -82,15 +77,9 @@ export class EventBusSQS implements EventBus, OnModuleInit, OnModuleDestroy { throw new Error('Could not create queue') } - console.log('Queue URL', QueueUrl) - this.queueUrl = QueueUrl } - async destroyQueue() { - await this.client.send(new PurgeQueueCommand({ QueueUrl: this.queueUrl })) - } - async publish(events: DomainEvent[]): Promise { for (const event of events) { const command = new SendMessageCommand({ diff --git a/src/shared/infrastructure/events/EventBus/EventBusSQSInit.ts b/src/shared/infrastructure/events/EventBus/EventBusSQSInit.ts new file mode 100644 index 00000000..33d8579d --- /dev/null +++ b/src/shared/infrastructure/events/EventBus/EventBusSQSInit.ts @@ -0,0 +1,5 @@ +import { EventBusSQS } from './EventBusSQS' + +// eslint-disable-next-line +const eventBus = new EventBusSQS(null as any) +eventBus.createQueue()