diff --git a/.env.template b/.env.template index 7bc74e2c4..0e03eb9eb 100644 --- a/.env.template +++ b/.env.template @@ -1,2 +1,3 @@ -MY_URL="https://mydomain.com" -IP_WHITELIST="0.0.0.0/0" \ No newline at end of file +MY_URL="https://mydomain.com" # Backend URL +IP_WHITELIST="0.0.0.0/0" # Backend Whitelisted IP Addresses +DOCKER_IMAGE="ghcr.io/generalmagicio/qacc-be:staging" # For Production: ghcr.io/generalmagicio/qacc-be:main \ No newline at end of file diff --git a/.github/workflows/main-deploy-only.yml b/.github/workflows/main-deploy-only.yml index 5fbca1df7..5e344db4d 100644 --- a/.github/workflows/main-deploy-only.yml +++ b/.github/workflows/main-deploy-only.yml @@ -16,8 +16,109 @@ jobs: port: ${{ secrets.SSH_PORT }} script: | cd QAcc-BE + git reset --hard HEAD~1 git checkout main - git pull - docker compose -f docker-compose-production.yml pull - docker compose -f docker-compose-production.yml up --force-recreate -d + git pull origin main docker image prune -a --force + docker compose pull + + rollout-deploy-1: + needs: deploy + runs-on: ubuntu-latest + steps: + - name: SSH and Redeploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.PRODUCTION_HOST }} + username: ${{ secrets.PRODUCTION_USERNAME }} + key: ${{ secrets.PRODUCTION_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd QAcc-BE + ## Update each backend service one by one + ## First Deployment + docker compose down qacc-be-graph-ql1 + docker compose down qacc-be-job + docker compose up --force-recreate -d qacc-be-graph-ql1 + docker compose up --force-recreate -d qacc-be-job + + # Wait for qacc-be-graph-ql1 to be healthy (timeout after 5 minutes) + echo "Waiting for qacc-be-graph-ql1 to become healthy..." + timeout 300 bash -c 'until [ "$(docker inspect --format="{{json .State.Health.Status}}" qacc-be-graph-ql1)" == "\"healthy\"" ]; do echo "Waiting for qacc-be-graph-ql1 to be healthy..."; sleep 5; done' + if [ $? -eq 124 ]; then + echo "Timeout waiting for qacc-be-graph-ql1 to become healthy" + exit 1 + fi + # Check if qacc-be-graph-ql1 is healthy + if [ "$(docker inspect --format='{{json .State.Health.Status}}' qacc-be-graph-ql1)" != "\"healthy\"" ]; then + echo "qacc-be-graph-ql1 is not healthy, stopping deployment" + exit 1 + fi + # Check if qacc-be-job is running + if [ "$(docker inspect --format='{{json .State.Status}}' qacc-be-job)" != "\"running\"" ]; then + echo "qacc-be-job is not running, stopping deployment" + exit 1 + fi + echo "First deployment phase completed successfully" + + rollout-deploy-2: + needs: rollout-deploy-1 + runs-on: ubuntu-latest + steps: + - name: SSH and Redeploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.PRODUCTION_HOST }} + username: ${{ secrets.PRODUCTION_USERNAME }} + key: ${{ secrets.PRODUCTION_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd QAcc-BE + ## Second Deployment + docker compose down qacc-be-graph-ql2 + docker compose up --force-recreate -d qacc-be-graph-ql2 + + # Wait for qacc-be-graph-ql2 to be healthy (timeout after 5 minutes) + echo "Waiting for qacc-be-graph-ql2 to become healthy..." + timeout 300 bash -c 'until [ "$(docker inspect --format="{{json .State.Health.Status}}" qacc-be-graph-ql2)" == "\"healthy\"" ]; do echo "Waiting for qacc-be-graph-ql2 to be healthy..."; sleep 5; done' + if [ $? -eq 124 ]; then + echo "Timeout waiting for qacc-be-graph-ql2 to become healthy" + exit 1 + fi + # Check if qacc-be-graph-ql2 is healthy + if [ "$(docker inspect --format='{{json .State.Health.Status}}' qacc-be-graph-ql2)" != "\"healthy\"" ]; then + echo "qacc-be-graph-ql2 is not healthy, stopping deployment" + exit 1 + fi + echo "Second deployment phase completed successfully" + + rollout-deploy-3: + needs: rollout-deploy-2 + runs-on: ubuntu-latest + steps: + - name: SSH and Redeploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.PRODUCTION_HOST }} + username: ${{ secrets.PRODUCTION_USERNAME }} + key: ${{ secrets.PRODUCTION_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd QAcc-BE + ## Third Deployment + docker compose down qacc-be-graph-ql3 + docker compose up --force-recreate -d qacc-be-graph-ql3 + + # Wait for qacc-be-graph-ql3 to be healthy (timeout after 5 minutes) + echo "Waiting for qacc-be-graph-ql3 to become healthy..." + timeout 300 bash -c 'until [ "$(docker inspect --format="{{json .State.Health.Status}}" qacc-be-graph-ql3)" == "\"healthy\"" ]; do echo "Waiting for qacc-be-graph-ql3 to be healthy..."; sleep 5; done' + if [ $? -eq 124 ]; then + echo "Timeout waiting for qacc-be-graph-ql3 to become healthy" + exit 1 + fi + # Check if qacc-be-graph-ql3 is healthy + if [ "$(docker inspect --format='{{json .State.Health.Status}}' qacc-be-graph-ql3)" != "\"healthy\"" ]; then + echo "qacc-be-graph-ql3 is not healthy, stopping deployment" + exit 1 + fi + echo "First deployment phase completed successfully" \ No newline at end of file diff --git a/.github/workflows/main-pipeline.yml b/.github/workflows/main-pipeline.yml index 674dcde34..72b8cad1a 100644 --- a/.github/workflows/main-pipeline.yml +++ b/.github/workflows/main-pipeline.yml @@ -158,8 +158,109 @@ jobs: port: ${{ secrets.SSH_PORT }} script: | cd QAcc-BE + git reset --hard HEAD~1 git checkout main - git pull - docker compose -f docker-compose-production.yml pull - docker compose -f docker-compose-production.yml up -d + git pull origin main docker image prune -a --force + docker compose pull + + rollout-deploy-1: + needs: deploy + runs-on: ubuntu-latest + steps: + - name: SSH and Redeploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.PRODUCTION_HOST }} + username: ${{ secrets.PRODUCTION_USERNAME }} + key: ${{ secrets.PRODUCTION_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd QAcc-BE + ## Update each backend service one by one + ## First Deployment + docker compose down qacc-be-graph-ql1 + docker compose down qacc-be-job + docker compose up --force-recreate -d qacc-be-graph-ql1 + docker compose up --force-recreate -d qacc-be-job + + # Wait for qacc-be-graph-ql1 to be healthy (timeout after 5 minutes) + echo "Waiting for qacc-be-graph-ql1 to become healthy..." + timeout 300 bash -c 'until [ "$(docker inspect --format="{{json .State.Health.Status}}" qacc-be-graph-ql1)" == "\"healthy\"" ]; do echo "Waiting for qacc-be-graph-ql1 to be healthy..."; sleep 5; done' + if [ $? -eq 124 ]; then + echo "Timeout waiting for qacc-be-graph-ql1 to become healthy" + exit 1 + fi + # Check if qacc-be-graph-ql1 is healthy + if [ "$(docker inspect --format='{{json .State.Health.Status}}' qacc-be-graph-ql1)" != "\"healthy\"" ]; then + echo "qacc-be-graph-ql1 is not healthy, stopping deployment" + exit 1 + fi + # Check if qacc-be-job is running + if [ "$(docker inspect --format='{{json .State.Status}}' qacc-be-job)" != "\"running\"" ]; then + echo "qacc-be-job is not running, stopping deployment" + exit 1 + fi + echo "First deployment phase completed successfully" + + rollout-deploy-2: + needs: rollout-deploy-1 + runs-on: ubuntu-latest + steps: + - name: SSH and Redeploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.PRODUCTION_HOST }} + username: ${{ secrets.PRODUCTION_USERNAME }} + key: ${{ secrets.PRODUCTION_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd QAcc-BE + ## Second Deployment + docker compose down qacc-be-graph-ql2 + docker compose up --force-recreate -d qacc-be-graph-ql2 + + # Wait for qacc-be-graph-ql2 to be healthy (timeout after 5 minutes) + echo "Waiting for qacc-be-graph-ql2 to become healthy..." + timeout 300 bash -c 'until [ "$(docker inspect --format="{{json .State.Health.Status}}" qacc-be-graph-ql2)" == "\"healthy\"" ]; do echo "Waiting for qacc-be-graph-ql2 to be healthy..."; sleep 5; done' + if [ $? -eq 124 ]; then + echo "Timeout waiting for qacc-be-graph-ql2 to become healthy" + exit 1 + fi + # Check if qacc-be-graph-ql2 is healthy + if [ "$(docker inspect --format='{{json .State.Health.Status}}' qacc-be-graph-ql2)" != "\"healthy\"" ]; then + echo "qacc-be-graph-ql2 is not healthy, stopping deployment" + exit 1 + fi + echo "Second deployment phase completed successfully" + + rollout-deploy-3: + needs: rollout-deploy-2 + runs-on: ubuntu-latest + steps: + - name: SSH and Redeploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.PRODUCTION_HOST }} + username: ${{ secrets.PRODUCTION_USERNAME }} + key: ${{ secrets.PRODUCTION_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd QAcc-BE + ## Third Deployment + docker compose down qacc-be-graph-ql3 + docker compose up --force-recreate -d qacc-be-graph-ql3 + + # Wait for qacc-be-graph-ql3 to be healthy (timeout after 5 minutes) + echo "Waiting for qacc-be-graph-ql3 to become healthy..." + timeout 300 bash -c 'until [ "$(docker inspect --format="{{json .State.Health.Status}}" qacc-be-graph-ql3)" == "\"healthy\"" ]; do echo "Waiting for qacc-be-graph-ql3 to be healthy..."; sleep 5; done' + if [ $? -eq 124 ]; then + echo "Timeout waiting for qacc-be-graph-ql3 to become healthy" + exit 1 + fi + # Check if qacc-be-graph-ql3 is healthy + if [ "$(docker inspect --format='{{json .State.Health.Status}}' qacc-be-graph-ql3)" != "\"healthy\"" ]; then + echo "qacc-be-graph-ql3 is not healthy, stopping deployment" + exit 1 + fi + echo "First deployment phase completed successfully" \ No newline at end of file diff --git a/.github/workflows/main-publish-deploy.yml b/.github/workflows/main-publish-deploy.yml index a4bba2192..cfda9ad63 100644 --- a/.github/workflows/main-publish-deploy.yml +++ b/.github/workflows/main-publish-deploy.yml @@ -63,8 +63,109 @@ jobs: port: ${{ secrets.SSH_PORT }} script: | cd QAcc-BE + git reset --hard HEAD~1 git checkout main - git pull - docker compose -f docker-compose-production.yml pull - docker compose -f docker-compose-production.yml up --force-recreate -d + git pull origin main docker image prune -a --force + docker compose pull + + rollout-deploy-1: + needs: deploy + runs-on: ubuntu-latest + steps: + - name: SSH and Redeploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.PRODUCTION_HOST }} + username: ${{ secrets.PRODUCTION_USERNAME }} + key: ${{ secrets.PRODUCTION_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd QAcc-BE + ## Update each backend service one by one + ## First Deployment + docker compose down qacc-be-graph-ql1 + docker compose down qacc-be-job + docker compose up --force-recreate -d qacc-be-graph-ql1 + docker compose up --force-recreate -d qacc-be-job + + # Wait for qacc-be-graph-ql1 to be healthy (timeout after 5 minutes) + echo "Waiting for qacc-be-graph-ql1 to become healthy..." + timeout 300 bash -c 'until [ "$(docker inspect --format="{{json .State.Health.Status}}" qacc-be-graph-ql1)" == "\"healthy\"" ]; do echo "Waiting for qacc-be-graph-ql1 to be healthy..."; sleep 5; done' + if [ $? -eq 124 ]; then + echo "Timeout waiting for qacc-be-graph-ql1 to become healthy" + exit 1 + fi + # Check if qacc-be-graph-ql1 is healthy + if [ "$(docker inspect --format='{{json .State.Health.Status}}' qacc-be-graph-ql1)" != "\"healthy\"" ]; then + echo "qacc-be-graph-ql1 is not healthy, stopping deployment" + exit 1 + fi + # Check if qacc-be-job is running + if [ "$(docker inspect --format='{{json .State.Status}}' qacc-be-job)" != "\"running\"" ]; then + echo "qacc-be-job is not running, stopping deployment" + exit 1 + fi + echo "First deployment phase completed successfully" + + rollout-deploy-2: + needs: rollout-deploy-1 + runs-on: ubuntu-latest + steps: + - name: SSH and Redeploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.PRODUCTION_HOST }} + username: ${{ secrets.PRODUCTION_USERNAME }} + key: ${{ secrets.PRODUCTION_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd QAcc-BE + ## Second Deployment + docker compose down qacc-be-graph-ql2 + docker compose up --force-recreate -d qacc-be-graph-ql2 + + # Wait for qacc-be-graph-ql2 to be healthy (timeout after 5 minutes) + echo "Waiting for qacc-be-graph-ql2 to become healthy..." + timeout 300 bash -c 'until [ "$(docker inspect --format="{{json .State.Health.Status}}" qacc-be-graph-ql2)" == "\"healthy\"" ]; do echo "Waiting for qacc-be-graph-ql2 to be healthy..."; sleep 5; done' + if [ $? -eq 124 ]; then + echo "Timeout waiting for qacc-be-graph-ql2 to become healthy" + exit 1 + fi + # Check if qacc-be-graph-ql2 is healthy + if [ "$(docker inspect --format='{{json .State.Health.Status}}' qacc-be-graph-ql2)" != "\"healthy\"" ]; then + echo "qacc-be-graph-ql2 is not healthy, stopping deployment" + exit 1 + fi + echo "Second deployment phase completed successfully" + + rollout-deploy-3: + needs: rollout-deploy-2 + runs-on: ubuntu-latest + steps: + - name: SSH and Redeploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.PRODUCTION_HOST }} + username: ${{ secrets.PRODUCTION_USERNAME }} + key: ${{ secrets.PRODUCTION_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd QAcc-BE + ## Third Deployment + docker compose down qacc-be-graph-ql3 + docker compose up --force-recreate -d qacc-be-graph-ql3 + + # Wait for qacc-be-graph-ql3 to be healthy (timeout after 5 minutes) + echo "Waiting for qacc-be-graph-ql3 to become healthy..." + timeout 300 bash -c 'until [ "$(docker inspect --format="{{json .State.Health.Status}}" qacc-be-graph-ql3)" == "\"healthy\"" ]; do echo "Waiting for qacc-be-graph-ql3 to be healthy..."; sleep 5; done' + if [ $? -eq 124 ]; then + echo "Timeout waiting for qacc-be-graph-ql3 to become healthy" + exit 1 + fi + # Check if qacc-be-graph-ql3 is healthy + if [ "$(docker inspect --format='{{json .State.Health.Status}}' qacc-be-graph-ql3)" != "\"healthy\"" ]; then + echo "qacc-be-graph-ql3 is not healthy, stopping deployment" + exit 1 + fi + echo "First deployment phase completed successfully" \ No newline at end of file diff --git a/.github/workflows/staging-deploy-only.yml b/.github/workflows/staging-deploy-only.yml index ac245b7f6..4f781712f 100644 --- a/.github/workflows/staging-deploy-only.yml +++ b/.github/workflows/staging-deploy-only.yml @@ -16,8 +16,109 @@ jobs: port: ${{ secrets.SSH_PORT }} script: | cd QAcc-BE + git reset --hard HEAD~1 git checkout staging - git pull - docker compose -f docker-compose-staging.yml pull - docker compose -f docker-compose-staging.yml up --force-recreate -d + git pull origin staging docker image prune -a --force + docker compose pull + + rollout-deploy-1: + needs: deploy + runs-on: ubuntu-latest + steps: + - name: SSH and Redeploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.STAGING_HOST }} + username: ${{ secrets.STAGING_USERNAME }} + key: ${{ secrets.STAGING_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd QAcc-BE + ## Update each backend service one by one + ## First Deployment + docker compose down qacc-be-graph-ql1 + docker compose down qacc-be-job + docker compose up --force-recreate -d qacc-be-graph-ql1 + docker compose up --force-recreate -d qacc-be-job + + # Wait for qacc-be-graph-ql1 to be healthy (timeout after 5 minutes) + echo "Waiting for qacc-be-graph-ql1 to become healthy..." + timeout 300 bash -c 'until [ "$(docker inspect --format="{{json .State.Health.Status}}" qacc-be-graph-ql1)" == "\"healthy\"" ]; do echo "Waiting for qacc-be-graph-ql1 to be healthy..."; sleep 5; done' + if [ $? -eq 124 ]; then + echo "Timeout waiting for qacc-be-graph-ql1 to become healthy" + exit 1 + fi + # Check if qacc-be-graph-ql1 is healthy + if [ "$(docker inspect --format='{{json .State.Health.Status}}' qacc-be-graph-ql1)" != "\"healthy\"" ]; then + echo "qacc-be-graph-ql1 is not healthy, stopping deployment" + exit 1 + fi + # Check if qacc-be-job is running + if [ "$(docker inspect --format='{{json .State.Status}}' qacc-be-job)" != "\"running\"" ]; then + echo "qacc-be-job is not running, stopping deployment" + exit 1 + fi + echo "First deployment phase completed successfully" + + rollout-deploy-2: + needs: rollout-deploy-1 + runs-on: ubuntu-latest + steps: + - name: SSH and Redeploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.STAGING_HOST }} + username: ${{ secrets.STAGING_USERNAME }} + key: ${{ secrets.STAGING_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd QAcc-BE + ## Second Deployment + docker compose down qacc-be-graph-ql2 + docker compose up --force-recreate -d qacc-be-graph-ql2 + + # Wait for qacc-be-graph-ql2 to be healthy (timeout after 5 minutes) + echo "Waiting for qacc-be-graph-ql2 to become healthy..." + timeout 300 bash -c 'until [ "$(docker inspect --format="{{json .State.Health.Status}}" qacc-be-graph-ql2)" == "\"healthy\"" ]; do echo "Waiting for qacc-be-graph-ql2 to be healthy..."; sleep 5; done' + if [ $? -eq 124 ]; then + echo "Timeout waiting for qacc-be-graph-ql2 to become healthy" + exit 1 + fi + # Check if qacc-be-graph-ql2 is healthy + if [ "$(docker inspect --format='{{json .State.Health.Status}}' qacc-be-graph-ql2)" != "\"healthy\"" ]; then + echo "qacc-be-graph-ql2 is not healthy, stopping deployment" + exit 1 + fi + echo "Second deployment phase completed successfully" + + rollout-deploy-3: + needs: rollout-deploy-2 + runs-on: ubuntu-latest + steps: + - name: SSH and Redeploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.STAGING_HOST }} + username: ${{ secrets.STAGING_USERNAME }} + key: ${{ secrets.STAGING_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd QAcc-BE + ## Third Deployment + docker compose down qacc-be-graph-ql3 + docker compose up --force-recreate -d qacc-be-graph-ql3 + + # Wait for qacc-be-graph-ql3 to be healthy (timeout after 5 minutes) + echo "Waiting for qacc-be-graph-ql3 to become healthy..." + timeout 300 bash -c 'until [ "$(docker inspect --format="{{json .State.Health.Status}}" qacc-be-graph-ql3)" == "\"healthy\"" ]; do echo "Waiting for qacc-be-graph-ql3 to be healthy..."; sleep 5; done' + if [ $? -eq 124 ]; then + echo "Timeout waiting for qacc-be-graph-ql3 to become healthy" + exit 1 + fi + # Check if qacc-be-graph-ql3 is healthy + if [ "$(docker inspect --format='{{json .State.Health.Status}}' qacc-be-graph-ql3)" != "\"healthy\"" ]; then + echo "qacc-be-graph-ql3 is not healthy, stopping deployment" + exit 1 + fi + echo "First deployment phase completed successfully" \ No newline at end of file diff --git a/.github/workflows/staging-funding-pot-execution.yml b/.github/workflows/staging-funding-pot-execution.yml index 8fdf82133..302e526af 100644 --- a/.github/workflows/staging-funding-pot-execution.yml +++ b/.github/workflows/staging-funding-pot-execution.yml @@ -22,5 +22,5 @@ jobs: port: ${{ secrets.SSH_PORT }} script: | cd QAcc-BE - docker compose -f docker-compose-staging.yml exec qacc-be npm run execute:inverter:production - docker compose -f docker-compose-staging.yml logs qacc-be \ No newline at end of file + docker compose exec qacc-be-graph-ql1 npm run execute:inverter:production + docker compose logs qacc-be-graph-ql1 \ No newline at end of file diff --git a/.github/workflows/staging-pipeline.yml b/.github/workflows/staging-pipeline.yml index 9b050b20f..26fbe244a 100644 --- a/.github/workflows/staging-pipeline.yml +++ b/.github/workflows/staging-pipeline.yml @@ -158,8 +158,109 @@ jobs: port: ${{ secrets.SSH_PORT }} script: | cd QAcc-BE + git reset --hard HEAD~1 git checkout staging - git pull - docker compose -f docker-compose-staging.yml pull - docker compose -f docker-compose-staging.yml up -d + git pull origin staging docker image prune -a --force + docker compose pull + + rollout-deploy-1: + needs: deploy + runs-on: ubuntu-latest + steps: + - name: SSH and Redeploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.STAGING_HOST }} + username: ${{ secrets.STAGING_USERNAME }} + key: ${{ secrets.STAGING_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd QAcc-BE + ## Update each backend service one by one + ## First Deployment + docker compose down qacc-be-graph-ql1 + docker compose down qacc-be-job + docker compose up --force-recreate -d qacc-be-graph-ql1 + docker compose up --force-recreate -d qacc-be-job + + # Wait for qacc-be-graph-ql1 to be healthy (timeout after 5 minutes) + echo "Waiting for qacc-be-graph-ql1 to become healthy..." + timeout 300 bash -c 'until [ "$(docker inspect --format="{{json .State.Health.Status}}" qacc-be-graph-ql1)" == "\"healthy\"" ]; do echo "Waiting for qacc-be-graph-ql1 to be healthy..."; sleep 5; done' + if [ $? -eq 124 ]; then + echo "Timeout waiting for qacc-be-graph-ql1 to become healthy" + exit 1 + fi + # Check if qacc-be-graph-ql1 is healthy + if [ "$(docker inspect --format='{{json .State.Health.Status}}' qacc-be-graph-ql1)" != "\"healthy\"" ]; then + echo "qacc-be-graph-ql1 is not healthy, stopping deployment" + exit 1 + fi + # Check if qacc-be-job is running + if [ "$(docker inspect --format='{{json .State.Status}}' qacc-be-job)" != "\"running\"" ]; then + echo "qacc-be-job is not running, stopping deployment" + exit 1 + fi + echo "First deployment phase completed successfully" + + rollout-deploy-2: + needs: rollout-deploy-1 + runs-on: ubuntu-latest + steps: + - name: SSH and Redeploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.STAGING_HOST }} + username: ${{ secrets.STAGING_USERNAME }} + key: ${{ secrets.STAGING_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd QAcc-BE + ## Second Deployment + docker compose down qacc-be-graph-ql2 + docker compose up --force-recreate -d qacc-be-graph-ql2 + + # Wait for qacc-be-graph-ql2 to be healthy (timeout after 5 minutes) + echo "Waiting for qacc-be-graph-ql2 to become healthy..." + timeout 300 bash -c 'until [ "$(docker inspect --format="{{json .State.Health.Status}}" qacc-be-graph-ql2)" == "\"healthy\"" ]; do echo "Waiting for qacc-be-graph-ql2 to be healthy..."; sleep 5; done' + if [ $? -eq 124 ]; then + echo "Timeout waiting for qacc-be-graph-ql2 to become healthy" + exit 1 + fi + # Check if qacc-be-graph-ql2 is healthy + if [ "$(docker inspect --format='{{json .State.Health.Status}}' qacc-be-graph-ql2)" != "\"healthy\"" ]; then + echo "qacc-be-graph-ql2 is not healthy, stopping deployment" + exit 1 + fi + echo "Second deployment phase completed successfully" + + rollout-deploy-3: + needs: rollout-deploy-2 + runs-on: ubuntu-latest + steps: + - name: SSH and Redeploy + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.STAGING_HOST }} + username: ${{ secrets.STAGING_USERNAME }} + key: ${{ secrets.STAGING_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd QAcc-BE + ## Third Deployment + docker compose down qacc-be-graph-ql3 + docker compose up --force-recreate -d qacc-be-graph-ql3 + + # Wait for qacc-be-graph-ql3 to be healthy (timeout after 5 minutes) + echo "Waiting for qacc-be-graph-ql3 to become healthy..." + timeout 300 bash -c 'until [ "$(docker inspect --format="{{json .State.Health.Status}}" qacc-be-graph-ql3)" == "\"healthy\"" ]; do echo "Waiting for qacc-be-graph-ql3 to be healthy..."; sleep 5; done' + if [ $? -eq 124 ]; then + echo "Timeout waiting for qacc-be-graph-ql3 to become healthy" + exit 1 + fi + # Check if qacc-be-graph-ql3 is healthy + if [ "$(docker inspect --format='{{json .State.Health.Status}}' qacc-be-graph-ql3)" != "\"healthy\"" ]; then + echo "qacc-be-graph-ql3 is not healthy, stopping deployment" + exit 1 + fi + echo "First deployment phase completed successfully" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6b4cb4b3e..232a5390e 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,5 @@ src/scripts/*.json ./src/server/adminJs/adminjs .DS_Store -.caddy.env \ No newline at end of file +.caddy.env +.env \ No newline at end of file diff --git a/Caddyfile b/Caddyfile index e189ecb0c..8cc0a9cf9 100644 --- a/Caddyfile +++ b/Caddyfile @@ -1,3 +1,17 @@ {$MY_URL} { - reverse_proxy qacc-be:4000 + route { + @allowed { + path /* + remote_ip {$IP_WHITELIST} + } + reverse_proxy @allowed { + to qacc-be-graph-ql1:4000 qacc-be-graph-ql2:4000 qacc-be-graph-ql3:4000 + lb_policy round_robin + health_uri /health + health_interval 5s + health_timeout 2s + health_status 200 + } + respond 403 + } } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 5ef323411..c6b708a58 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ COPY tsconfig.json . RUN apk add --update alpine-sdk -RUN apk add git python3 +RUN apk add git python3 curl RUN apk add --no-cache chromium --repository=http://dl-cdn.alpinelinux.org/alpine/v3.18/main RUN npm ci RUN npm i -g ts-node diff --git a/config/example.env b/config/example.env index ea69564e4..90c43da09 100644 --- a/config/example.env +++ b/config/example.env @@ -300,4 +300,8 @@ ANKR_API_KEY_FOR_FUNDING_POT= # Sync donations with ankr ENABLE_ANKR_SYNC= ANKR_RPC_URL= -ANKR_SYNC_CRONJOB_EXPRESSION= \ No newline at end of file +ANKR_SYNC_CRONJOB_EXPRESSION= + +# Reports database +MONGO_DB_URI= +MONGO_DB_REPORT_DB_NAME= diff --git a/docker-compose-local.yml b/docker-compose-local.yml index dcba85772..7d5a9fe3e 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -93,12 +93,31 @@ services: ports: - '6379:6379' + qacc-mongo: + container_name: qacc-mongo + image: mongo:latest + profiles: + - server + - database + - local + restart: always + environment: + - MONGO_INITDB_ROOT_USERNAME=admin + - MONGO_INITDB_ROOT_PASSWORD=password + volumes: + - mongo-data:/data/db + networks: + - qacc + ports: + - '27017:27017' + volumes: db-data: db-data-test: redis-data: + mongo-data: networks: qacc: name: qacc-be - external: true + external: true \ No newline at end of file diff --git a/docker-compose-production.yml b/docker-compose-production.yml deleted file mode 100644 index 9f465e949..000000000 --- a/docker-compose-production.yml +++ /dev/null @@ -1,76 +0,0 @@ -services: - qacc-be-graph-ql: - container_name: qacc-be-graph-ql - image: ghcr.io/generalmagicio/qacc-be:main - command: npm run start:docker:server - environment: - - ENVIRONMENT=production - - LOG_PATH=/usr/src/app/logs/qacc.log - - GRAPHQL_MODE=true - restart: always - volumes: - - ./config:/usr/src/app/config - - ./config:/usr/src/app/build/config - - ./logs-graph-ql:/usr/src/app/logs - networks: - - qacc - ports: - - '4001:4000' - - qacc-be-job: - container_name: qacc-be-job - image: ghcr.io/generalmagicio/qacc-be:main - command: npm run start:docker:server - environment: - - ENVIRONMENT=production - - LOG_PATH=/usr/src/app/logs/qacc.log - - JOB_MODE=true - restart: always - volumes: - - ./config:/usr/src/app/config - - ./config:/usr/src/app/build/config - - ./logs-job:/usr/src/app/logs - networks: - - qacc - - qacc-redis: - container_name: qacc-redis - image: redis:7-alpine - environment: - - REDIS_ALLOW_EMPTY_PASSWORD=yes - restart: always - volumes: - - redis-data:/data - networks: - - qacc - ports: - - '6379:6379' - - caddy: - image: caddy:2-alpine - container_name: caddy - restart: unless-stopped - networks: - - qacc - ports: - - 80:80 - - 443:443 - env_file: - - .env - environment: - - MY_URL=${MY_URL:-} - - IP_WHITELIST=${IP_WHITELIST:-} - volumes: - - caddy_data:/data - - caddy_config:/config - - ./Caddyfile:/etc/caddy/Caddyfile - -volumes: - redis-data: - caddy_config: - caddy_data: - -networks: - qacc: - name: qacc-be_qacc - external: true diff --git a/docker-compose-staging.yml b/docker-compose-staging.yml deleted file mode 100644 index 4b167184d..000000000 --- a/docker-compose-staging.yml +++ /dev/null @@ -1,76 +0,0 @@ -services: - qacc-be-graph-ql: - container_name: qacc-be-graph-ql - image: ghcr.io/generalmagicio/qacc-be:staging - command: npm run start:docker:server - environment: - - ENVIRONMENT=production - - LOG_PATH=/usr/src/app/logs/qacc.log - - GRAPHQL_MODE=true - restart: always - volumes: - - ./config:/usr/src/app/config - - ./config:/usr/src/app/build/config - - ./logs-graph-ql:/usr/src/app/logs - networks: - - qacc - ports: - - '4001:4000' - - qacc-be-job: - container_name: qacc-be-job - image: ghcr.io/generalmagicio/qacc-be:staging - command: npm run start:docker:server - environment: - - ENVIRONMENT=production - - LOG_PATH=/usr/src/app/logs/qacc.log - - JOB_MODE=true - restart: always - volumes: - - ./config:/usr/src/app/config - - ./config:/usr/src/app/build/config - - ./logs-job:/usr/src/app/logs - networks: - - qacc - - qacc-redis: - container_name: qacc-redis - image: redis:7-alpine - environment: - - REDIS_ALLOW_EMPTY_PASSWORD=yes - restart: always - volumes: - - redis-data:/data - networks: - - qacc - ports: - - '6379:6379' - - caddy: - image: caddy:2-alpine - container_name: caddy - restart: unless-stopped - networks: - - qacc - ports: - - 80:80 - - 443:443 - env_file: - - .env - environment: - - MY_URL=${MY_URL:-} - - IP_WHITELIST=${IP_WHITELIST:-} - volumes: - - caddy_data:/data - - caddy_config:/config - - ./Caddyfile:/etc/caddy/Caddyfile - -volumes: - redis-data: - caddy_config: - caddy_data: - -networks: - qacc: - name: qacc-be_qacc - external: true diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..429162a52 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,150 @@ +services: + qacc-be-graph-ql1: + container_name: qacc-be-graph-ql1 + image: ${DOCKER_IMAGE} + command: npm run start:docker:server-graphql + environment: + - ENVIRONMENT=production + - LOG_PATH=/usr/src/app/logs/qacc.log + - GRAPHQL_MODE=true + restart: always + labels: + - 'autoheal=true' + volumes: + - ./config:/usr/src/app/config + - ./config:/usr/src/app/build/config + - ./logs-graph-ql:/usr/src/app/logs + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:4000/health'] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s + networks: + - qacc + ports: + - '4001:4000' + + qacc-be-graph-ql2: + container_name: qacc-be-graph-ql2 + image: ${DOCKER_IMAGE} + command: npm run start:docker:server-graphql + environment: + - ENVIRONMENT=production + - LOG_PATH=/usr/src/app/logs/qacc.log + - GRAPHQL_MODE=true + restart: always + labels: + - 'autoheal=true' + volumes: + - ./config:/usr/src/app/config + - ./config:/usr/src/app/build/config + - ./logs-graph-ql:/usr/src/app/logs + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:4000/health'] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s + networks: + - qacc + ports: + - '4002:4000' + + qacc-be-graph-ql3: + container_name: qacc-be-graph-ql3 + image: ${DOCKER_IMAGE} + command: npm run start:docker:server-graphql + environment: + - ENVIRONMENT=production + - LOG_PATH=/usr/src/app/logs/qacc.log + - GRAPHQL_MODE=true + restart: always + labels: + - 'autoheal=true' + volumes: + - ./config:/usr/src/app/config + - ./config:/usr/src/app/build/config + - ./logs-graph-ql:/usr/src/app/logs + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:4000/health'] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s + networks: + - qacc + ports: + - '4003:4000' + + qacc-be-job: + container_name: qacc-be-job + image: ${DOCKER_IMAGE} + command: npm run start:docker:server-job + environment: + - ENVIRONMENT=production + - LOG_PATH=/usr/src/app/logs/qacc.log + - JOB_MODE=true + restart: always + volumes: + - ./config:/usr/src/app/config + - ./config:/usr/src/app/build/config + - ./logs-job:/usr/src/app/logs + networks: + - qacc + + qacc-redis: + container_name: qacc-redis + image: redis:7-alpine + environment: + - REDIS_ALLOW_EMPTY_PASSWORD=yes + restart: always + volumes: + - redis-data:/data + networks: + - qacc + ports: + - '6379:6379' + + autoheal: + container_name: autoheal + image: willfarrell/autoheal:latest + tty: true + restart: always + environment: + - AUTOHEAL_INTERVAL=30 + - AUTOHEAL_START_PERIOD=300 + - AUTOHEAL_DEFAULT_STOP_TIMEOUT=10 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + networks: + - qacc + + caddy: + image: caddy:2-alpine + container_name: caddy + restart: unless-stopped + networks: + - qacc + ports: + - 80:80 + - 443:443 + env_file: + - .env + environment: + - MY_URL=${MY_URL:-} + - IP_WHITELIST=${IP_WHITELIST:-} + volumes: + - caddy_data:/data + - caddy_config:/config + - ./Caddyfile:/etc/caddy/Caddyfile + +volumes: + redis-data: + caddy_config: + caddy_data: + +networks: + qacc: + name: qacc-be_qacc + external: true diff --git a/migration/1730852418249-InsertEARounds.ts b/migration/1730852418249-InsertEARounds.ts new file mode 100644 index 000000000..4be5d819c --- /dev/null +++ b/migration/1730852418249-InsertEARounds.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class InsertEARounds1730852418249 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + INSERT INTO "early_access_round" ( + "roundNumber", "startDate", "endDate", "roundUSDCapPerProject", "roundUSDCapPerUserPerProject", "tokenPrice", "isBatchMintingExecuted" + ) VALUES + (1, '2024-11-06 12:00:00', '2024-11-11 12:00:00', 100000, 5000, null, false), + (2, '2024-11-11 12:00:00', '2024-11-16 12:00:00', 200000, 10000, null, false), + (3, '2024-11-16 12:00:00', '2024-11-21 12:00:00', 200000, 10000, null, false) + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + DELETE FROM "early_access_round" WHERE "roundNumber" IN (1, 2, 3) + `); + } +} diff --git a/migration/1730852760371-InsertQAccRound.ts b/migration/1730852760371-InsertQAccRound.ts new file mode 100644 index 000000000..56b43f1bb --- /dev/null +++ b/migration/1730852760371-InsertQAccRound.ts @@ -0,0 +1,47 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class InsertQAccRound1730852760371 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + INSERT INTO "qf_round" ( + "roundNumber", + "name", + "title", + "description", + "slug", + "beginDate", + "endDate", + "tokenPrice", + "roundUSDCapPerProject", + "roundUSDCloseCapPerProject", + "roundUSDCapPerUserPerProject", + "isBatchMintingExecuted", + "isActive", + "allocatedFund", + "minimumPassportScore" + ) VALUES ( + 1, + 'QAcc first round', + 'First QAcc round', + 'This is the first QAcc round', + 'round-1', + '2024-11-25 12:00:00', + '2024-12-09 12:00:00', + NULL, + 1000000, + 1050000, + 2500, + false, + true, + 1000000, + 0 + ) + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + DELETE FROM "qf_round" WHERE "roundNumber" = 1 + `); + } +} diff --git a/package.json b/package.json index 9cd290d0e..f8b8300d9 100644 --- a/package.json +++ b/package.json @@ -202,7 +202,8 @@ "build": "rm -rf ./build && tsc && mkdir ./build/config && mkdir ./build/src/server/adminJs/tabs/components && cp -r src/server/adminJs/tabs/components/* ./build/src/server/adminJs/tabs/components/ && mkdir ./build/src/utils/locales && cp -r ./src/utils/locales/* ./build/src/utils/locales/ && cp -r ./src/abi build/src/abi ", "dev": "NODE_ENV=development node ./build/src/index.js", "production": "NODE_ENV=production node ./build/src/index.js", - "start:docker:server": "npm run db:migrate:run:production && npm run production", + "start:docker:server-job": "npm run db:migrate:run:production && npm run production", + "start:docker:server-graphql": "npm run production", "start:docker:locally": "npm run db:migrate:run:local && npm run dev", "postinstall": "patch-package", "sync:inverter:test": "NODE_ENV=test node ./build/src/scripts/runScript.js", diff --git a/src/scripts/configs.ts b/src/scripts/configs.ts index 9640f181e..34114d40c 100644 --- a/src/scripts/configs.ts +++ b/src/scripts/configs.ts @@ -19,3 +19,5 @@ export function getReportsSubDir() { reportsSubDir += '/output'; return reportsSubDir; } + +export const reportFilesDir = path.join(repoLocalDir, getReportsSubDir()); diff --git a/src/scripts/reportService.ts b/src/scripts/reportService.ts new file mode 100644 index 000000000..30dbc1b62 --- /dev/null +++ b/src/scripts/reportService.ts @@ -0,0 +1,89 @@ +/* eslint-disable no-console */ +import fs from 'fs'; +import path from 'path'; +import { MongoClient } from 'mongodb'; +import config from '../config'; + +const mongoUri = config.get('MONGO_DB_URI') as string; +const dbName = config.get('MONGO_DB_REPORT_DB_NAME') as string; +const collectionName = 'reports'; + +// Function to save all reports in the MongoDB +export async function saveReportsToDB(outputFolderPath: string) { + const client = new MongoClient(mongoUri); + console.info('Connecting to mongo db ...'); + await client.connect(); + console.info('Connected to mongo db successfully.'); + const db = client.db(dbName); + const collection = db.collection(collectionName); + + // Traverse the output directory and read files + const traverseDirectory = async (dir: string) => { + const files = await fs.promises.readdir(dir, { withFileTypes: true }); + + for (const file of files) { + const filePath = path.join(dir, file.name); + + if (file.isDirectory()) { + // Recursively traverse subdirectories + await traverseDirectory(filePath); + } else if (file.isFile() && path.extname(file.name) === '.json') { + // Read the file content + const content = await fs.promises.readFile(filePath, 'utf8'); + const projectPath = path.relative(outputFolderPath, filePath); + + // Upsert file data and relative path to MongoDB + await collection.updateOne( + { projectPath }, // Query to check for existing record with the same projectPath + { + $set: { + projectPath, + content, + createdAt: new Date(), + }, + }, + { upsert: true }, // Insert if no document matches the query + ); + + console.info( + `Saved ${file.name} to MongoDB with path ${path.relative(outputFolderPath, filePath)}`, + ); + } + } + }; + + await traverseDirectory(outputFolderPath); + await client.close(); + console.info('All reports have been saved to MongoDB.'); +} + +// Function to retrieve all reports from MongoDB and recreate the folder structure +export async function restoreReportsFromDB(outputFolderPath: string) { + const client = new MongoClient(mongoUri); + console.info('Connecting to mongo db ...'); + await client.connect(); + console.info('Connected to mongo db successfully.'); + const db = client.db(dbName); + const collection = db.collection(collectionName); + + // Retrieve all reports from MongoDB + const reportsCursor = collection.find(); + const reports = await reportsCursor.toArray(); + + // Restore the file structure and save each report + for (const report of reports) { + const restoredFilePath = path.join(outputFolderPath, report.projectPath); + + // Ensure the directory exists + await fs.promises.mkdir(path.dirname(restoredFilePath), { + recursive: true, + }); + + // Write the content to the file + await fs.promises.writeFile(restoredFilePath, report.content, 'utf8'); + console.info(`Restored report to ${restoredFilePath}`); + } + + await client.close(); + console.info('All reports have been restored from MongoDB.'); +} diff --git a/src/scripts/runFundingPotService.ts b/src/scripts/runFundingPotService.ts index fb6c29401..42f5dc04f 100644 --- a/src/scripts/runFundingPotService.ts +++ b/src/scripts/runFundingPotService.ts @@ -3,7 +3,7 @@ import { spawn } from 'child_process'; import path from 'path'; import fs from 'fs-extra'; import simpleGit from 'simple-git'; -import { repoLocalDir, repoUrl } from './configs'; +import { repoLocalDir, reportFilesDir, repoUrl } from './configs'; import config from '../config'; import { Project } from '../entities/project'; import { AppDataSource } from '../orm'; @@ -17,6 +17,7 @@ import { EarlyAccessRound } from '../entities/earlyAccessRound'; import { findAllEarlyAccessRounds } from '../repositories/earlyAccessRoundRepository'; import { findQfRounds } from '../repositories/qfRoundRepository'; import { updateRewardsForDonations } from './syncDataWithJsonReport'; +import { restoreReportsFromDB, saveReportsToDB } from './reportService'; // Attention: the configs of batches should be saved in the funding pot repo // this script pulls the latest version of funding pot service, @@ -223,7 +224,13 @@ async function installDependencies() { async function runFundingPotService(batchNumber: number) { const command = 'npm run all ' + batchNumber; console.info(`Running "${command}" in ${serviceDir}...`); - await execShellCommand(command, serviceDir); + try { + await execShellCommand(command, serviceDir); + } catch (e) { + console.error('Error in funding pot execution:', e); + } + console.info('Saving reports to the DB...'); + await saveReportsToDB(reportFilesDir); } async function getFirstRoundThatNeedExecuteBatchMinting() { @@ -341,16 +348,21 @@ async function main() { console.info('Env file created successfully.'); // Step 5 + console.info('Restoring previous report files...'); + await restoreReportsFromDB(reportFilesDir); + console.info('Previous report files restored successfully!'); + + // Step 6 console.info('Running funding pot service...'); await runFundingPotService(batchNumber); console.info('Funding pot service executed successfully!'); - // Step 6 + // Step 7 console.info('Setting batch minting execution flag in round data...'); await setBatchMintingExecutionFlag(batchNumber); console.info('Batch minting execution flag set successfully.'); - // Step 7 + // Step 8 console.info('Start Syncing reward data in donations...'); await updateRewardsForDonations(batchNumber); console.info('Rewards data synced successfully.'); diff --git a/src/scripts/syncDataWithJsonReport.ts b/src/scripts/syncDataWithJsonReport.ts index 505502c81..cea6c1ac2 100644 --- a/src/scripts/syncDataWithJsonReport.ts +++ b/src/scripts/syncDataWithJsonReport.ts @@ -7,7 +7,7 @@ import { Donation } from '../entities/donation'; import { Project } from '../entities/project'; import { AppDataSource } from '../orm'; import { getStreamDetails } from './helpers'; -import { repoLocalDir, getReportsSubDir } from './configs'; +import { reportFilesDir } from './configs'; async function loadReportFile(filePath: string) { try { @@ -48,63 +48,73 @@ async function processReportForDonations( ); for (const donation of donations) { - const participantData = - lowerCasedParticipants[donation.fromWalletAddress.toLowerCase()]; + try { + const participantData = + lowerCasedParticipants[donation.fromWalletAddress.toLowerCase()]; - if (!participantData) { - console.error(`No participant data found for donation ${donation.id}`); - continue; - } - - const totalValidContribution = ethers.BigNumber.from( - participantData.validContribution.inCollateral, - ); - // if issuance allocation is not exist, that mean this user has not any valid contributions - let rewardAmount = 0; - if (participantData.issuanceAllocation) { - const issuanceAllocationRow = ethers.BigNumber.from( - participantData.issuanceAllocation, - ); - const issuanceAllocation = parseFloat( - ethers.utils.formatUnits(issuanceAllocationRow, 18), - ); // Assuming 18 decimal places - - const donationTransaction = participantData.transactions.find( - (tx: any) => - tx.transactionHash.toLowerCase() === - donation.transactionId.toLowerCase(), - ); - - if (!donationTransaction) { + if (!participantData) { console.error( - `No transaction data found for donation ${donation.id}`, + `No participant data found for donation ${donation.id}`, ); continue; } - const donationValidContribution = ethers.BigNumber.from( - donationTransaction.validContribution, + const totalValidContribution = ethers.BigNumber.from( + participantData.validContribution.inCollateral, ); - const contributionPercentage = - parseFloat(ethers.utils.formatUnits(donationValidContribution, 18)) / - parseFloat(ethers.utils.formatUnits(totalValidContribution, 18)); + // if issuance allocation is not exist, that mean this user has not any valid contributions + let rewardAmount = 0; + if (participantData.issuanceAllocation) { + const issuanceAllocationRow = ethers.BigNumber.from( + participantData.issuanceAllocation, + ); + const issuanceAllocation = parseFloat( + ethers.utils.formatUnits(issuanceAllocationRow, 18), + ); // Assuming 18 decimal places + + const donationTransaction = participantData.transactions.find( + (tx: any) => + tx.transactionHash.toLowerCase() === + donation.transactionId.toLowerCase(), + ); - // Calculate the reward proportionally based on the valid contribution - rewardAmount = issuanceAllocation * contributionPercentage; - } - donation.rewardTokenAmount = rewardAmount || 0; + if (!donationTransaction) { + console.error( + `No transaction data found for donation ${donation.id}`, + ); + continue; + } - const isEarlyAccessRound = reportData.batch.config.isEarlyAccess; - const vestingInfo = getStreamDetails(isEarlyAccessRound); + const donationValidContribution = ethers.BigNumber.from( + donationTransaction.validContribution, + ); + const contributionPercentage = + parseFloat( + ethers.utils.formatUnits(donationValidContribution, 18), + ) / + parseFloat(ethers.utils.formatUnits(totalValidContribution, 18)); + + // Calculate the reward proportionally based on the valid contribution + rewardAmount = issuanceAllocation * contributionPercentage; + } + donation.rewardTokenAmount = rewardAmount || 0; - donation.cliff = vestingInfo.CLIFF * 1000; - donation.rewardStreamStart = new Date(vestingInfo.START * 1000); - donation.rewardStreamEnd = new Date(vestingInfo.END * 1000); + const isEarlyAccessRound = reportData.batch.config.isEarlyAccess; + const vestingInfo = getStreamDetails(isEarlyAccessRound); - await donation.save(); - console.debug( - `Reward data for donation ${donation.id} successfully updated`, - ); + donation.cliff = vestingInfo.CLIFF * 1000; + donation.rewardStreamStart = new Date(vestingInfo.START * 1000); + donation.rewardStreamEnd = new Date(vestingInfo.END * 1000); + + await donation.save(); + console.debug( + `Reward data for donation ${donation.id} successfully updated`, + ); + } catch (e) { + console.error( + `Failed to process donations rewards for project ${donations[0].projectId} and donation ${donation.id}: ${e.message}`, + ); + } } } catch (error) { console.error( @@ -127,7 +137,6 @@ export async function updateRewardsForDonations(batchNumber: number) { const donationsByProjectId = _.groupBy(donations, 'projectId'); - const reportFilesDir = path.join(repoLocalDir, getReportsSubDir()); const allReportFiles = getAllReportFiles(reportFilesDir); for (const projectId of Object.keys(donationsByProjectId)) { @@ -196,8 +205,12 @@ async function updateNumberOfBatchMintingTransactionsForProject( if (transactions.length > 0) { if (!project.batchNumbersWithSafeTransactions) { project.batchNumbersWithSafeTransactions = [batchNumber]; - } else { + } else if ( + !project.batchNumbersWithSafeTransactions.includes(batchNumber) + ) { project.batchNumbersWithSafeTransactions.push(batchNumber); + } else { + return; } await project.save(); }