diff --git a/.ebextensions/00_rds_env.config b/.ebextensions/00_rds_env.config new file mode 100644 index 0000000..3fbcac5 --- /dev/null +++ b/.ebextensions/00_rds_env.config @@ -0,0 +1,18 @@ +files: + "/tmp/set_env.sh": + mode: "000755" + owner: root + group: root + content: | + #!/bin/bash + DB_USERNAME="$(/opt/elasticbeanstalk/bin/get-config environment -k RDS_USERNAME)" + DB_PASSWORD="$(/opt/elasticbeanstalk/bin/get-config environment -k RDS_PASSWORD)" + DB_HOST="$(/opt/elasticbeanstalk/bin/get-config environment -k RDS_HOSTNAME)" + DB_NAME="$(/opt/elasticbeanstalk/bin/get-config environment -k RDS_DB_NAME)" + + export DATABASE_URL="postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:5432/${DB_NAME}" + echo "DATABASE_URL=${DATABASE_URL}" >> /opt/elasticbeanstalk/deployment/env.list + +commands: + 00-set-env: + command: /tmp/set_env.sh diff --git a/.ebextensions/01_app_release.config b/.ebextensions/01_app_release.config new file mode 100644 index 0000000..6c93459 --- /dev/null +++ b/.ebextensions/01_app_release.config @@ -0,0 +1,19 @@ +files: + "/opt/elasticbeanstalk/hooks/appdeploy/post/01_app_release.sh": + mode: "000755" + owner: root + group: root + content: | + #!/usr/bin/env bash + if [ -f /tmp/leader_only ]; then + echo "Running Migrations" + container_id=`docker ps -q --no-trunc --filter label="com.amazonaws.ecs.container-name=" | head -n 1` + docker inspect $container_id + docker exec $container_id /entrypoint.sh npx prisma migrate deploy + fi +container_commands: + 01_touch_the_leader: + command: | + #!/usr/bin/env bash + touch /tmp/leader_only + leader_only: true diff --git a/.ebextensions/log.config b/.ebextensions/log.config new file mode 100644 index 0000000..7e89940 --- /dev/null +++ b/.ebextensions/log.config @@ -0,0 +1,17 @@ +files: + "/etc/awslogs/config/beanstalklogs.conf": + mode: "000644" + user: root + group: root + content: | + [/var/log/eb-docker/containers/eb-current-app/stdouterr.log] + log_group_name=/aws/elasticbeanstalk/EB-ENV-NAME/var/log/eb-docker/containers/eb-current-app/stdouterr.log + log_stream_name={instance_id} + file=/var/log/eb-docker/containers/eb-current-app/*stdouterr.log + file_fingerprint_lines=1-20 + +commands: + 01_remove_eb_stream_config: + command: 'rm /etc/awslogs/config/beanstalklogs.conf.bak' + 02_restart_log_agent: + command: 'service awslogs restart' diff --git a/.gitignore b/.gitignore index 0cc588d..c186f90 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,14 @@ lerna-debug.log* .env .env.dist .env.test +Dockerrun.aws.json +etc/infra/aws/.terraform +etc/infra/aws/Dockerrun.aws.json +etc/infra/aws/Dockerrun.aws.zip +Dockerrun.aws.zip +*.tfvars + +# Elastic Beanstalk Files +.elasticbeanstalk/* +!.elasticbeanstalk/*.cfg.yml +!.elasticbeanstalk/*.global.yml diff --git a/Dockerfile b/Dockerfile index 1aa929c..f694ff7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,3 +27,19 @@ COPY --chown=carbonable:carbonable --from=builder /srv/www/prisma ./prisma EXPOSE 8080 CMD ["npm", "run", "start:prod"] + +FROM node:20-alpine3.17 as aws_runtime + +WORKDIR /srv/www + +COPY --from=builder /srv/www/package.json ./package.json +COPY --from=builder /srv/www/pnpm-lock.yaml ./pnpm-lock.yaml +COPY --from=builder /srv/www/node_modules ./node_modules +COPY --from=builder /srv/www/dist ./dist +COPY --from=builder /srv/www/src/schemas ./src/schemas +COPY --from=builder /srv/www/prisma ./prisma +COPY .ebextensions ./.ebextensions + +EXPOSE 8080 + +CMD ["npm", "run", "start:prod"] diff --git a/etc/infra/aws/.gitignore b/etc/infra/aws/.gitignore new file mode 100644 index 0000000..bca646a --- /dev/null +++ b/etc/infra/aws/.gitignore @@ -0,0 +1,5 @@ + +# Elastic Beanstalk Files +.elasticbeanstalk/* +!.elasticbeanstalk/*.cfg.yml +!.elasticbeanstalk/*.global.yml diff --git a/etc/infra/aws/.terraform.lock.hcl b/etc/infra/aws/.terraform.lock.hcl new file mode 100644 index 0000000..6741418 --- /dev/null +++ b/etc/infra/aws/.terraform.lock.hcl @@ -0,0 +1,126 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/archive" { + version = "2.5.0" + constraints = "2.5.0" + hashes = [ + "h1:OTk41JfiDc1TVFTcRZ//4+jwPBIcWHXOwN29mjdOyug=", + "zh:3b5774d20e87058d6d67d9ad4ce3fc4a5f7ea7748d345fa6721e24a0cbb0a3d4", + "zh:3b94e706ac0f5151880ccc9e63d33c4113361f27e64224a942caa04a5a19cd44", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7d7201858fa9376029818c9d017b4b53a933cea75480306b1122663d1e8eea2b", + "zh:8c8c7537978adf12271fe143f93b3587bb5dbabf8202ff49d0e3955b7bddc24b", + "zh:a5942584665a2689e73f3a3c43296adeaeb7e8698631d157419aa931ff856907", + "zh:a63673abdba624d60c84b819184fe86422bdbdf6bc73f68d903a7191aed32c00", + "zh:bcd1586cc32b263265e09e78f56dba3a6b6b19f5371c099a9d7a1bfe0b0667cc", + "zh:cc9e70e186e4dcef60208b4a64b42e6813b197e21ea106a96bb4eb23b54c3e44", + "zh:d4c8a0f69412892507a2c9ec0e334bcc2812a54b81212420d4f2c96ef58f713a", + "zh:e91e6d90bbc15252310eca6400d4188b29260aab0539480a3fc7b45e4d19c446", + "zh:fc468449c0dbda56aae6cb924e4a67578d18504b5b06e8989783182c6b4a5f73", + ] +} + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.64.0" + constraints = "~> 5.0" + hashes = [ + "h1:YH4I78rsS9t+YoGMPNzrM53aWi0Rb9Nud16iusrSXMg=", + "zh:1d361f8062c68c9d5ac14b0aa8390709542129b8a9b258e61bbbabc706078b44", + "zh:39dcbf53e3896bdd77071384c8fad4a5862c222c73f3bcf356aca488101f22fd", + "zh:3fad63505f0c5b6f01cc9a6ef02b2226983b79424126a9caf6eb724f654299f4", + "zh:53a8b90d00829cc27e3171a13a8ff1404ee0ea018e73f31d3f916d246cc39613", + "zh:5734c25ef5a04b40f3c1ac5f817f11e42ee3328f74dbc141c0e64afbb0acc834", + "zh:66ea14dbd87f291ce4a877123363933d3ca4022f209f885807a6689c22c24e80", + "zh:68e79654ad0894a3d93134c3377748ace3058d5fad5ec09d1e9a8f8f9b8a47ea", + "zh:7b74259d0ceef0c49cea6bcd171df997b6bad141085bbadded15b440faeb0eee", + "zh:988ebfb5d115dc57070b5abf2e4200ad49cde535f27fd2ba5e34cf9ab336a57f", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a0a2d4efe2835f0101a0a5024e044a3f28c00e10a8d87fce89c707ef6db75cea", + "zh:aecb3e4b9121771dee9cac7975bf5d0657b5f3e8b57788c455beaeb0f3c48d93", + "zh:d2d3393170b8ef761d3146f39f6788c4a3e876e6c5d4cedca4870c2680688ae6", + "zh:daba5a005c1baa4a5eefbfb86d43ccf880eb5b42e8136f0d932f55886d72bda0", + "zh:de16a6ff3baacdaf9609a0a89aa1913fc19cccaf5ee0fc1c49c5a075baa47c02", + ] +} + +provider "registry.terraform.io/hashicorp/external" { + version = "2.3.4" + hashes = [ + "h1:cCabxnWQ5fX1lS7ZqgUzsvWmKZw9FA7NRxAZ94vcTcc=", + "zh:037fd82cd86227359bc010672cd174235e2d337601d4686f526d0f53c87447cb", + "zh:0ea1db63d6173d01f2fa8eb8989f0809a55135a0d8d424b08ba5dabad73095fa", + "zh:17a4d0a306566f2e45778fbac48744b6fd9c958aaa359e79f144c6358cb93af0", + "zh:298e5408ab17fd2e90d2cd6d406c6d02344fe610de5b7dae943a58b958e76691", + "zh:38ecfd29ee0785fd93164812dcbe0664ebbe5417473f3b2658087ca5a0286ecb", + "zh:59f6a6f31acf66f4ea3667a555a70eba5d406c6e6d93c2c641b81d63261eeace", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:ad0279dfd09d713db0c18469f585e58d04748ca72d9ada83883492e0dd13bd58", + "zh:c69f66fd21f5e2c8ecf7ca68d9091c40f19ad913aef21e3ce23836e91b8cbb5f", + "zh:d4a56f8c48aa86fc8e0c233d56850f5783f322d6336f3bf1916e293246b6b5d4", + "zh:f2b394ebd4af33f343835517e80fc876f79361f4688220833bc3c77655dd2202", + "zh:f31982f29f12834e5d21e010856eddd19d59cd8f449adf470655bfd19354377e", + ] +} + +provider "registry.terraform.io/hashicorp/local" { + version = "2.5.1" + constraints = "2.5.1" + hashes = [ + "h1:/GAVA/xheGQcbOZEq0qxANOg+KVLCA7Wv8qluxhTjhU=", + "zh:0af29ce2b7b5712319bf6424cb58d13b852bf9a777011a545fac99c7fdcdf561", + "zh:126063ea0d79dad1f68fa4e4d556793c0108ce278034f101d1dbbb2463924561", + "zh:196bfb49086f22fd4db46033e01655b0e5e036a5582d250412cc690fa7995de5", + "zh:37c92ec084d059d37d6cffdb683ccf68e3a5f8d2eb69dd73c8e43ad003ef8d24", + "zh:4269f01a98513651ad66763c16b268f4c2da76cc892ccfd54b401fff6cc11667", + "zh:51904350b9c728f963eef0c28f1d43e73d010333133eb7f30999a8fb6a0cc3d8", + "zh:73a66611359b83d0c3fcba2984610273f7954002febb8a57242bbb86d967b635", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7ae387993a92bcc379063229b3cce8af7eaf082dd9306598fcd42352994d2de0", + "zh:9e0f365f807b088646db6e4a8d4b188129d9ebdbcf2568c8ab33bddd1b82c867", + "zh:b5263acbd8ae51c9cbffa79743fbcadcb7908057c87eb22fd9048268056efbc4", + "zh:dfcd88ac5f13c0d04e24be00b686d069b4879cc4add1b7b1a8ae545783d97520", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.6.2" + constraints = ">= 3.1.0, 3.6.2" + hashes = [ + "h1:VavG5unYCa3SYISMKF9pzc3718M0bhPlcbUZZGl7wuo=", + "zh:0ef01a4f81147b32c1bea3429974d4d104bbc4be2ba3cfa667031a8183ef88ec", + "zh:1bcd2d8161e89e39886119965ef0f37fcce2da9c1aca34263dd3002ba05fcb53", + "zh:37c75d15e9514556a5f4ed02e1548aaa95c0ecd6ff9af1119ac905144c70c114", + "zh:4210550a767226976bc7e57d988b9ce48f4411fa8a60cd74a6b246baf7589dad", + "zh:562007382520cd4baa7320f35e1370ffe84e46ed4e2071fdc7e4b1a9b1f8ae9b", + "zh:5efb9da90f665e43f22c2e13e0ce48e86cae2d960aaf1abf721b497f32025916", + "zh:6f71257a6b1218d02a573fc9bff0657410404fb2ef23bc66ae8cd968f98d5ff6", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:9647e18f221380a85f2f0ab387c68fdafd58af6193a932417299cdcae4710150", + "zh:bb6297ce412c3c2fa9fec726114e5e0508dd2638cad6a0cb433194930c97a544", + "zh:f83e925ed73ff8a5ef6e3608ad9225baa5376446349572c2449c0c0b3cf184b7", + "zh:fbef0781cb64de76b1df1ca11078aecba7800d82fd4a956302734999cfd9a4af", + ] +} + +provider "registry.terraform.io/kreuzwerker/docker" { + version = "3.0.2" + constraints = "3.0.2" + hashes = [ + "h1:XjdpVL61KtTsuPE8swok3GY8A+Bu3TZs8T2DOEpyiXo=", + "zh:15b0a2b2b563d8d40f62f83057d91acb02cd0096f207488d8b4298a59203d64f", + "zh:23d919de139f7cd5ebfd2ff1b94e6d9913f0977fcfc2ca02e1573be53e269f95", + "zh:38081b3fe317c7e9555b2aaad325ad3fa516a886d2dfa8605ae6a809c1072138", + "zh:4a9c5065b178082f79ad8160243369c185214d874ff5048556d48d3edd03c4da", + "zh:5438ef6afe057945f28bce43d76c4401254073de01a774760169ac1058830ac2", + "zh:60b7fadc287166e5c9873dfe53a7976d98244979e0ab66428ea0dea1ebf33e06", + "zh:61c5ec1cb94e4c4a4fb1e4a24576d5f39a955f09afb17dab982de62b70a9bdd1", + "zh:a38fe9016ace5f911ab00c88e64b156ebbbbfb72a51a44da3c13d442cd214710", + "zh:c2c4d2b1fd9ebb291c57f524b3bf9d0994ff3e815c0cd9c9bcb87166dc687005", + "zh:d567bb8ce483ab2cf0602e07eae57027a1a53994aba470fa76095912a505533d", + "zh:e83bf05ab6a19dd8c43547ce9a8a511f8c331a124d11ac64687c764ab9d5a792", + "zh:e90c934b5cd65516fbcc454c89a150bfa726e7cf1fe749790c7480bbeb19d387", + "zh:f05f167d2eaf913045d8e7b88c13757e3cf595dd5cd333057fdafc7c4b7fed62", + "zh:fcc9c1cea5ce85e8bcb593862e699a881bd36dffd29e2e367f82d15368659c3d", + ] +} diff --git a/etc/infra/aws/Dockerrun.aws.zip b/etc/infra/aws/Dockerrun.aws.zip new file mode 100644 index 0000000..0920324 Binary files /dev/null and b/etc/infra/aws/Dockerrun.aws.zip differ diff --git a/etc/infra/aws/README.md b/etc/infra/aws/README.md new file mode 100644 index 0000000..891f157 --- /dev/null +++ b/etc/infra/aws/README.md @@ -0,0 +1,78 @@ +# CCRP - Infrastructure as Code + +[Getting Started with Terraform & AWS](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) + +First you need to install : + +- [ ] Terraform +- [ ] AWS CLI + +Connect to [AWS IAM console](https://us-east-1.console.aws.amazon.com/iam/home?region=eu-west-3#/home) and create an account for cli usage named `carbonable_deployer` +Connect to [Github.com](https://github.com/settings/tokens) and create a new access token **with admin:hooks** roles(required to create amplify app for frontend to work) **TOKEN HAS TO BE CREATED BY ORG ADMIN** +Add this token to the `.tfvars` file + +Then under `Users > Security Credentials` create new access key and configure them with the access key id and secret access key they give you. + +```bash +aws configure --profile carbonable_deployer +``` + +Create the backend bucket : + +```bash +aws s3api create-bucket --bucket carbonable-deployments-backends --region eu-west-3 --profile carbonable_deployer +``` + +Init backend : + +```bash +AWS_PROFILE=carbonable_deployer terraform init -backend-config=${client}.backend-config.tfvars +``` + +Update backend : (when you changes infrastructure files) + +```bash +AWS_PROFILE=carbonable_deployer terraform plan -var-file=${client}.tfvars +AWS_PROFILE=carbonable_deployer terraform apply -var-file=${client}.tfvars +``` + +When applying changes to `src` directory will trigger a new build of docker version. Applying terraform changes will subsequently trigger a new deploy of the application. + +```bash +AWS_PROFILE=carbonable_deployer terraform apply -var-file=${client}.tfvars +``` + +### Logging + +Application logs are available in the AWS CloudWatch console under `/aws/elasticbeanstalk/{client}-{env}-eb-environment/var/log/eb-docker/containers/eb-current-app/stdouterr.log` log group +Also keep in mind that you can access all the logs of the deployment / container creation under `Elastic Beanstalk > Environment > Logs > Request logs (full)` and download the bundle + +### Considerations + +When updating infrastructure configuration files, you may need to update zip files by hand, as this may not be taken in account in the apply command. +For further configuration of the Elastic Beanstalk environment you may want to read the following section. + +### Extend AWS Elastic Beanstalk environment + +Check informations described here : [https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/platforms-linux-extend.html](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/platforms-linux-extend.html) + +- +- +- +- + +### AWS EB cli + +Install eb cli : + +```bash +brew install awsebcli +``` + +Init env : + +```bash +eb init +``` + +and follow the instructions diff --git a/etc/infra/aws/amplify.tf b/etc/infra/aws/amplify.tf new file mode 100644 index 0000000..29a5f67 --- /dev/null +++ b/etc/infra/aws/amplify.tf @@ -0,0 +1,40 @@ +resource "aws_amplify_app" "frontend" { + name = "${var.app_name}-frontend" + repository = "https://github.com/carbonable/ccrp-front" + access_token = var.github_access_token + + build_spec = <<-EOT + version: 0.1 + frontend: + phases: + preBuild: + commands: + - yarn install + build: + commands: + - yarn run build + artifacts: + baseDirectory: build + files: + - '**/*' + cache: + paths: + - node_modules/**/* +EOT + + environment_variables = { + BACKEND_URL = aws_elastic_beanstalk_environment.eb_environment.endpoint_url + } +} + +resource "aws_amplify_branch" "main" { + app_id = aws_amplify_app.frontend.id + branch_name = "main" + stage = "PRODUCTION" +} + +resource "aws_amplify_webhook" "main_trigger" { + app_id = aws_amplify_app.frontend.id + branch_name = aws_amplify_branch.main.branch_name + description = "triggermaster" +} diff --git a/etc/infra/aws/docker.tf b/etc/infra/aws/docker.tf new file mode 100644 index 0000000..68c5079 --- /dev/null +++ b/etc/infra/aws/docker.tf @@ -0,0 +1,128 @@ +data "aws_caller_identity" "current" {} +data "aws_ecr_authorization_token" "token" {} + +# Create ERC repository to host built docker images +resource "aws_ecr_repository" "repository" { + name = format("%s-%s", var.app_name, var.app_env) + tags = local.tags +} + +# Build docker image +resource "docker_image" "image" { + name = aws_ecr_repository.repository.repository_url + + build { + context = "../../../" + tag = [ + "latest", + data.external.git_commit.result.sha, + ] + + target = "aws_runtime" + platform = "linux/amd64" + } + + + # Triggers build image when src folder changes + triggers = { + dir_sha1 = sha1(join("", [for f in fileset(path.module, "../../../*") : filesha1(f)])) + } +} +resource "docker_tag" "image_latest" { + source_image = docker_image.image.name + target_image = local.docker_image_tagged_latest +} +resource "docker_tag" "image_commit" { + source_image = docker_image.image.name + target_image = local.docker_image_tagged_commit +} +resource "docker_registry_image" "image_registry" { + name = docker_image.image.name +} + +# Creates a Dockerrun.aws.json file +resource "local_file" "docker_run_config" { + depends_on = [docker_image.image] + content = jsonencode({ + AWSEBDockerrunVersion = "1" + Image = { + Name = local.docker_image_tagged_latest + Update = "true" + } + Ports = [{ + ContainerPort = 8080 + HostPort = 80 + }] + }) + filename = "${path.module}/Dockerrun.aws.json" +} + +resource "local_file" "migration_ebextension" { + content = <<-EOT +files: + "/opt/elasticbeanstalk/hooks/appdeploy/post/01_app_release.sh": + mode: "000755" + owner: root + group: root + content: | + #!/usr/bin/env bash + if [ -f /tmp/leader_only ]; then + echo "Running Migrations" + container_id=`docker ps -q --no-trunc --filter label="com.amazonaws.ecs.container-name=${var.app_name}-${var.app_env}"` + docker inspect $container_id + docker exec $container_id /entrypoint.sh npx prisma migrate deploy + fi +container_commands: + 01_touch_the_leader: + command: | + #!/usr/bin/env bash + touch /tmp/leader_only + leader_only: true + EOT + filename = "${path.module}/../../../.ebextensions/01_app_release.config" +} + +# Compress file to upload to s3 bucket that will be used as source for elastic beanstalk +data "archive_file" "docker_run" { + type = "zip" + output_path = "${path.module}/Dockerrun.aws.zip" + + source { + content = local_file.docker_run_config.content + filename = local_file.docker_run_config.filename + } + + source { + content = file("${path.module}/../../../.ebextensions/00_rds_env.config") + filename = ".ebextensions/00_rds_env.config" + } + + source { + content = local_file.migration_ebextension.content + filename = ".ebextensions/01_app_release.config" + } +} + +# Dockerrun.aws.json config file bucket storage +resource "aws_s3_bucket" "docker_run_bucket" { + bucket = "${var.app_name}-${var.app_env}-docker-run-bucket" + + tags = local.tags +} +resource "aws_s3_bucket_server_side_encryption_configuration" "docker_run_bucket_encryption" { + bucket = aws_s3_bucket.docker_run_bucket.id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } +} + +# Create s3 object from the compressed docker run config +resource "aws_s3_object" "docker_run_object" { + key = "${local.docker_run_config_sha}.zip" + bucket = aws_s3_bucket.docker_run_bucket.id + source = data.archive_file.docker_run.output_path + tags = local.tags +} diff --git a/etc/infra/aws/elasticbeanstalk.tf b/etc/infra/aws/elasticbeanstalk.tf new file mode 100644 index 0000000..385dbfc --- /dev/null +++ b/etc/infra/aws/elasticbeanstalk.tf @@ -0,0 +1,192 @@ +data "aws_iam_policy_document" "assume_policy" { + statement { + actions = ["sts:AssumeRole"] + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + } +} + +data "aws_iam_policy_document" "permissions" { + statement { + actions = [ + "cloudwatch:PutMetricData", + "ec2:DescribeInstanceStatus", + "ssm:*", + "ec2messages:*", + "s3:*", + "sqs:*" + ] + resources = ["*"] + } +} + +# This is required to spin up containers in EB environment +# Create instance profile +resource "aws_iam_instance_profile" "ec2_eb_profile" { + name = format("%s-%s-ec2-profile", var.app_name, var.app_env) + role = aws_iam_role.ec2_role.name + tags = local.tags +} + +resource "aws_iam_role" "ec2_role" { + name = format("%s-%s-ec2-role", var.app_name, var.app_env) + assume_role_policy = data.aws_iam_policy_document.assume_policy.json + managed_policy_arns = [ + "arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier", + "arn:aws:iam::aws:policy/AWSElasticBeanstalkMulticontainerDocker", + "arn:aws:iam::aws:policy/AWSElasticBeanstalkWorkerTier", + "arn:aws:iam::aws:policy/EC2InstanceProfileForImageBuilderECRContainerBuilds" + ] + + inline_policy { + name = "eb-application-permissions" + policy = data.aws_iam_policy_document.permissions.json + } + tags = local.tags +} + +# +# Create Elastic Beanstalk application +# Actual Elastic Beanstalk configuraation +# +resource "aws_elastic_beanstalk_application" "eb_application" { + name = format("%s-%s-eb-application", var.app_name, var.app_env) + description = format("%s CCRP backend", var.app_name) + tags = local.tags +} + +resource "aws_elastic_beanstalk_application_version" "eb_version" { + name = local.docker_run_config_sha + application = aws_elastic_beanstalk_application.eb_application.name + description = "Carbonable CCRP Backend" + tags = local.tags + bucket = aws_s3_bucket.docker_run_bucket.bucket + key = aws_s3_object.docker_run_object.id + depends_on = [aws_s3_object.docker_run_object] +} + +resource "aws_elastic_beanstalk_environment" "eb_environment" { + depends_on = [docker_registry_image.image_registry] + name = format("%s-%s-eb-environment", var.app_name, var.app_env) + application = aws_elastic_beanstalk_application.eb_application.name + # list of version available with this command : aws elasticbeanstalk list-platform-versions --query 'PlatformSummaryList[*].PlatformArn' + platform_arn = "arn:aws:elasticbeanstalk:eu-west-3::platform/Docker running on 64bit Amazon Linux 2023/4.3.6" + version_label = aws_elastic_beanstalk_application_version.eb_version.name + cname_prefix = format("%s-%s", var.app_name, var.app_env) + tags = local.tags + + # Set required environment variables for container + dynamic "setting" { + for_each = var.environment_variables + content { + namespace = "aws:elasticbeanstalk:application:environment" + name = setting.key + value = setting.value + } + } + + # Settings namespace list : https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options-general.html + setting { + namespace = "aws:rds:dbinstance" + name = "DBEngine" + value = "postgres" + } + + setting { + namespace = "aws:rds:dbinstance" + name = "DBAllocatedStorage" + value = "20" + } + + setting { + namespace = "aws:rds:dbinstance" + name = "HasCoupledDatabase" + value = "true" + } + + setting { + namespace = "aws:rds:dbinstance" + name = "DBDeletionPolicy" + value = "Snapshot" + } + + setting { + namespace = "aws:rds:dbinstance" + name = "DBEngineVersion" + value = "16.3" + } + + setting { + namespace = "aws:rds:dbinstance" + name = "DBInstanceClass" + value = local.db_instance_class + } + + setting { + namespace = "aws:rds:dbinstance" + name = "DBUser" + value = local.db_username + } + + setting { + namespace = "aws:rds:dbinstance" + name = "DBPassword" + value = local.db_password + } + + setting { + namespace = "aws:autoscaling:launchconfiguration" + name = "IamInstanceProfile" + value = aws_iam_instance_profile.ec2_eb_profile.name + } + + setting { + namespace = "aws:autoscaling:launchconfiguration" + name = "InstanceType" + value = var.instance_type + } + + setting { + namespace = "aws:autoscaling:asg" + name = "MaxSize" + value = var.max_instance_count + } + + setting { + namespace = "aws:elasticbeanstalk:environment" + name = "LoadBalancerType" + value = "application" + } + + setting { + namespace = "aws:elasticbeanstalk:environment:process:default" + name = "MatcherHTTPCode" + value = 200 + } + + setting { + namespace = "aws:elasticbeanstalk:environment:process:default" + name = "HealthCheckPath" + value = "/healthz" + } + + setting { + namespace = "aws:elasticbeanstalk:cloudwatch:logs" + name = "StreamLogs" + value = "true" + } + + setting { + namespace = "aws:elasticbeanstalk:cloudwatch:logs" + name = "RetentionInDays" + value = "7" + } + + setting { + namespace = "aws:elasticbeanstalk:cloudwatch:logs" + name = "DeleteOnTerminate" + value = "false" + } +} diff --git a/etc/infra/aws/main.tf b/etc/infra/aws/main.tf new file mode 100644 index 0000000..65765ce --- /dev/null +++ b/etc/infra/aws/main.tf @@ -0,0 +1,88 @@ +terraform { + required_version = ">= 1.9" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + + docker = { + source = "kreuzwerker/docker" + version = "3.0.2" + } + + local = { + source = "hashicorp/local" + version = "2.5.1" + } + + archive = { + source = "hashicorp/archive" + version = "2.5.0" + } + + random = { + source = "hashicorp/random" + version = "3.6.2" + } + } + + backend "s3" { + bucket = "carbonable-deployments-backends" + } +} + +# Configure the AWS Provider this is recommanded to first create a deployer account on the aws console +# then use this profile to use aws cli and therefore terraform +provider "aws" { + # region needs to be hardcoded. Paris is default region + region = var.aws_region + profile = var.aws_profile +} + +# Configure docker provider to use ECR +provider "docker" { + registry_auth { + address = local.aws_ecr_url + username = data.aws_ecr_authorization_token.token.user_name + password = data.aws_ecr_authorization_token.token.password + } +} + +data "aws_availability_zones" "az" {} +data "external" "git_commit" { + program = ["git", "log", "--pretty=format:{ \"sha\": \"%H\" }", "-n 1"] +} + +resource "random_password" "db_password" { + length = 32 + special = false +} + +locals { + tags = { + App = "ccrp" + Env = var.app_env + EnvironmentName = var.app_env + Name = var.app_name + Client = var.app_name + DeploymentType = "Automatic" + } + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.az.names, 0, 3) + + aws_ecr_url = "${data.aws_caller_identity.current.account_id}.dkr.ecr.${var.aws_region}.amazonaws.com" + docker_run_config_sha = sha256(local_file.docker_run_config.content) + + db_username = "${var.app_name}${var.app_env}" + db_password = random_password.db_password.result + db_instance_class = "db.t3.micro" + + # db_url = "postgresql://${local.db_username}:${local.db_password}@${aws_elastic_beanstalk_environment.}/${local.db_name}" + + docker_image_tagged_latest = "${aws_ecr_repository.repository.repository_url}:latest" + docker_image_tagged_commit = "${aws_ecr_repository.repository.repository_url}:${data.external.git_commit.result.sha}" +} + diff --git a/etc/infra/aws/organization.tf b/etc/infra/aws/organization.tf new file mode 100644 index 0000000..795813b --- /dev/null +++ b/etc/infra/aws/organization.tf @@ -0,0 +1,17 @@ +# Create an organization per client to encapsulate application resources +# and have a better billing control +#resource "aws_organizations_organization" "org" { +# aws_service_access_principals = [ +# "cloudwatch.amazonaws.com", +# "cloudtrail.amazonaws.com", +# "s3.amazonaws.com", +# "rds.amazonaws.com", +# "eb.awmazonaws.com", +# "ec2.amazonaws.com", +# "route53.amazonaws.com", +# "sqs.amazonaws.com", +# "ssm.amazonaws.com", +# ] +# +# feature_set = "ALL" +#} diff --git a/etc/infra/aws/output.tf b/etc/infra/aws/output.tf new file mode 100644 index 0000000..50c2d1c --- /dev/null +++ b/etc/infra/aws/output.tf @@ -0,0 +1,3 @@ +output "backend_url" { + value = aws_elastic_beanstalk_environment.eb_environment.endpoint_url +} diff --git a/etc/infra/aws/sanofi.backend-config.tfvars.tpl b/etc/infra/aws/sanofi.backend-config.tfvars.tpl new file mode 100644 index 0000000..171a5e3 --- /dev/null +++ b/etc/infra/aws/sanofi.backend-config.tfvars.tpl @@ -0,0 +1,2 @@ +key = "ccrp-staging-sanofi-backend.tfstate" +region = "eu-west-3" diff --git a/etc/infra/aws/sanofi.tfvars.tpl b/etc/infra/aws/sanofi.tfvars.tpl new file mode 100644 index 0000000..9ab6d2c --- /dev/null +++ b/etc/infra/aws/sanofi.tfvars.tpl @@ -0,0 +1,16 @@ +# Configure AWS provider +aws_profile = "carbonable_deployer" +aws_region = "eu-west-3" + +# Application variables +app_name = "sanofi" +app_env = "staging" + +environment_variables = { + JWT_KEY = "189c982c0554bcb313390dc4b233faefa073696900dc7d85122c9b37cf4cc4ce" + DEFAULT_ADMIN_NAME = "admin" + DEFAULT_ADMIN_PASSWORD = "changeme" + DEFAULT_ADMIN_ROLES = "['user', 'admin']" + CARBONABLE_SALT = "78860" +} + diff --git a/etc/infra/aws/variables.tf b/etc/infra/aws/variables.tf new file mode 100644 index 0000000..b5a51fe --- /dev/null +++ b/etc/infra/aws/variables.tf @@ -0,0 +1,44 @@ +variable "aws_profile" { + type = string + description = "AWS profile to use" +} + +variable "aws_region" { + type = string + description = "AWS region to use" + default = "eu-west-3" +} + +variable "github_access_token" { + type = string + description = "Github access token to use" +} + +variable "app_name" { + type = string + description = "Name of the application i.e can be the client name" +} + +variable "app_env" { + type = string + description = "Environment of the application i.e can be dev, staging, prod" + default = "staging" +} + +variable "environment_variables" { + type = map(any) + description = "Environment variables to pass to the application" + default = {} +} + +variable "instance_type" { + type = string + description = "Instance type to use for the EC2 instance" + default = "t2.micro" +} +variable "max_instance_count" { + type = number + description = "Maximum number of instances to run" + default = 1 +} + diff --git a/package.json b/package.json index 290302e..7402c13 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,8 @@ "@nestjs/platform-express": "^10.0.5", "@nestjs/swagger": "^7.1.1", "@prisma/client": "^5.19.0", + "@nestjs/terminus": "^10.2.3", + "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "csv-parser": "^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a1140e..8cc49f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,9 +41,15 @@ importers: '@nestjs/swagger': specifier: ^7.1.1 version: 7.1.1(@nestjs/common@10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.5(@nestjs/common@10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/platform-express@10.0.5)(reflect-metadata@0.1.13)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13) + '@nestjs/terminus': + specifier: ^10.2.3 + version: 10.2.3(@nestjs/common@10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.5(@nestjs/common@10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/platform-express@10.0.5)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@prisma/client@5.19.0(prisma@5.19.0))(reflect-metadata@0.1.13)(rxjs@7.8.1) '@prisma/client': specifier: ^5.19.0 version: 5.19.0(prisma@5.19.0) + bcrypt: + specifier: ^5.1.1 + version: 5.1.1 class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -749,6 +755,10 @@ packages: resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} engines: {node: '>=8'} + '@mapbox/node-pre-gyp@1.0.11': + resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} + hasBin: true + '@nestjs/apollo@12.0.7': resolution: {integrity: sha512-snY8uM/ypE5TZOVChIIuNlTFWGCrwJ8U/xq3EFYE6A+dIqY7/B9sou79snevCm3zTde69KdsOYg5FBmznasxNg==} peerDependencies: @@ -891,6 +901,54 @@ packages: class-validator: optional: true + '@nestjs/terminus@10.2.3': + resolution: {integrity: sha512-iX7gXtAooePcyQqFt57aDke5MzgdkBeYgF5YsFNNFwOiAFdIQEhfv3PR0G+HlH9F6D7nBCDZt9U87Pks/qHijg==} + peerDependencies: + '@grpc/grpc-js': '*' + '@grpc/proto-loader': '*' + '@mikro-orm/core': '*' + '@mikro-orm/nestjs': '*' + '@nestjs/axios': ^1.0.0 || ^2.0.0 || ^3.0.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + '@nestjs/microservices': ^9.0.0 || ^10.0.0 + '@nestjs/mongoose': ^9.0.0 || ^10.0.0 + '@nestjs/sequelize': ^9.0.0 || ^10.0.0 + '@nestjs/typeorm': ^9.0.0 || ^10.0.0 + '@prisma/client': '*' + mongoose: '*' + reflect-metadata: 0.1.x || 0.2.x + rxjs: 7.x + sequelize: '*' + typeorm: '*' + peerDependenciesMeta: + '@grpc/grpc-js': + optional: true + '@grpc/proto-loader': + optional: true + '@mikro-orm/core': + optional: true + '@mikro-orm/nestjs': + optional: true + '@nestjs/axios': + optional: true + '@nestjs/microservices': + optional: true + '@nestjs/mongoose': + optional: true + '@nestjs/sequelize': + optional: true + '@nestjs/typeorm': + optional: true + '@prisma/client': + optional: true + mongoose: + optional: true + sequelize: + optional: true + typeorm: + optional: true + '@nestjs/testing@10.0.5': resolution: {integrity: sha512-TQcFOxR+kIibMbYg71yajic3289/Iw7B5LliYiZ4Pz36BZvU0TRMYqpxaGAlX/Srk0BCpP99ZHoofm8dqZKmxw==} peerDependencies: @@ -1330,6 +1388,9 @@ packages: abab@2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -1394,6 +1455,9 @@ packages: amp@0.3.1: resolution: {integrity: sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==} + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -1428,6 +1492,14 @@ packages: append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + + are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -1552,6 +1624,10 @@ packages: resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} engines: {node: '>=0.10.0'} + bcrypt@5.1.1: + resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} + engines: {node: '>= 10.0.0'} + binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -1575,6 +1651,10 @@ packages: resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + boxen@5.1.2: + resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} + engines: {node: '>=10'} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1669,10 +1749,18 @@ packages: charm@0.1.2: resolution: {integrity: sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==} + check-disk-space@3.4.0: + resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==} + engines: {node: '>=16'} + chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + chrome-trace-event@1.0.3: resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} engines: {node: '>=6.0'} @@ -1700,6 +1788,10 @@ packages: class-validator@0.14.0: resolution: {integrity: sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==} + cli-boxes@2.2.1: + resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} + engines: {node: '>=6'} + cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -1765,6 +1857,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1800,6 +1896,9 @@ packages: consola@2.15.3: resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -1971,6 +2070,9 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1979,6 +2081,10 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -2361,6 +2467,10 @@ packages: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + fs-monkey@1.0.4: resolution: {integrity: sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==} @@ -2379,6 +2489,11 @@ packages: function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + generic-pool@3.9.0: resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} engines: {node: '>= 4'} @@ -2514,6 +2629,9 @@ packages: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + has-value@0.3.1: resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==} engines: {node: '>=0.10.0'} @@ -3344,14 +3462,26 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + minipass@4.2.8: resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} engines: {node: '>=8'} + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + minipass@7.0.2: resolution: {integrity: sha512-eL79dXrE1q9dBbDCLg7xfn/vl7MS4F1gvJAgjJrQli/jbQWdUttuVawphqpffoIYfRdq78LHx6GP4bU/EQ2ATA==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + mixin-deep@1.3.2: resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} engines: {node: '>=0.10.0'} @@ -3435,6 +3565,9 @@ packages: node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + node-addon-api@5.1.0: + resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} @@ -3456,6 +3589,11 @@ packages: node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} + nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -3475,6 +3613,10 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + deprecated: This package is no longer supported. + nssocket@0.6.0: resolution: {integrity: sha512-a9GSOIql5IqgWJR3F/JXG4KpJTA3Z53Cj0MeMvGpglytB1nxE4PdFNC0jINe27CS7cGivoynwc054EzCcT3M3w==} engines: {node: '>= 0.10.x'} @@ -4244,6 +4386,10 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + terminal-link@2.1.1: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} @@ -4650,6 +4796,13 @@ packages: engines: {node: '>= 8'} hasBin: true + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + + widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + windows-release@4.0.0: resolution: {integrity: sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==} engines: {node: '>=10'} @@ -5658,6 +5811,21 @@ snapshots: '@lukeed/csprng@1.1.0': {} + '@mapbox/node-pre-gyp@1.0.11': + dependencies: + detect-libc: 2.0.3 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.6.12 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.5.4 + tar: 6.2.1 + transitivePeerDependencies: + - encoding + - supports-color + '@nestjs/apollo@12.0.7(@apollo/server@4.7.5(graphql@16.7.1))(@nestjs/common@10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.5(@nestjs/common@10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/platform-express@10.0.5)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/graphql@12.0.8(@nestjs/common@10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.5(@nestjs/common@10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/platform-express@10.0.5)(reflect-metadata@0.1.13)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.0)(graphql@16.7.1)(reflect-metadata@0.1.13)(ts-morph@20.0.0))(graphql@16.7.1)': dependencies: '@apollo/server': 4.7.5(graphql@16.7.1) @@ -5820,6 +5988,17 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.0 + '@nestjs/terminus@10.2.3(@nestjs/common@10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.5(@nestjs/common@10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/platform-express@10.0.5)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@prisma/client@5.19.0(prisma@5.19.0))(reflect-metadata@0.1.13)(rxjs@7.8.1)': + dependencies: + '@nestjs/common': 10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/core': 10.0.5(@nestjs/common@10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/platform-express@10.0.5)(reflect-metadata@0.1.13)(rxjs@7.8.1) + boxen: 5.1.2 + check-disk-space: 3.4.0 + reflect-metadata: 0.1.13 + rxjs: 7.8.1 + optionalDependencies: + '@prisma/client': 5.19.0(prisma@5.19.0) + '@nestjs/testing@10.0.5(@nestjs/common@10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.5(@nestjs/common@10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/platform-express@10.0.5)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/platform-express@10.0.5(@nestjs/common@10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.0.5))': dependencies: '@nestjs/common': 10.0.5(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) @@ -6378,6 +6557,8 @@ snapshots: abab@2.0.6: {} + abbrev@1.1.1: {} + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -6438,6 +6619,10 @@ snapshots: amp@0.3.1: {} + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + ansi-colors@4.1.3: {} ansi-escapes@4.3.2: @@ -6470,6 +6655,13 @@ snapshots: append-field@1.0.0: {} + aproba@2.0.0: {} + + are-we-there-yet@2.0.0: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + arg@4.1.3: {} argparse@1.0.10: @@ -6620,6 +6812,14 @@ snapshots: mixin-deep: 1.3.2 pascalcase: 0.1.1 + bcrypt@5.1.1: + dependencies: + '@mapbox/node-pre-gyp': 1.0.11 + node-addon-api: 5.1.0 + transitivePeerDependencies: + - encoding + - supports-color + binary-extensions@2.2.0: {} bl@4.1.0: @@ -6666,6 +6866,17 @@ snapshots: transitivePeerDependencies: - supports-color + boxen@5.1.2: + dependencies: + ansi-align: 3.0.1 + camelcase: 6.3.0 + chalk: 4.1.2 + cli-boxes: 2.2.1 + string-width: 4.2.3 + type-fest: 0.20.2 + widest-line: 3.1.0 + wrap-ansi: 7.0.0 + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -6777,6 +6988,8 @@ snapshots: charm@0.1.2: {} + check-disk-space@3.4.0: {} + chokidar@3.5.3: dependencies: anymatch: 3.1.3 @@ -6789,6 +7002,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chownr@2.0.0: {} + chrome-trace-event@1.0.3: {} ci-info@2.0.0: {} @@ -6814,6 +7029,8 @@ snapshots: libphonenumber-js: 1.10.44 validator: 13.11.0 + cli-boxes@2.2.1: {} + cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -6873,6 +7090,8 @@ snapshots: color-name@1.1.4: {} + color-support@1.1.3: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -6906,6 +7125,8 @@ snapshots: consola@2.15.3: {} + console-control-strings@1.1.0: {} + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -7055,10 +7276,14 @@ snapshots: delayed-stream@1.0.0: {} + delegates@1.0.0: {} + depd@2.0.0: {} destroy@1.2.0: {} + detect-libc@2.0.3: {} + detect-newline@3.1.0: {} dezalgo@1.0.4: @@ -7552,6 +7777,10 @@ snapshots: jsonfile: 4.0.0 universalify: 0.1.2 + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + fs-monkey@1.0.4: {} fs.realpath@1.0.0: {} @@ -7566,6 +7795,18 @@ snapshots: function-bind@1.1.1: {} + gauge@3.0.2: + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + generic-pool@3.9.0: {} gensync@1.0.0-beta.2: {} @@ -7689,6 +7930,8 @@ snapshots: has-symbols@1.0.3: {} + has-unicode@2.0.1: {} + has-value@0.3.1: dependencies: get-value: 2.0.6 @@ -8066,7 +8309,7 @@ snapshots: jest-environment-jsdom: 26.6.2 jest-environment-node: 26.6.2 jest-get-type: 26.3.0 - jest-jasmine2: 26.6.3(ts-node@10.9.1(@types/node@20.4.1)(typescript@5.1.6)) + jest-jasmine2: 26.6.3 jest-regex-util: 26.0.0 jest-resolve: 26.6.2 jest-util: 26.6.2 @@ -8239,7 +8482,7 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - jest-jasmine2@26.6.3(ts-node@10.9.1(@types/node@20.4.1)(typescript@5.1.6)): + jest-jasmine2@26.6.3: dependencies: '@babel/traverse': 7.22.8 '@jest/environment': 26.6.2 @@ -8260,11 +8503,7 @@ snapshots: pretty-format: 26.6.2 throat: 5.0.0 transitivePeerDependencies: - - bufferutil - - canvas - supports-color - - ts-node - - utf-8-validate jest-leak-detector@26.6.2: dependencies: @@ -8941,10 +9180,21 @@ snapshots: minimist@1.2.8: {} + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + minipass@4.2.8: {} + minipass@5.0.0: {} + minipass@7.0.2: {} + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + mixin-deep@1.3.2: dependencies: for-in: 1.0.2 @@ -9041,6 +9291,8 @@ snapshots: node-abort-controller@3.1.1: {} + node-addon-api@5.1.0: {} + node-emoji@1.11.0: dependencies: lodash: 4.17.21 @@ -9063,6 +9315,10 @@ snapshots: node-releases@2.0.13: {} + nopt@5.0.0: + dependencies: + abbrev: 1.1.1 + normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 @@ -9084,6 +9340,13 @@ snapshots: dependencies: path-key: 3.1.1 + npmlog@5.0.1: + dependencies: + are-we-there-yet: 2.0.0 + console-control-strings: 1.1.0 + gauge: 3.0.2 + set-blocking: 2.0.0 + nssocket@0.6.0: dependencies: eventemitter2: 0.4.14 @@ -9953,6 +10216,15 @@ snapshots: tapable@2.2.1: {} + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + terminal-link@2.1.1: dependencies: ansi-escapes: 4.3.2 @@ -10338,6 +10610,14 @@ snapshots: dependencies: isexe: 2.0.0 + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + + widest-line@3.1.0: + dependencies: + string-width: 4.2.3 + windows-release@4.0.0: dependencies: execa: 4.1.0 diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8b45db0..28ef02a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -3,7 +3,7 @@ generator client { provider = "prisma-client-js" - binaryTargets = ["linux-musl-openssl-3.0.x", "darwin-arm64", "native"] + binaryTargets = ["linux-musl-openssl-3.0.x", "darwin-arm64", "native", "linux-musl-arm64-openssl-3.0.x"] engineType = "binary" } @@ -221,15 +221,15 @@ model Allocation { } model Stock { - id String @id - vintage String @db.VarChar(4) - quantity Int - available Int @default(0) - consumed Int @default(0) - purchased Int @default(0) - purchasedPrice Int @default(0)@map("purchased_price") - issuedPrice Int @default(0)@map("issued_price") - retired Int @default(0) + id String @id + vintage String @db.VarChar(4) + quantity Int + available Int @default(0) + consumed Int @default(0) + purchased Int @default(0) + purchasedPrice Int @default(0) @map("purchased_price") + issuedPrice Int @default(0) @map("issued_price") + retired Int @default(0) businessUnitId String? @map("business_unit_id") businessUnit BusinessUnit? @relation(fields: [businessUnitId], references: [id]) @@ -244,15 +244,15 @@ model Stock { } model Vintage { - id String @id - year String @db.VarChar(4) - capacity Int - available Int - reserved Int @default(0) - consumed Int @default(0) - purchased Int @default(0) - purchasedPrice Int @default(0) @map("purchased_price") - issuedPrice Int @default(0) @map("issued_price") + id String @id + year String @db.VarChar(4) + capacity Int + available Int + reserved Int @default(0) + consumed Int @default(0) + purchased Int @default(0) + purchasedPrice Int @default(0) @map("purchased_price") + issuedPrice Int @default(0) @map("issued_price") projectId String @map("project_id") project Project @relation(fields: [projectId], references: [id]) diff --git a/src/app.module.ts b/src/app.module.ts index abcac2b..6585561 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -11,8 +11,12 @@ import { ClerkAuthGuard } from './auth/auth.guard'; import { APP_GUARD } from '@nestjs/core'; import { RolesGuard } from './roles/roles.guard'; import { CsvModule } from './csv/csv.module'; +import { TerminusModule } from '@nestjs/terminus'; +import { HealthController } from './health.controller'; +import { PrismaModule } from './infrastructure/prisma.module'; @Module({ + controllers: [HealthController], imports: [ GraphQLModule.forRoot({ driver: ApolloDriver, @@ -28,6 +32,8 @@ import { CsvModule } from './csv/csv.module'; ConsoleModule, EventEmitterModule.forRoot(), UsersModule, + TerminusModule, + PrismaModule, ], providers: [ { diff --git a/src/console/console.module.ts b/src/console/console.module.ts index ad36726..962ad54 100644 --- a/src/console/console.module.ts +++ b/src/console/console.module.ts @@ -1,11 +1,10 @@ import { Module } from '@nestjs/common'; import { BuildFixturesCommand } from './build-fixtures'; import { InfrastructureModule } from '../infrastructure/infrastructure.module'; -import { FlushRedisCommand } from './flush-redis'; @Module({ - providers: [BuildFixturesCommand, FlushRedisCommand], - exports: [BuildFixturesCommand, FlushRedisCommand], + providers: [BuildFixturesCommand], + exports: [BuildFixturesCommand], imports: [InfrastructureModule], }) -export class ConsoleModule {} +export class ConsoleModule { } diff --git a/src/console/flush-redis.ts b/src/console/flush-redis.ts deleted file mode 100644 index ee9b828..0000000 --- a/src/console/flush-redis.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Inject, Logger } from '@nestjs/common'; -import { CommandRunner, Command } from 'nest-commander'; -import { REDIS_CLIENT } from '../infrastructure/infrastructure.module'; -import { RedisClientType } from 'redis'; - -@Command({ name: 'flush-redis' }) -export class FlushRedisCommand extends CommandRunner { - private readonly logger = new Logger(FlushRedisCommand.name); - - constructor( - @Inject(REDIS_CLIENT) private readonly redisClient: RedisClientType, - ) { - super(); - } - - async run( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - passedParams: string[], - // eslint-disable-next-line @typescript-eslint/no-unused-vars - options?: Record, - ): Promise { - try { - await this.redisClient.flushAll(); - - this.logger.log('Redis flushed successfully'); - } catch (e) { - this.logger.error(e); - } finally { - process.exit(0); - } - } -} diff --git a/src/contribution-manager/contribution-manager.module.ts b/src/contribution-manager/contribution-manager.module.ts index abaf89e..b01db6e 100644 --- a/src/contribution-manager/contribution-manager.module.ts +++ b/src/contribution-manager/contribution-manager.module.ts @@ -7,7 +7,6 @@ import { BusinessUnitResolver, DemandResolver, AllocationResolver, - VisualizationResolver, StockResolver, } from './resolvers'; import { @@ -55,7 +54,6 @@ import { AbsorptionController } from './absorption-curve/absorption-curve.contro BusinessUnitResolver, DemandResolver, AllocationResolver, - VisualizationResolver, AllocationResolver, StockResolver, ProjectService, @@ -102,7 +100,6 @@ import { AbsorptionController } from './absorption-curve/absorption-curve.contro DemandResolver, CertifierResolver, AllocationResolver, - VisualizationResolver, StockResolver, ProjectedDecarbonationService, ImpactMetricsService, @@ -113,4 +110,4 @@ import { AbsorptionController } from './absorption-curve/absorption-curve.contro imports: [InfrastructureModule, CsvModule], }) -export class ContributionManagerModule {} +export class ContributionManagerModule { } diff --git a/src/contribution-manager/resolvers.ts b/src/contribution-manager/resolvers.ts index ae41389..f9f3029 100644 --- a/src/contribution-manager/resolvers.ts +++ b/src/contribution-manager/resolvers.ts @@ -5,5 +5,4 @@ export { CarbonCreditResolver } from './carbon-credits/carbon-credits.resolver'; export { BusinessUnitResolver } from './business-unit/business-unit.resolver'; export { DemandResolver } from './demand/demand.resolver'; export { AllocationResolver } from './allocations/allocations.resolver'; -export { VisualizationResolver } from './visualization/visualization'; export { StockResolver } from './stock/stock.resolver'; diff --git a/src/contribution-manager/services/projected-decarbonation.ts b/src/contribution-manager/services/projected-decarbonation.ts index c93231a..c2209ce 100644 --- a/src/contribution-manager/services/projected-decarbonation.ts +++ b/src/contribution-manager/services/projected-decarbonation.ts @@ -44,18 +44,14 @@ type DataFilter = { filter?: string; page?: number; count?: number }; export class ProjectedDecarbonationService { private readonly logger = new Logger(ProjectedDecarbonationService.name); - constructor( - private readonly prisma: PrismaService, - @Inject(VISUALIZATION_REPOSITORY) - private readonly visualizationRepository: VisualizationRepositoryInterface, - ) {} + constructor(private readonly prisma: PrismaService) { } async get(id: string): Promise { - const visualizations = await this.visualizationRepository.get( - `NET-ZERO#COMPANY#${id}`, - ); - - return JSON.parse(visualizations); + // const visualizations = await this.visualizationRepository.get( + // `NET-ZERO#COMPANY#${id}`, + // ); + // + return JSON.parse('[]'); } async getTable( diff --git a/src/contribution-manager/visualization/visualization.ts b/src/contribution-manager/visualization/visualization.ts deleted file mode 100644 index e0dcef6..0000000 --- a/src/contribution-manager/visualization/visualization.ts +++ /dev/null @@ -1,149 +0,0 @@ -import * as _ from 'lodash'; -import { Args, Resolver, Query } from '@nestjs/graphql'; -import { - VISUALIZATION_REPOSITORY, - VisualizationRepositoryInterface, -} from '../../domain/allocation'; -import { Inject } from '@nestjs/common'; -import { - VisualizationStrategyKeyInput, - annualPlanningKey, - cumulativePlanningKey, - financialAnalysisKey, - netZeroKey, -} from '../../domain/allocation/visualization/utils'; -import { VisualizationViewType } from '../../schemas/graphql.autogenerated'; -import Utils from '../../utils'; -import { - AnnualPlanningVisualizationStrategy, - CumulativePlanningVisualizationStrategy, - NetZeroVisualizationStrategy, -} from '../../domain/allocation/visualization'; -import { FinancialAnalysisVisualizationStrategy } from '../../domain/allocation/visualization/financial-analysis.strategy'; -import { Public } from '../../auth/auth.public.decorator'; - -@Resolver('Visualization') -export class VisualizationResolver { - constructor( - @Inject(VISUALIZATION_REPOSITORY) - private readonly repository: VisualizationRepositoryInterface, - private readonly netZeroStrategy: NetZeroVisualizationStrategy, - private readonly annualPlannigStrategy: AnnualPlanningVisualizationStrategy, - private readonly cumulativePlannigStrategy: CumulativePlanningVisualizationStrategy, - private readonly financialAnalysisStrategy: FinancialAnalysisVisualizationStrategy, - ) {} - - @Public() - @Query() - async netZeroPlanning(@Args('view') view: any) { - const data = await getVisualizationView(netZeroKey)( - this.repository, - view as VisualizationViewType, - ); - - if (data.length === 0 || process.env.SHOULD_CACHE) { - await this.netZeroStrategy.hydrateFromViewType(view); - } - - return await getVisualizationView(netZeroKey)( - this.repository, - view as VisualizationViewType, - ); - } - - @Public() - @Query() - async annual(@Args('view') view: any, @Args('pagination') pagination: any) { - const data = await getVisualizationView(annualPlanningKey)( - this.repository, - view as VisualizationViewType, - ); - - if (data.length === 0 || process.env.SHOULD_CACHE) { - await this.annualPlannigStrategy.hydrateFromViewType(view); - } - - return Utils.paginate( - await getVisualizationView(annualPlanningKey)( - this.repository, - view as VisualizationViewType, - ), - pagination, - ); - } - - @Public() - @Query() - async cumulative( - @Args('view') view: any, - @Args('pagination') pagination: any, - ) { - const data = await getVisualizationView(cumulativePlanningKey)( - this.repository, - view as VisualizationViewType, - ); - - if (data.length === 0 || process.env.SHOULD_CACHE) { - await this.cumulativePlannigStrategy.hydrateFromViewType(view); - } - return Utils.paginate( - await getVisualizationView(cumulativePlanningKey)( - this.repository, - view as VisualizationViewType, - ), - pagination, - ); - } - - @Public() - @Query() - async financialAnalysis( - @Args('view') view: any, - @Args('pagination') pagination: any, - ) { - const data = await getVisualizationView(financialAnalysisKey)( - this.repository, - view as VisualizationViewType, - ); - - if (data.length === 0 || process.env.SHOULD_CACHE) { - await this.financialAnalysisStrategy.hydrateFromViewType(view); - } - - return Utils.paginate( - await getVisualizationView(financialAnalysisKey)( - this.repository, - view as VisualizationViewType, - ), - pagination, - ); - } -} - -function getVisualizationView( - viewKey: (input: VisualizationStrategyKeyInput) => string, -) { - return async function ( - repository: VisualizationRepositoryInterface, - view: VisualizationViewType, - ) { - // converting snakecase to camelCase - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const camelized = _.mapKeys(view, (_v: string, k: string) => - _.camelCase(k), - ); - const res = await repository.get( - viewKey(camelized as VisualizationStrategyKeyInput), - ); - if (!res) { - return []; - } - - return _.values( - _.mapValues(JSON.parse(res), (item) => { - return _.mapKeys(item, (_v: any, k: string) => _.snakeCase(k)); - }), - ); - }; -} diff --git a/src/health.controller.ts b/src/health.controller.ts new file mode 100644 index 0000000..5a28f41 --- /dev/null +++ b/src/health.controller.ts @@ -0,0 +1,26 @@ +import { Controller, Get } from '@nestjs/common'; +import { + HealthCheck, + HealthCheckService, + PrismaHealthIndicator, +} from '@nestjs/terminus'; +import { PrismaService } from './infrastructure/prisma.service'; +import { Public } from './auth/auth.public.decorator'; + +@Public() +@Controller('healthz') +export class HealthController { + constructor( + private health: HealthCheckService, + private db: PrismaHealthIndicator, + private prisma: PrismaService, + ) {} + + @Get() + @HealthCheck() + readiness() { + return this.health.check([ + async () => this.db.pingCheck('database', this.prisma), + ]); + } +} diff --git a/src/infrastructure/infrastructure.module.ts b/src/infrastructure/infrastructure.module.ts index 1f67cee..1745252 100644 --- a/src/infrastructure/infrastructure.module.ts +++ b/src/infrastructure/infrastructure.module.ts @@ -44,8 +44,6 @@ import { NEST_EVENT_DISPATCHER, NestjsEventDispatcher, } from './event-dispatcher.nestjs'; -import { RedisClientType, createClient } from 'redis'; -import { RedisVisualizationRepository } from './repository/visualization.redis'; import { NetZeroVisualizationStrategy, AnnualPlanningVisualizationStrategy, @@ -206,43 +204,26 @@ export const REDIS_CLIENT = 'REDIS_CLIENT'; }, inject: [EventEmitter2], }, - { - provide: AllocationFinishedHandler, - useFactory: ( - prisma: PrismaService, - visualizationManager: VisualizationManager, - ) => { - return new AllocationFinishedHandler( - new PrismaAllocationRepository(prisma), - visualizationManager, - ); - }, - inject: [PrismaService, VisualizationManager], - }, - { - provide: NestJsOnAllocationFinished, - useFactory: (handler: AllocationFinishedHandler) => { - return new NestJsOnAllocationFinished(handler); - }, - inject: [AllocationFinishedHandler], - }, - { - provide: REDIS_CLIENT, - useFactory: async () => { - const client = createClient({ - url: process.env.REDIS_URL ?? 'redis://localhost:6379', - }); - await client.connect(); - return client; - }, - }, - { - provide: VISUALIZATION_REPOSITORY, - useFactory: (client: RedisClientType) => { - return new RedisVisualizationRepository(client); - }, - inject: [REDIS_CLIENT], - }, + // { + // provide: AllocationFinishedHandler, + // useFactory: ( + // prisma: PrismaService, + // visualizationManager: VisualizationManager, + // ) => { + // return new AllocationFinishedHandler( + // new PrismaAllocationRepository(prisma), + // visualizationManager, + // ); + // }, + // inject: [PrismaService, VisualizationManager], + // }, + // { + // provide: NestJsOnAllocationFinished, + // useFactory: (handler: AllocationFinishedHandler) => { + // return new NestJsOnAllocationFinished(handler); + // }, + // inject: [AllocationFinishedHandler], + // }, { provide: VisualizationDataExtractor, useFactory: ( @@ -262,137 +243,137 @@ export const REDIS_CLIENT = 'REDIS_CLIENT'; PROJECT_REPOSITORY, ], }, - { - provide: NetZeroVisualizationStrategy, - useFactory: ( - visualizationDataExtractor: VisualizationDataExtractor, - visualizationRepository: VisualizationRepositoryInterface, - stockRepository: StockRepositoryInterface, - orderRepository: OrderBookRepositoryInterface, - businessUnitRepository: BusinessUnitRepositoryInterface, - ) => { - return new NetZeroVisualizationStrategy( - visualizationDataExtractor, - visualizationRepository, - stockRepository, - orderRepository, - businessUnitRepository, - ); - }, - inject: [ - VisualizationDataExtractor, - VISUALIZATION_REPOSITORY, - STOCK_REPOSITORY, - ORDER_BOOK_REPOSITORY, - BUSINESS_UNIT_REPOSITORY, - ], - }, - { - provide: AnnualPlanningVisualizationStrategy, - useFactory: ( - visualizationDataExtractor: VisualizationDataExtractor, - visualizationRepository: VisualizationRepositoryInterface, - stockRepository: StockRepositoryInterface, - orderRepository: OrderBookRepositoryInterface, - businessUnitRepository: BusinessUnitRepositoryInterface, - companyRepository: CompanyRepositoryInterface, - ) => { - return new AnnualPlanningVisualizationStrategy( - visualizationDataExtractor, - visualizationRepository, - stockRepository, - orderRepository, - businessUnitRepository, - companyRepository, - ); - }, - inject: [ - VisualizationDataExtractor, - VISUALIZATION_REPOSITORY, - STOCK_REPOSITORY, - ORDER_BOOK_REPOSITORY, - BUSINESS_UNIT_REPOSITORY, - COMPANY_REPOSITORY, - ], - }, - { - provide: CumulativePlanningVisualizationStrategy, - useFactory: ( - visualizationDataExtractor: VisualizationDataExtractor, - visualizationRepository: VisualizationRepositoryInterface, - stockRepository: StockRepositoryInterface, - orderRepository: OrderBookRepositoryInterface, - businessUnitRepository: BusinessUnitRepositoryInterface, - companyRepository: CompanyRepositoryInterface, - ) => { - return new CumulativePlanningVisualizationStrategy( - visualizationDataExtractor, - visualizationRepository, - stockRepository, - orderRepository, - businessUnitRepository, - companyRepository, - ); - }, - inject: [ - VisualizationDataExtractor, - VISUALIZATION_REPOSITORY, - STOCK_REPOSITORY, - ORDER_BOOK_REPOSITORY, - BUSINESS_UNIT_REPOSITORY, - COMPANY_REPOSITORY, - ], - }, - { - provide: FinancialAnalysisVisualizationStrategy, - useFactory: ( - visualizationDataExtractor: VisualizationDataExtractor, - visualizationRepository: VisualizationRepositoryInterface, - stockRepository: StockRepositoryInterface, - businessUnitRepository: BusinessUnitRepositoryInterface, - orderRepository: OrderBookRepositoryInterface, - companyRepository: CompanyRepositoryInterface, - ) => { - return new FinancialAnalysisVisualizationStrategy( - visualizationDataExtractor, - visualizationRepository, - stockRepository, - businessUnitRepository, - orderRepository, - companyRepository, - ); - }, - inject: [ - VisualizationDataExtractor, - VISUALIZATION_REPOSITORY, - STOCK_REPOSITORY, - BUSINESS_UNIT_REPOSITORY, - ORDER_BOOK_REPOSITORY, - COMPANY_REPOSITORY, - ], - }, - { - provide: VisualizationManager, - useFactory: ( - netZeroVisualizationStrategy: NetZeroVisualizationStrategy, - annualPlanningVisualizationStrategy: AnnualPlanningVisualizationStrategy, - cumulativePlanningVisualizationStrategy: CumulativePlanningVisualizationStrategy, - financialAnalysisVisualizationStrategy: FinancialAnalysisVisualizationStrategy, - ) => { - return new VisualizationManager([ - netZeroVisualizationStrategy, - annualPlanningVisualizationStrategy, - cumulativePlanningVisualizationStrategy, - financialAnalysisVisualizationStrategy, - ]); - }, - inject: [ - NetZeroVisualizationStrategy, - AnnualPlanningVisualizationStrategy, - CumulativePlanningVisualizationStrategy, - FinancialAnalysisVisualizationStrategy, - ], - }, + // { + // provide: NetZeroVisualizationStrategy, + // useFactory: ( + // visualizationDataExtractor: VisualizationDataExtractor, + // visualizationRepository: VisualizationRepositoryInterface, + // stockRepository: StockRepositoryInterface, + // orderRepository: OrderBookRepositoryInterface, + // businessUnitRepository: BusinessUnitRepositoryInterface, + // ) => { + // return new NetZeroVisualizationStrategy( + // visualizationDataExtractor, + // visualizationRepository, + // stockRepository, + // orderRepository, + // businessUnitRepository, + // ); + // }, + // inject: [ + // VisualizationDataExtractor, + // VISUALIZATION_REPOSITORY, + // STOCK_REPOSITORY, + // ORDER_BOOK_REPOSITORY, + // BUSINESS_UNIT_REPOSITORY, + // ], + // }, + // { + // provide: AnnualPlanningVisualizationStrategy, + // useFactory: ( + // visualizationDataExtractor: VisualizationDataExtractor, + // visualizationRepository: VisualizationRepositoryInterface, + // stockRepository: StockRepositoryInterface, + // orderRepository: OrderBookRepositoryInterface, + // businessUnitRepository: BusinessUnitRepositoryInterface, + // companyRepository: CompanyRepositoryInterface, + // ) => { + // return new AnnualPlanningVisualizationStrategy( + // visualizationDataExtractor, + // visualizationRepository, + // stockRepository, + // orderRepository, + // businessUnitRepository, + // companyRepository, + // ); + // }, + // inject: [ + // VisualizationDataExtractor, + // VISUALIZATION_REPOSITORY, + // STOCK_REPOSITORY, + // ORDER_BOOK_REPOSITORY, + // BUSINESS_UNIT_REPOSITORY, + // COMPANY_REPOSITORY, + // ], + // }, + // { + // provide: CumulativePlanningVisualizationStrategy, + // useFactory: ( + // visualizationDataExtractor: VisualizationDataExtractor, + // visualizationRepository: VisualizationRepositoryInterface, + // stockRepository: StockRepositoryInterface, + // orderRepository: OrderBookRepositoryInterface, + // businessUnitRepository: BusinessUnitRepositoryInterface, + // companyRepository: CompanyRepositoryInterface, + // ) => { + // return new CumulativePlanningVisualizationStrategy( + // visualizationDataExtractor, + // visualizationRepository, + // stockRepository, + // orderRepository, + // businessUnitRepository, + // companyRepository, + // ); + // }, + // inject: [ + // VisualizationDataExtractor, + // VISUALIZATION_REPOSITORY, + // STOCK_REPOSITORY, + // ORDER_BOOK_REPOSITORY, + // BUSINESS_UNIT_REPOSITORY, + // COMPANY_REPOSITORY, + // ], + // }, + // { + // provide: FinancialAnalysisVisualizationStrategy, + // useFactory: ( + // visualizationDataExtractor: VisualizationDataExtractor, + // visualizationRepository: VisualizationRepositoryInterface, + // stockRepository: StockRepositoryInterface, + // businessUnitRepository: BusinessUnitRepositoryInterface, + // orderRepository: OrderBookRepositoryInterface, + // companyRepository: CompanyRepositoryInterface, + // ) => { + // return new FinancialAnalysisVisualizationStrategy( + // visualizationDataExtractor, + // visualizationRepository, + // stockRepository, + // businessUnitRepository, + // orderRepository, + // companyRepository, + // ); + // }, + // inject: [ + // VisualizationDataExtractor, + // VISUALIZATION_REPOSITORY, + // STOCK_REPOSITORY, + // BUSINESS_UNIT_REPOSITORY, + // ORDER_BOOK_REPOSITORY, + // COMPANY_REPOSITORY, + // ], + // }, + // { + // provide: VisualizationManager, + // useFactory: ( + // netZeroVisualizationStrategy: NetZeroVisualizationStrategy, + // annualPlanningVisualizationStrategy: AnnualPlanningVisualizationStrategy, + // cumulativePlanningVisualizationStrategy: CumulativePlanningVisualizationStrategy, + // financialAnalysisVisualizationStrategy: FinancialAnalysisVisualizationStrategy, + // ) => { + // return new VisualizationManager([ + // netZeroVisualizationStrategy, + // annualPlanningVisualizationStrategy, + // cumulativePlanningVisualizationStrategy, + // financialAnalysisVisualizationStrategy, + // ]); + // }, + // inject: [ + // NetZeroVisualizationStrategy, + // AnnualPlanningVisualizationStrategy, + // CumulativePlanningVisualizationStrategy, + // FinancialAnalysisVisualizationStrategy, + // ], + // }, ], exports: [ PrismaService, @@ -402,18 +383,16 @@ export const REDIS_CLIENT = 'REDIS_CLIENT'; AddAllocationUseCase, ID_GENERATOR, NEST_EVENT_DISPATCHER, - REDIS_CLIENT, - VisualizationManager, - VISUALIZATION_REPOSITORY, + // VisualizationManager, ORDER_BOOK_REPOSITORY, BUSINESS_UNIT_REPOSITORY, PROJECT_REPOSITORY, COMPANY_REPOSITORY, STOCK_REPOSITORY, - NetZeroVisualizationStrategy, - AnnualPlanningVisualizationStrategy, - CumulativePlanningVisualizationStrategy, - FinancialAnalysisVisualizationStrategy, + // NetZeroVisualizationStrategy, + // AnnualPlanningVisualizationStrategy, + // CumulativePlanningVisualizationStrategy, + // FinancialAnalysisVisualizationStrategy, ], }) -export class InfrastructureModule {} +export class InfrastructureModule { } diff --git a/src/utils.ts b/src/utils.ts index 92d86a2..cac2800 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -10,6 +10,7 @@ type FormatStringArgs = { suffix?: string; prefix?: string; }; + export default class Utils { static formatString({ value, suffix, prefix }: FormatStringArgs): string { if (null === value) { @@ -19,9 +20,8 @@ export default class Utils { if (typeof value === 'string' && value.includes('.')) { cbFn = parseFloat; } - return `${prefix ? prefix + ' ' : ''}${cbFn(value).toLocaleString('fr')}${ - suffix ? ' ' + suffix : '' - }`; + return `${prefix ? prefix + ' ' : ''}${cbFn(value).toLocaleString('fr')}${suffix ? ' ' + suffix : '' + }`; } static paginate(data: any[], pagination: any) {