From aa38ed9ec98df66f6ea5e515c424a39482acea36 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 May 2022 14:56:29 +0200 Subject: [PATCH 01/46] chore(deps): update docker/login-action action to v2 (#311) Co-authored-by: Renovate Bot --- .github/workflows/publish-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index 941ed5b1..5e4c442c 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -60,7 +60,7 @@ jobs: uses: docker/setup-buildx-action@f211e3e9ded2d9377c8cadc4489a4e38014bc4c9 # tag=v1.7.0 - name: Login to GitHub Container Registry - uses: docker/login-action@dd4fa0671be5250ee6f50aedf4cb05514abda2c7 # tag=v1.14.1 + uses: docker/login-action@49ed152c8eca782a232dede0303416e8f356c37b # tag=v2.0.0 with: registry: ${{ env.REGISTRY_URL }} username: ${{ github.repository_owner }} From fe62fc97bd5b7413494c62d73d404a54a191b250 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 May 2022 14:56:51 +0200 Subject: [PATCH 02/46] chore(deps): update docker/build-push-action action to v3 (#310) Co-authored-by: Renovate Bot --- .github/workflows/publish-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index 5e4c442c..c178e68e 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -67,7 +67,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push image - uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a # tag=v2.10.0 + uses: docker/build-push-action@e551b19e49efd4e98792db7592c17c09b89db8d8 # tag=v3.0.0 with: push: true context: . From 84c7d54d3b7eddc1b81f692e836aed0c71e81215 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 May 2022 14:57:15 +0200 Subject: [PATCH 03/46] chore(deps): update docker/setup-buildx-action action to v2 (#312) Co-authored-by: Renovate Bot --- .github/workflows/publish-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index c178e68e..4e4d9eff 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -57,7 +57,7 @@ jobs: fetch-depth: 0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f211e3e9ded2d9377c8cadc4489a4e38014bc4c9 # tag=v1.7.0 + uses: docker/setup-buildx-action@dc7b9719a96d48369863986a06765841d7ea23f6 # tag=v2.0.0 - name: Login to GitHub Container Registry uses: docker/login-action@49ed152c8eca782a232dede0303416e8f356c37b # tag=v2.0.0 From 2216d54bc2e652fefe708bdc77ac8d410a44d2a5 Mon Sep 17 00:00:00 2001 From: Guido de Jong <35309288+guidojw@users.noreply.github.com> Date: Wed, 18 May 2022 15:10:35 +0200 Subject: [PATCH 04/46] fix(README): remove badge (#305) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index c9aa69e7..17e16f4a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ Alpha AMBER API ================ [![Continuous Integration](https://github.com/csvalpha/amber-api/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/csvalpha/amber-api/actions/workflows/continuous-integration.yml) [![Continuous Delivery](https://github.com/csvalpha/amber-api/actions/workflows/continuous-delivery.yml/badge.svg)](https://github.com/csvalpha/amber-api/actions/workflows/continuous-delivery.yml) -[![Depfu](https://badges.depfu.com/badges/663adb8e75ff19a32ca0d866d5fe2e85/count.svg)](https://depfu.com/github/csvalpha/amber-api?project_id=7749) ## Prerequisites If you're going to run the project with Docker, you only need to install the following prerequisites: From 2139cf8076f1102a63d7f88734b7ff30b0228675 Mon Sep 17 00:00:00 2001 From: Guido de Jong <35309288+guidojw@users.noreply.github.com> Date: Wed, 18 May 2022 15:32:56 +0200 Subject: [PATCH 05/46] chore: update postgres in actions too (#307) --- .github/workflows/continuous-integration.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index ed4feb07..aeec22b1 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -39,7 +39,10 @@ jobs: needs: build services: db: - image: postgres:11.2 + image: postgres:14.2 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres options: >- --health-cmd pg_isready --health-interval 10s @@ -73,8 +76,8 @@ jobs: run: | EXIT_STATUS=0 ./actionlint -ignore 'property "app_private_key" is not defined' -ignore 'SC2153:' || EXIT_STATUS=$? - docker run -e POSTGRES_USER=postgres -e POSTGRES_HOST=localhost -e RAILS_MASTER_KEY --network=host app \ - bin/ci.sh lint || EXIT_STATUS=$? + docker run -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_HOST=localhost -e \ + RAILS_MASTER_KEY --network=host app bin/ci.sh lint || EXIT_STATUS=$? exit $EXIT_STATUS test: @@ -83,7 +86,10 @@ jobs: needs: build services: db: - image: postgres:11.2 + image: postgres:14.2 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres options: >- --health-cmd pg_isready --health-interval 10s @@ -110,5 +116,5 @@ jobs: env: RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} run: | - docker run -e POSTGRES_USER=postgres -e POSTGRES_HOST=localhost -e RAILS_MASTER_KEY --network=host app \ - bin/ci.sh spec + docker run -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_HOST=localhost -e \ + RAILS_MASTER_KEY --network=host app bin/ci.sh spec From 76684370187ea6b4e570517347c599abef80389f Mon Sep 17 00:00:00 2001 From: Wilco Date: Thu, 19 May 2022 18:24:44 +0200 Subject: [PATCH 06/46] Fix (deprecation) warnings (#313) --- app/controllers/application_controller.rb | 2 +- app/models/board_room_presence.rb | 2 -- config/initializers/rack_attack.rb | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b4b9cd3f..f710b923 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,5 @@ class ApplicationController < JSONAPI::ResourceController - include Pundit + include Pundit::Authorization before_action :set_paper_trail_whodunnit before_action :set_raven_context diff --git a/app/models/board_room_presence.rb b/app/models/board_room_presence.rb index 117756aa..48c2abdd 100644 --- a/app/models/board_room_presence.rb +++ b/app/models/board_room_presence.rb @@ -1,6 +1,4 @@ class BoardRoomPresence < ApplicationRecord - acts_as_paranoid - belongs_to :user validates :start_time, presence: true diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index de7a00b0..a9aa9652 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -25,9 +25,9 @@ class Attack req.params['username'].presence if req.path == '/oauth/token' end - self.throttled_response = lambda do |env| + self.throttled_responder = lambda do |req| now = Time.zone.now - match_data = env['rack.attack.match_data'] + match_data = req.env['rack.attack.match_data'] headers = { 'X-RateLimit-Limit' => match_data[:limit].to_s, From 6e02d171bfb7a0493415e1c01321565d5db10031 Mon Sep 17 00:00:00 2001 From: Guido de Jong <35309288+guidojw@users.noreply.github.com> Date: Fri, 22 Jul 2022 15:50:28 +0200 Subject: [PATCH 07/46] fix: pin actionlint to v1.6.15 (#318) --- .github/workflows/continuous-integration.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index aeec22b1..82d8e234 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -63,7 +63,7 @@ jobs: - name: Download actionlint run: | - bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) + bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.6.15 - name: Load test image uses: guidojw/actions/load-docker-image@92f1fbb6058a84135e953d0d49462bccf3e6336a # tag=v1.2.0 @@ -75,7 +75,8 @@ jobs: RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} run: | EXIT_STATUS=0 - ./actionlint -ignore 'property "app_private_key" is not defined' -ignore 'SC2153:' || EXIT_STATUS=$? + ./actionlint -ignore 'property "app_private_key" is not defined' -ignore 'SC2153:' \ + -ignore 'property "sha" is not defined in object type {}' || EXIT_STATUS=$? docker run -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_HOST=localhost -e \ RAILS_MASTER_KEY --network=host app bin/ci.sh lint || EXIT_STATUS=$? exit $EXIT_STATUS From 4ef44995ce4b7128f5deca9f4c520ee56faac43d Mon Sep 17 00:00:00 2001 From: Wilco Date: Mon, 22 Aug 2022 14:55:54 +0200 Subject: [PATCH 08/46] Fix SSH host after cloudflare setup (#321) --- .github/workflows/continuous-delivery.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index dce36a7e..ef05d5c6 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -155,7 +155,7 @@ jobs: env: STAGE: ${{ needs.metadata.outputs.stage }} with: - host: csvalpha.nl + host: ssh.csvalpha.nl username: github-actions key: ${{ secrets.SSH_PRIVATE_KEY }} envs: PROJECT_NAME,STAGE From 0c1292e7df6fa74869231761d75796a6e294631e Mon Sep 17 00:00:00 2001 From: Guido de Jong <35309288+guidojw@users.noreply.github.com> Date: Thu, 25 Aug 2022 12:51:37 +0200 Subject: [PATCH 09/46] refactor: ignore bundle vulnerabilities (#325) --- bin/ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/ci.sh b/bin/ci.sh index 4df6ba95..d728ca5d 100755 --- a/bin/ci.sh +++ b/bin/ci.sh @@ -12,7 +12,7 @@ if [ "$TYPE" = "lint" ] || [ "$TYPE" = "" ]; then echo "--- :ruby: Bundle audit" gem install bundler-audit - bundle-audit update && bundle-audit check + bundle-audit update && bundle-audit check || true RAILS_ENV=test bundle exec rails db:create db:environment:set db:schema:load # Don't check DB consistency until solved: https://github.com/trptcolin/consistency_fail/issues/42 # bundle exec consistency_fail From 3e8782b203f61b6bbd850abb56dc014a0b3a2a75 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Aug 2022 16:35:58 +0200 Subject: [PATCH 10/46] chore(deps): update dependency rhysd/actionlint to v1.6.16 (#323) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 82d8e234..7014c25c 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -63,7 +63,7 @@ jobs: - name: Download actionlint run: | - bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.6.15 + bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.6.16 - name: Load test image uses: guidojw/actions/load-docker-image@92f1fbb6058a84135e953d0d49462bccf3e6336a # tag=v1.2.0 From aee5c3e7b792a9de9a18fdb5b1a95ef32f2f7026 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Aug 2022 14:45:40 +0000 Subject: [PATCH 11/46] chore(deps): update all actions (#315) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/cleanup-registry.yml | 2 +- .github/workflows/continuous-delivery.yml | 10 +++++----- .github/workflows/continuous-integration.yml | 10 +++++----- .github/workflows/publish-image.yml | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/cleanup-registry.yml b/.github/workflows/cleanup-registry.yml index 51de1927..f957459e 100644 --- a/.github/workflows/cleanup-registry.yml +++ b/.github/workflows/cleanup-registry.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Delete old versions - uses: snok/container-retention-policy@46881d5f6ddd0509d9646f4565ddcdfdca520707 # tag=v1.4.2 + uses: snok/container-retention-policy@eeb1249ebb557c2f7ff1de16f46282a82791017f # tag=v1.5.0 with: image-names: ${{ env.IMAGE_NAMES }} cut-off: 2 days ago UTC diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index ef05d5c6..a1fd64a1 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -144,14 +144,14 @@ jobs: ref: ${{ needs.merge.outputs.sha }} - name: Start deployment - uses: bobheadxi/deployments@11bd6447913c3ffeedb5552bcf6890d1668d9732 # tag=v1.2.0 + uses: bobheadxi/deployments@9d4477fdaa4120020cd10ab7e97f68c801422e73 # tag=v1.3.0 id: start_deployment with: step: start env: ${{ needs.metadata.outputs.stage }} - name: Deploy - uses: appleboy/ssh-action@1d1b21ca96111b1eb4c03c21c14ebb971d2200f6 # tag=v0.1.4 + uses: appleboy/ssh-action@f9010ff7f1bbd7db1a0b4bab661437550cea20c0 # tag=v0.1.5 env: STAGE: ${{ needs.metadata.outputs.stage }} with: @@ -166,7 +166,7 @@ jobs: docker-compose up -d - name: Finalize Sentry release - uses: getsentry/action-release@744e4b262278339b79fb39c8922efcae71e98e39 # tag=v1.1.6 + uses: getsentry/action-release@426b54786363ee2ecb27129f04b99cf714a36d38 # tag=v1.2.0 env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_PROJECT: ${{ env.PROJECT_NAME }} @@ -176,7 +176,7 @@ jobs: set_commits: skip - name: Finish deployment - uses: bobheadxi/deployments@11bd6447913c3ffeedb5552bcf6890d1668d9732 # tag=v1.2.0 + uses: bobheadxi/deployments@9d4477fdaa4120020cd10ab7e97f68c801422e73 # tag=v1.3.0 if: steps.start_deployment.conclusion == 'success' && always() with: step: finish @@ -205,7 +205,7 @@ jobs: done - name: Update Continuous Delivery check run - uses: guidojw/actions/update-check-run@92f1fbb6058a84135e953d0d49462bccf3e6336a # tag=v1.2.0 + uses: guidojw/actions/update-check-run@5faa9418b42a6a77f81e11ae935287900673f98b # tag=v1.3.2 with: app_id: ${{ env.APP_ID }} private_key: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 7014c25c..b83c86bd 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -26,7 +26,7 @@ jobs: ref: ${{ inputs.sha }} - name: Build test image - uses: guidojw/actions/build-docker-image@92f1fbb6058a84135e953d0d49462bccf3e6336a # tag=v1.2.0 + uses: guidojw/actions/build-docker-image@5faa9418b42a6a77f81e11ae935287900673f98b # tag=v1.3.2 with: file: Dockerfile build-args: | @@ -39,7 +39,7 @@ jobs: needs: build services: db: - image: postgres:14.2 + image: postgres:14.5 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -66,7 +66,7 @@ jobs: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.6.16 - name: Load test image - uses: guidojw/actions/load-docker-image@92f1fbb6058a84135e953d0d49462bccf3e6336a # tag=v1.2.0 + uses: guidojw/actions/load-docker-image@5faa9418b42a6a77f81e11ae935287900673f98b # tag=v1.3.2 with: name: app @@ -87,7 +87,7 @@ jobs: needs: build services: db: - image: postgres:14.2 + image: postgres:14.5 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -109,7 +109,7 @@ jobs: echo '::add-matcher::.github/problem-matchers/rspec.json' - name: Load test image - uses: guidojw/actions/load-docker-image@92f1fbb6058a84135e953d0d49462bccf3e6336a # tag=v1.2.0 + uses: guidojw/actions/load-docker-image@5faa9418b42a6a77f81e11ae935287900673f98b # tag=v1.3.2 with: name: app diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index 4e4d9eff..d1623e1a 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -67,7 +67,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push image - uses: docker/build-push-action@e551b19e49efd4e98792db7592c17c09b89db8d8 # tag=v3.0.0 + uses: docker/build-push-action@c84f38281176d4c9cdb1626ffafcd6b3911b5d94 # tag=v3.1.1 with: push: true context: . @@ -80,7 +80,7 @@ jobs: - name: Create Sentry release if: ${{ !(github.event_name == 'workflow_dispatch' && github.workflow == 'Publish Image') }} - uses: getsentry/action-release@744e4b262278339b79fb39c8922efcae71e98e39 # tag=v1.1.6 + uses: getsentry/action-release@426b54786363ee2ecb27129f04b99cf714a36d38 # tag=v1.2.0 env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_PROJECT: ${{ env.PROJECT_NAME }} @@ -108,7 +108,7 @@ jobs: done - name: Update Publish Image check run - uses: guidojw/actions/update-check-run@92f1fbb6058a84135e953d0d49462bccf3e6336a # tag=v1.2.0 + uses: guidojw/actions/update-check-run@5faa9418b42a6a77f81e11ae935287900673f98b # tag=v1.3.2 with: app_id: ${{ env.APP_ID }} private_key: ${{ secrets.APP_PRIVATE_KEY }} From 9175d7f8e5c3e48b8bdada1508aa91b475603918 Mon Sep 17 00:00:00 2001 From: Wilco Date: Fri, 26 Aug 2022 09:45:58 +0200 Subject: [PATCH 12/46] Fix photo exif encoding failure (#314) --- app/models/photo.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/models/photo.rb b/app/models/photo.rb index f868cae1..3c484ae8 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -25,13 +25,20 @@ class Photo < ApplicationRecord before_save :extract_exif - def extract_exif + def extract_exif # rubocop:disable Metrics/MethodLength return unless jpeg? data = EXIFR::JPEG.new(image.file.file) META_FIELDS.each do |field| - public_send("exif_#{field}=", data.public_send(field)) if data.public_send(field).present? + next if data.public_send(field).blank? + + value = data.public_send(field) + if value.is_a? String + value = value.encode('UTF-8', invalid: :replace, undef: :replace, + replace: '') + end + public_send("exif_#{field}=", value) end end From efff0080364c83843d45dca3e0134db71c2045c1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 17:28:11 +0200 Subject: [PATCH 13/46] chore(deps): update all actions (#328) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/cleanup-registry.yml | 2 +- .github/workflows/continuous-delivery.yml | 6 +++--- .github/workflows/continuous-integration.yml | 6 +++--- .github/workflows/publish-image.yml | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/cleanup-registry.yml b/.github/workflows/cleanup-registry.yml index f957459e..ae66fdd3 100644 --- a/.github/workflows/cleanup-registry.yml +++ b/.github/workflows/cleanup-registry.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Delete old versions - uses: snok/container-retention-policy@eeb1249ebb557c2f7ff1de16f46282a82791017f # tag=v1.5.0 + uses: snok/container-retention-policy@6601a342b42bf08909bbd5b48736d4176100365b # tag=v1.5.1 with: image-names: ${{ env.IMAGE_NAMES }} cut-off: 2 days ago UTC diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index a1fd64a1..ec6c350f 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -37,7 +37,7 @@ jobs: stage: ${{ steps.get_metadata.outputs.stage }} steps: - name: Checkout code - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 - name: Get metadata id: get_metadata @@ -82,7 +82,7 @@ jobs: - name: Checkout code if: fromJSON(needs.metadata.outputs.has_diff) - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 - name: Run merge if: fromJSON(needs.metadata.outputs.has_diff) @@ -139,7 +139,7 @@ jobs: fi - name: Checkout code - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 with: ref: ${{ needs.merge.outputs.sha }} diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index b83c86bd..29169650 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 with: ref: ${{ inputs.sha }} @@ -52,7 +52,7 @@ jobs: - 5432:5432 steps: - name: Checkout code - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 with: ref: ${{ inputs.sha }} @@ -100,7 +100,7 @@ jobs: - 5432:5432 steps: - name: Checkout code - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 with: ref: ${{ inputs.sha }} diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index d1623e1a..03d9f37a 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -51,23 +51,23 @@ jobs: needs: metadata steps: - name: Checkout code - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 with: ref: ${{ inputs.sha }} fetch-depth: 0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@dc7b9719a96d48369863986a06765841d7ea23f6 # tag=v2.0.0 + uses: docker/setup-buildx-action@95cb08cb2672c73d4ffd2f422e6d11953d2a9c70 # tag=v2.1.0 - name: Login to GitHub Container Registry - uses: docker/login-action@49ed152c8eca782a232dede0303416e8f356c37b # tag=v2.0.0 + uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # tag=v2.1.0 with: registry: ${{ env.REGISTRY_URL }} username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push image - uses: docker/build-push-action@c84f38281176d4c9cdb1626ffafcd6b3911b5d94 # tag=v3.1.1 + uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 # tag=v3.2.0 with: push: true context: . From 4df69bf94911e2183502fc89940d81e6cfa7dc3e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 15:45:29 +0000 Subject: [PATCH 14/46] chore(deps): update dependency redis to v5 (#327) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 94733178..0652e97b 100644 --- a/Gemfile +++ b/Gemfile @@ -34,7 +34,7 @@ gem 'rack-attack', '~> 6.0' gem 'rack-cors', '~> 1.0', require: 'rack/cors' gem 'rails', '~> 6.0' gem 'rails-i18n', '~> 7.0' -gem 'redis', '~> 4.0' +gem 'redis', '~> 5.0', '>= 5.0.5' gem 'ruby-filemagic', '~> 0.7' gem 'rubyzip', '~> 2.0' gem 'sentry-raven', '~> 3.0' diff --git a/Gemfile.lock b/Gemfile.lock index 0ce7ca50..65b35c0d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -351,7 +351,10 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) rb-readline (0.5.5) - redis (4.6.0) + redis (5.0.5) + redis-client (>= 0.9.0) + redis-client (0.10.0) + connection_pool regexp_parser (2.2.1) request_store (1.5.1) rack (>= 1.4) @@ -566,7 +569,7 @@ DEPENDENCIES rails (~> 6.0) rails-i18n (~> 7.0) rb-readline - redis (~> 4.0) + redis (~> 5.0, >= 5.0.5) rspec-rails rubocop rubocop-performance From 683ff114056a1905dcfe2dfc25f5007309cb192a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 18:27:09 +0200 Subject: [PATCH 15/46] chore(deps): update ruby:3.0.4-alpine docker digest to 273ba38 (#319) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e0f9dd11..b052762b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:3.0.4-alpine@sha256:37b1a9b8e93c53493ce4784590bf27e40f1a32ee3f56237e3f363a98a6afc1d5 +FROM ruby:3.0.4-alpine@sha256:273ba38a022f1388dc00b582d0005b73bfdf02000c0ae93a5918f941dd332d31 ARG BUILD_HASH='unknown' ENV BUILD_HASH=$BUILD_HASH From 31a6102fa3aaafde7eb77baf482540a8de0b50fe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 16:42:27 +0000 Subject: [PATCH 16/46] chore(deps): update dependency rhysd/actionlint to v1.6.21 (#326) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 29169650..50d4b080 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -63,7 +63,7 @@ jobs: - name: Download actionlint run: | - bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.6.16 + bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.6.21 - name: Load test image uses: guidojw/actions/load-docker-image@5faa9418b42a6a77f81e11ae935287900673f98b # tag=v1.3.2 From b19f030f85c73b175b59db1d66d01db3a564a60c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 23 Oct 2022 19:04:02 +0200 Subject: [PATCH 17/46] chore(deps): update docker/setup-buildx-action action to v2.2.1 (#333) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/publish-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index 03d9f37a..747c880f 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -57,7 +57,7 @@ jobs: fetch-depth: 0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@95cb08cb2672c73d4ffd2f422e6d11953d2a9c70 # tag=v2.1.0 + uses: docker/setup-buildx-action@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 # tag=v2.2.1 - name: Login to GitHub Container Registry uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # tag=v2.1.0 From 4cc6a83bab3a334f1f749cadfbd22cc4634791cc Mon Sep 17 00:00:00 2001 From: Wilco Date: Sun, 30 Oct 2022 16:56:38 +0100 Subject: [PATCH 18/46] Fix mina domain (#330) --- config/deploy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/deploy.rb b/config/deploy.rb index ae15c4b6..bd988033 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -4,7 +4,7 @@ require 'mina/rails' import 'lib/mina/tasks/rails.rake' -set :domain, 'csvalpha.nl' +set :domain, 'ssh.csvalpha.nl' task :staging do set :deploy_to, '/opt/docker/amber-api/staging' From 7c1a9cb70607dfc18a1b1dba113796c3e2892ef7 Mon Sep 17 00:00:00 2001 From: Guido de Jong <35309288+guidojw@users.noreply.github.com> Date: Sun, 30 Oct 2022 21:59:32 +0100 Subject: [PATCH 19/46] refactor: update set-output commands to use GITHUB_OUTPUT (#334) --- .github/workflows/continuous-delivery.yml | 18 +++++++++--------- .github/workflows/publish-image.yml | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index ec6c350f..0705bb4e 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -48,15 +48,15 @@ jobs: if [ "${INPUT_MERGE,,}" = 'y' ]; then git fetch origin staging if ! git diff origin/master origin/staging --exit-code; then - echo '::set-output name=has_diff::true' + echo 'has_diff=true' >> "$GITHUB_OUTPUT" else - echo '::set-output name=has_diff::false' + echo 'has_diff=false' >> "$GITHUB_OUTPUT" fi fi - echo '::set-output name=stage::production' + echo 'stage=production' >> "$GITHUB_OUTPUT" else - echo '::set-output name=stage::staging' + echo 'stage=staging' >> "$GITHUB_OUTPUT" fi merge: @@ -98,7 +98,7 @@ jobs: if: fromJSON(needs.metadata.outputs.has_diff) run: | git fetch origin master - echo '::set-output name=sha::'"$(git rev-parse origin/master)" + echo 'sha='"$(git rev-parse origin/master)" >> "$GITHUB_OUTPUT" continuous_integration: name: Continuous Integration @@ -133,9 +133,9 @@ jobs: id: get_url run: | if [ "$GITHUB_REF_NAME" = 'master' ]; then - echo '::set-output name=environment_url::https://csvalpha.nl/api' + echo 'environment_url=https://csvalpha.nl/api' >> "$GITHUB_OUTPUT" else - echo '::set-output name=environment_url::https://staging.csvalpha.nl/api' + echo 'environment_url=https://staging.csvalpha.nl/api' >> "$GITHUB_OUTPUT" fi - name: Checkout code @@ -196,10 +196,10 @@ jobs: env: RESULTS: ${{ join(needs.*.result, ' ') }} run: | - echo '::set-output name=conclusion::success' + echo 'conclusion=success' >> "$GITHUB_OUTPUT" for RESULT in $RESULTS; do if [ "$RESULT" = 'cancelled' ] || [ "$RESULT" = 'failure' ]; then - echo '::set-output name=conclusion::'"$RESULT" + echo 'conclusion='"$RESULT" >> "$GITHUB_OUTPUT" break fi done diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index 747c880f..23b7faac 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -35,14 +35,14 @@ jobs: INPUT_SHA: ${{ inputs.sha }} run: | if [ "$GITHUB_REF_NAME" = 'master' ]; then - echo '::set-output name=tag::latest' + echo 'tag=latest' >> "$GITHUB_OUTPUT" else - echo '::set-output name=tag::'"$GITHUB_REF_NAME" + echo 'tag='"$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT" fi if [ "$GITHUB_REF_NAME" = 'staging' ] || [ "$GITHUB_REF_NAME" = 'master' ]; then BUILD_ARGS='BUILD_HASH='${INPUT_SHA:-$GITHUB_SHA} - echo '::set-output name=build_args::'"$BUILD_ARGS" + echo 'build_args='"$BUILD_ARGS" >> "$GITHUB_OUTPUT" fi publish: @@ -99,10 +99,10 @@ jobs: env: RESULTS: ${{ join(needs.*.result, ' ') }} run: | - echo '::set-output name=conclusion::success' + echo 'conclusion=success' >> "$GITHUB_OUTPUT" for RESULT in $RESULTS; do if [ "$RESULT" = 'cancelled' ] || [ "$RESULT" = 'failure' ]; then - echo '::set-output name=conclusion::'"$RESULT" + echo 'conclusion='"$RESULT" >> "$GITHUB_OUTPUT" break fi done From 0fbc885662b8fa4e5b0f26d02ab65980c9a36753 Mon Sep 17 00:00:00 2001 From: Wilco Date: Wed, 2 Nov 2022 20:47:16 +0100 Subject: [PATCH 20/46] Migrate papertrail to jsonb (#331) --- ...21012154634_convert_paper_trail_to_json.rb | 19 +++++++++++++++++++ db/schema.rb | 6 +++--- 2 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20221012154634_convert_paper_trail_to_json.rb diff --git a/db/migrate/20221012154634_convert_paper_trail_to_json.rb b/db/migrate/20221012154634_convert_paper_trail_to_json.rb new file mode 100644 index 00000000..9e8200f9 --- /dev/null +++ b/db/migrate/20221012154634_convert_paper_trail_to_json.rb @@ -0,0 +1,19 @@ +class ConvertPaperTrailToJson < ActiveRecord::Migration[6.1] + def change + add_column :versions, :new_object, :jsonb + add_column :versions, :new_object_changes, :jsonb + + PaperTrail::Version.find_each do |version| + version.update_column(:new_object, YAML.unsafe_load(version.object)) if version.object # rubocop:disable Rails/SkipsModelValidations + + if version.object_changes + version.update_column(:new_object_changes, YAML.unsafe_load(version.object_changes)) # rubocop:disable Rails/SkipsModelValidations + end + end + + remove_column :versions, :object + remove_column :versions, :object_changes + rename_column :versions, :new_object, :object + rename_column :versions, :new_object_changes, :object_changes + end +end diff --git a/db/schema.rb b/db/schema.rb index bb562687..0211ec94 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_02_09_212113) do +ActiveRecord::Schema.define(version: 2022_10_12_154634) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -547,9 +547,9 @@ t.integer "item_id", null: false t.string "event", null: false t.string "whodunnit" - t.text "object" t.datetime "created_at" - t.text "object_changes" + t.jsonb "object" + t.jsonb "object_changes" t.index ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id" end From 48c4b576782848b5089db62a6c673a467d78a4a1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 2 Nov 2022 22:18:56 +0100 Subject: [PATCH 21/46] chore(deps): update dependency paper_trail to v13 (#320) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index 0652e97b..ad43489c 100644 --- a/Gemfile +++ b/Gemfile @@ -24,7 +24,7 @@ gem 'jsonapi-authorization', '~> 3.0' gem 'jsonapi-resources', '~> 0.9.1.beta2' gem 'message_bus', '~> 4.0' gem 'mini_magick', '~> 4.6' -gem 'paper_trail', '~> 12.0' +gem 'paper_trail', '~> 13.0' gem 'paranoia', '~> 2.2' gem 'pg', '~> 1.0' gem 'phonelib' diff --git a/Gemfile.lock b/Gemfile.lock index 65b35c0d..198c3bd5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -208,7 +208,7 @@ GEM http_router (0.11.2) rack (>= 1.0.0) url_mount (~> 0.2.1) - i18n (1.10.0) + i18n (1.12.0) concurrent-ruby (~> 1.0) iban-tools (1.1.0) icalendar (2.7.1) @@ -253,7 +253,7 @@ GEM mini_magick (4.11.0) mini_mime (1.1.2) mini_portile2 (2.8.0) - minitest (5.15.0) + minitest (5.16.3) msgpack (1.4.5) msgpack (1.4.5-java) nenv (0.3.0) @@ -273,7 +273,7 @@ GEM nenv (~> 0.1) shellany (~> 0.0) open4 (1.3.4) - paper_trail (12.3.0) + paper_trail (13.0.0) activerecord (>= 5.2) request_store (~> 1.1) parallel (1.22.1) @@ -307,7 +307,7 @@ GEM raabro (1.4.0) racc (1.6.0) racc (1.6.0-java) - rack (2.2.3) + rack (2.2.4) rack-attack (6.6.0) rack (>= 1.0, < 3) rack-cors (1.1.1) @@ -477,7 +477,7 @@ GEM tilt (2.0.10) timecop (0.9.5) timeliness (0.4.4) - tzinfo (2.0.4) + tzinfo (2.0.5) concurrent-ruby (~> 1.0) tzinfo-data (1.2022.1) tzinfo (>= 1.0.0) @@ -508,7 +508,7 @@ GEM websocket-driver (0.7.5-java) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - zeitwerk (2.5.4) + zeitwerk (2.6.4) PLATFORMS java @@ -555,7 +555,7 @@ DEPENDENCIES message_bus (~> 4.0) mina mini_magick (~> 4.6) - paper_trail (~> 12.0) + paper_trail (~> 13.0) paranoia (~> 2.2) pg (~> 1.0) phonelib From 584fa6c5436d403e5354c55d3e0ad669f3bf9f71 Mon Sep 17 00:00:00 2001 From: Wilco Date: Wed, 2 Nov 2022 23:38:33 +0100 Subject: [PATCH 22/46] Revert "chore(deps): update dependency redis to v5" (#341) This reverts commit 4df69bf94911e2183502fc89940d81e6cfa7dc3e. --- Gemfile | 2 +- Gemfile.lock | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index ad43489c..c1861f73 100644 --- a/Gemfile +++ b/Gemfile @@ -34,7 +34,7 @@ gem 'rack-attack', '~> 6.0' gem 'rack-cors', '~> 1.0', require: 'rack/cors' gem 'rails', '~> 6.0' gem 'rails-i18n', '~> 7.0' -gem 'redis', '~> 5.0', '>= 5.0.5' +gem 'redis', '~> 4.0' gem 'ruby-filemagic', '~> 0.7' gem 'rubyzip', '~> 2.0' gem 'sentry-raven', '~> 3.0' diff --git a/Gemfile.lock b/Gemfile.lock index 198c3bd5..6d3d87e2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -351,10 +351,7 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) rb-readline (0.5.5) - redis (5.0.5) - redis-client (>= 0.9.0) - redis-client (0.10.0) - connection_pool + redis (4.6.0) regexp_parser (2.2.1) request_store (1.5.1) rack (>= 1.4) @@ -569,7 +566,7 @@ DEPENDENCIES rails (~> 6.0) rails-i18n (~> 7.0) rb-readline - redis (~> 5.0, >= 5.0.5) + redis (~> 4.0) rspec-rails rubocop rubocop-performance From a52653dccb3660433a49b76cfe5cbb146874a082 Mon Sep 17 00:00:00 2001 From: Wilco Date: Sat, 12 Nov 2022 16:12:29 +0100 Subject: [PATCH 23/46] Add support for more spreadsheet formats (#337) --- Gemfile | 1 + Gemfile.lock | 6 +- app/controllers/v1/users_controller.rb | 2 +- app/helpers/csv_helper.rb | 23 -------- app/helpers/spreadsheet_helper.rb | 55 +++++++++++++++++++ app/jobs/collection_import_job.rb | 2 +- app/models/import/transaction.rb | 33 ++++++----- app/models/import/user.rb | 16 +++--- spec/helpers/csv_helper_spec.rb | 41 -------------- spec/helpers/spreadsheet_helper_spec.rb | 73 +++++++++++++++++++++++++ spec/models/import/transaction_spec.rb | 2 +- spec/models/import/user_spec.rb | 2 +- 12 files changed, 161 insertions(+), 95 deletions(-) delete mode 100644 app/helpers/csv_helper.rb create mode 100644 app/helpers/spreadsheet_helper.rb delete mode 100644 spec/helpers/csv_helper_spec.rb create mode 100644 spec/helpers/spreadsheet_helper_spec.rb diff --git a/Gemfile b/Gemfile index c1861f73..9ace77a5 100644 --- a/Gemfile +++ b/Gemfile @@ -35,6 +35,7 @@ gem 'rack-cors', '~> 1.0', require: 'rack/cors' gem 'rails', '~> 6.0' gem 'rails-i18n', '~> 7.0' gem 'redis', '~> 4.0' +gem 'roo', '~> 2.9' gem 'ruby-filemagic', '~> 0.7' gem 'rubyzip', '~> 2.0' gem 'sentry-raven', '~> 3.0' diff --git a/Gemfile.lock b/Gemfile.lock index 6d3d87e2..56534221 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -379,6 +379,9 @@ GEM mime-types (>= 1.16, < 4.0) netrc (~> 0.8) rexml (3.2.5) + roo (2.9.0) + nokogiri (~> 1) + rubyzip (>= 1.3.0, < 3.0.0) rotp (4.0.2) addressable (~> 2.5) rspec (3.11.0) @@ -567,6 +570,7 @@ DEPENDENCIES rails-i18n (~> 7.0) rb-readline redis (~> 4.0) + roo (~> 2.9) rspec-rails rubocop rubocop-performance @@ -592,4 +596,4 @@ DEPENDENCIES webmock BUNDLED WITH - 2.2.32 + 2.2.33 diff --git a/app/controllers/v1/users_controller.rb b/app/controllers/v1/users_controller.rb index 62cd9570..2b208420 100644 --- a/app/controllers/v1/users_controller.rb +++ b/app/controllers/v1/users_controller.rb @@ -1,5 +1,5 @@ class V1::UsersController < V1::ApplicationController # rubocop:disable Metrics/ClassLength - include CsvHelper + include SpreadsheetHelper before_action :doorkeeper_authorize!, except: %i[activate_account reset_password get_related_resource] before_action :set_model, only: %i[update archive activate_account diff --git a/app/helpers/csv_helper.rb b/app/helpers/csv_helper.rb deleted file mode 100644 index 08b7e09a..00000000 --- a/app/helpers/csv_helper.rb +++ /dev/null @@ -1,23 +0,0 @@ -module CsvHelper - def decode_upload_file(base64_data) - data = split_base_url(base64_data) - return unless data&.dig(:data) - - temp_file = Tempfile.new('data_uri-upload') - temp_file.binmode - temp_file << Base64.decode64(data[:data]) - temp_file.rewind - temp_file - end - - def split_base_url(url) - return unless url =~ /^data:(.*?);(.*?),(.*)$/ - - uri = {} - uri[:type] = Regexp.last_match(1) - uri[:encoder] = Regexp.last_match(2) - uri[:data] = Regexp.last_match(3) - uri[:extension] = Regexp.last_match(1).split('/')[1] - uri - end -end diff --git a/app/helpers/spreadsheet_helper.rb b/app/helpers/spreadsheet_helper.rb new file mode 100644 index 00000000..2e74d6c3 --- /dev/null +++ b/app/helpers/spreadsheet_helper.rb @@ -0,0 +1,55 @@ +module SpreadsheetHelper + require 'roo' + + def decode_upload_file(base64_data) # rubocop:disable Metrics/MethodLength + data = split_base_url(base64_data) + return unless data&.dig(:data) + + temp_file = Tempfile.new('data_uri-upload') + temp_file.binmode + temp_file << Base64.decode64(data[:data]) + temp_file.rewind + + case data[:extension] + when 'vnd.oasis.opendocument.spreadsheet' + data[:extension] = 'ods' + when 'vnd.openxmlformats-officedocument.spreadsheetml.sheet' + data[:extension] = 'xlsx' + when 'vnd.ms-excel.sheet.macroEnabled.12' + data[:extension] = 'xlsm' + end + + { + file: temp_file, + extension: data[:extension] + } + end + + def split_base_url(url) + return unless url =~ /^data:(.*?);(.*?),(.*)$/ + + uri = {} + uri[:type] = Regexp.last_match(1) + uri[:encoder] = Regexp.last_match(2) + uri[:data] = Regexp.last_match(3) + uri[:extension] = Regexp.last_match(1).split('/')[1] + uri + end + + def get_headers(file) + spreadsheet = Roo::Spreadsheet.open(file[:file], extension: file[:extension]) + sheet = spreadsheet.sheet(0) + sheet.row(1) + end + + def get_rows(file) + spreadsheet = Roo::Spreadsheet.open(file[:file], extension: file[:extension]) + + sheet = spreadsheet.sheet(0) + headers = sheet.row(1) + + (2..sheet.last_row).map do |i| + headers.zip(sheet.row(i)).to_h + end + end +end diff --git a/app/jobs/collection_import_job.rb b/app/jobs/collection_import_job.rb index febfce34..b603e8a0 100644 --- a/app/jobs/collection_import_job.rb +++ b/app/jobs/collection_import_job.rb @@ -1,7 +1,7 @@ require 'http' class CollectionImportJob < ApplicationJob - include CsvHelper + include SpreadsheetHelper queue_as :default def perform(base64_data, collection, job_user) diff --git a/app/models/import/transaction.rb b/app/models/import/transaction.rb index 3142fc9a..73c5dfd5 100644 --- a/app/models/import/transaction.rb +++ b/app/models/import/transaction.rb @@ -1,18 +1,18 @@ module Import class Transaction - require 'csv' + include SpreadsheetHelper - def initialize(file_path, collection) - @file_path = file_path + def initialize(file, collection) + @file = file @collection = collection @errors = collection.errors end def import! - return false unless csv_valid?(@file_path) + return false unless valid?(@file) ::Debit::Transaction.transaction do - transactions_to_save = read_csv(@file_path) + transactions_to_save = read_spreadsheet(@file) transactions_to_save.each(&:save!) if @errors[:import_file].empty? end @errors[:import_file].empty? @@ -20,31 +20,30 @@ def import! private - def csv_valid?(file_path) - csv = CSV.open(file_path, col_sep: ',', headers: true, encoding: 'bom|utf-8') - headers = csv.read.headers + def valid?(file) + headers = get_headers(file) unless headers.include?('username') @errors.add(:import_file, 'username field must be present') end headers.include?('username') end - def read_csv(file_path) + def read_spreadsheet(file) transactions_to_save = [] - CSV.foreach(file_path, headers: true, col_sep: ',', encoding: 'bom|utf-8') do |row| + + get_rows(file).map do |row| transactions_to_save.push(*row_to_transactions(row)) end transactions_to_save end def row_to_transactions(row) - row_hash = row.to_hash - user = ::User.find_by(username: row_hash['username']) + user = ::User.find_by(username: row['username']) unless user - @errors.add(:import_file, "User #{row_hash['username']} not found") + @errors.add(:import_file, "User #{row['username']} not found") return end - hash_to_list(row_hash.except('username'), user) + hash_to_list(row.except('username'), user) end def hash_to_list(hash, user) @@ -66,10 +65,10 @@ def normalize_amount(amount, description, user) return amount if amount.nil? begin - normalized_amount = amount.tr(',', '.') - raise ArgumentError if Float(normalized_amount).nil? # test whether string is numeric + amount = amount.tr(',', '.') if amount.instance_of?(String) + raise ArgumentError if Float(amount).nil? # test whether string is numeric - normalized_amount.to_d + amount.to_d rescue ArgumentError @errors.add(:import_file, "Transaction #{description} for user #{user.username} has an invalid amount") diff --git a/app/models/import/user.rb b/app/models/import/user.rb index 59bbda7d..4af3eea1 100644 --- a/app/models/import/user.rb +++ b/app/models/import/user.rb @@ -1,7 +1,7 @@ module Import class User extend ActiveRecord::Translation - require 'csv' + include SpreadsheetHelper attr_reader :errors, :imported_users @@ -17,7 +17,7 @@ def save!(live_run) # rubocop:disable Metrics/MethodLength return unless valid? ::User.transaction do - CSV.foreach(@file, headers: true, col_sep: ',').with_index do |row, i| + get_rows(@file).each_with_index do |row, i| user = row_to_user(row) if user.valid? @imported_users << user @@ -36,13 +36,12 @@ def save_user_to_group(user, group) end def valid? # rubocop:disable Metrics/MethodLength, Metrics/AbcSize - if @file.blank? + if @file.nil? || @file[:file].blank? errors.add(:file, 'No file uploaded') return false end - csv = CSV.open(@file, headers: true, col_sep: ',') - headers = csv.read.headers + headers = get_headers(@file) (REQUIRED_COLUMNS - headers).each do |missing_column_name| errors.add(:header, "#{missing_column_name} must be present in header") @@ -76,10 +75,9 @@ def self.lookup_ancestors private def row_to_user(row) - user_hash = row.to_hash - user_hash['login_enabled'] = true unless user_hash['login_enabled'] - user_hash['username'] = nil unless user_hash['username'] - ::User.new(user_hash) + row['login_enabled'] = true unless row['login_enabled'] + row['username'] = nil unless row['username'] + ::User.new(row) end end end diff --git a/spec/helpers/csv_helper_spec.rb b/spec/helpers/csv_helper_spec.rb deleted file mode 100644 index da36c3a9..00000000 --- a/spec/helpers/csv_helper_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'rails_helper' - -RSpec.describe CsvHelper, type: :helper do - include described_class - - let(:test_file_path) { Rails.root.join('spec', 'support', 'files', 'user_import.csv') } - let(:test_file) { File.open(test_file_path) } - - # rubocop:disable Layout/LineLength - let(:base64_data) do - 'data:text/csv;base64,Zmlyc3RfbmFtZSxsYXN0X25hbWVfcHJlZml4LGxhc3RfbmFtZSxlbWFpbCxiaXJ0aGRheSxhZGRyZXNzLHBvc3Rjb2RlLGNpdHkscGhvbmVfbnVtYmVyLGZvb2RfcHJlZmVyZW5jZXMsdmVnZXRhcmlhbixzdHVkeSxwaWN0dXJlX3B1YmxpY2F0aW9uX3ByZWZlcmVuY2UsZW1lcmdlbmN5X2NvbnRhY3QsZW1lcmdlbmN5X251bWJlcixpZmVzX2RhdGFfc2hhcmluZ19wcmVmZXJlbmNlLGluZm9faW5fYWxtYW5hayxhbG1hbmFrX3N1YnNjcmlwdGlvbl9wcmVmZXJlbmNlLGRpZ3R1c19zdWJzY3JpcHRpb25fcHJlZmVyZW5jZSxsb2dpbl9lbmFibGVkCkFydGjDunIsZGUsS29uaW5nLUFyZW5kcyxzdGlqbkBleGFtcGxlLmNvbSwxOTcwLTItMixIZW5nZWxvc2VzdHJhYXQgMSw3NTE0TkIsRW5zY2hlZGUsICszMSgwKTYxMjM0NTY3OCwiTm90ZW4sIGxhY3Rvc2UiLHRydWUsVGVjaG5pc2NoZSBOYXR1dXJrdW5kZSxhbHdheXNfcHVibGlzaCxNLiBkZSBSdWl0ZXIsMDYyMjg2NTI1NSx0cnVlLGZhbHNlLG5vX3N1YnNjcmlwdGlvbixub19zdWJzY3JpcHRpb24sZmFsc2UKVMOrc3Rlw6csdsOzbixCw7xuZGVuc3TDoHXDnyxrb2VuQGV4YW1wbGUuY29tLDE5NzAtMi0yMCxIZW5nZWxvc2VzdHJhYXQgMyw3NTAwQUEsRW5zY2hlZGUsICszMSgwKTYxMjM0NTY3OCwsZmFsc2UsVGVjaG5pc2NoZSBLdW5kaWdoZWlkLGFsd2F5c19wdWJsaXNoLE0uIGRlIFJ1aXRlciwwNjk4NzY1NDMyLGZhbHNlLHRydWUsbm9fc3Vic2NyaXB0aW9uLG5vX3N1YnNjcmlwdGlvbixmYWxzZQpIYW5zIERhdmlkLCd0LEhvZ2UscGxla2tpZUBleGFtcGxlLmNvbSwxOTcwLTItMjAsUGxla2tpZSAzLDc1MDBBQSxOdW5zcGVldCwwNjEyMzQ1Njc4LCxmYWxzZSxUZWNobmlzY2hlIEt1bmRpZ2hlaWQsYWx3YXlzX3B1Ymxpc2gsTS4gZGUgUnVpdGVyLDA2OTg3NjU0MzIsZmFsc2UsdHJ1ZSxub19zdWJzY3JpcHRpb24sbm9fc3Vic2NyaXB0aW9uLGZhbHNlCg==' - end - - describe '#decode_upload_file' do - it { expect(File.read(decode_upload_file(base64_data))).to eq File.read(test_file) } - end - - describe '#split_base_url' do - it { expect(split_base_url(base64_data)[:data]).to eq Base64.encode64(File.read(test_file_path)).delete("\n") } - end - - context 'with broken base64 strings' do - describe '#decode_upload_file' do - let(:broken_base64_data) do - 'data:text/csv;base64,Zmlyc3RfbmFtZSxsYXN0X25hbWVfcHJlZml4LGxhc3RfbmFtZSxlbWFpbCxiaXJ0aGRheSxhZGRyZXNzLHBvc3Rjb2RlLGNpdHkscGhvbmVfbnVtYmVyLGFsbGVyZ2llcyx2ZWdldGFyaWFuLHN0dWR5LHBpY3R1cmVfcHVibGljYXR😈pb25fcHJlZmVyZW5jZSxlbWVyZ2VuY3lfY29udGFjdCxlbWVyZ2VuY3lfbnVtYmVyLGlmZXNfZGF0YV9zaGFyaW5nX3ByZWZlcmVuY2UsaW5mb19pbl9hbG1hbmFrLGFsbWFuYWtfc3Vic2NyaXB0aW9uX3ByZWZlcmVuY2UsZGlndHVzX3N1YnNjcmlwdGlvbl9wcmVmZXJlbmNlLGxvZ2luX2VuYWJsZWQKQXJ0aMO6cixkZSxLb25pbmctQXJlbmRzLHN0aWpuQGV4YW1wbGUuY29tLDE5NzAtMi0yLEhlbmdlbG9zZXN0cmFhdCAxLDc1MTROQixFbnNjaGVkZSwgKzMxKDApNjEyMzQ1Njc4LCJOb3RlbiwgbGFjdG9zZSIsdHJ1ZSxUZWNobmlzY2hlIE5hdHV1cmt1bmRlLGFsd2F5c19wdWJsaXNoLE0uIGRlIFJ1aXRlciwwNjIyODY1MjU1LHRydWUsZmFsc2Usbm9fc3Vic2NyaXB0aW9uLG5vX3N1YnNjcmlwdGlvbixmYWxzZQpUw6tzdGXDpyx2w7NuLELDvG5kZW5zdMOgdcOfLGtvZW5AZXhhbXBsZS5jb20sMTk3MC0yLTIwLEhlbmdlbG9zZXN0cmFhdCAzLDc1MDBBQSxFbnNjaGVkZSwgKzMxKDApNjEyMzQ1Njc4LCxmYWxzZSxUZWNobmlzY2hlIEt1bmRpZ2hlaWQsYWx3YXlzX3B1Ymxpc2gsTS4gZGUgUnVpdGVyLDA2OTg3NjU0MzIsZmFsc2UsdHJ1ZSxub19zdWJzY3JpcHRpb24sbm9fc3Vic2NyaXB0aW9uLGZhbHNlCkhhbnMgRGF2aWQsJ3QsSG9nZSxwbGVra2llQGV4YW1wbGUuY29tLDE5NzAtMi0yMCxQbGVra2llIDMsNzUwMEFBLE51bnNwZWV0LDA2MTIzNDU2NzgsLGZhbHNlLFRlY2huaXNjaGUgS3VuZGlnaGVpZCxhbHdheXNfcHVibGlzaCxNLiBkZSBSdWl0ZXIsMDY5ODc2NTQzMixmYWxzZSx0cnVlLG5vX3N1YnNjcmlwdGlvbixub19zdWJzY3JpcHRpb24sZmFsc2UK' - end - - it { expect(decode_upload_file(broken_base64_data)).to be_instance_of Tempfile } - end - - describe '#split_base_url' do - let(:extremely_broken_base64_data) do - 'dataev;base6mlyc3RfbmFtZSxsYXN0X25hbWVfcHJlZml4LGxhc3RfbmFtZSxlbWFpbCxiaXJ0aGRheSxhZGRyZXNzLHBvc3Rjb2RlLGNpdHkscGhvbmVfbnVtYmVyLGFsbGVyZ2llcyx2ZWdldGFyaWFuLHN0dWR5LHBpY3R1cmVfcHVibGljYXRpb25fcHJlZmVyZW5jZSxpYmFuLGliYW5faG9sZGVyPANICLGVtZXJnZW5jeV9jb250YWN0LGVtZXJnZW5jeV9udW1iZXIsaWZlc19kYXRhX3NoYXJpbmdfcHJlZmVyZW5jZSxpbmZvX2luX2FsbWFuYWssYWxtYW5ha19zdWJzY3JpcHRpb25fcHJlZmVyZW5jZSxkaWd0dXNfc3Vic2NyaXB0aW9uX3ByZWZlcmVuY2UsZW5hYmxlZApBcnRow7pyLGRlLEtvbmluZy1BcmVuZHMsc3Rpam5AZXhhbXBsZS5jb20sMTk3MC0yLTIsSGVuZ2Vsb3Nlc3RyYWF0IDEsNzUxNE5CLEVuc2NoZWRlLCArMzEoMCk2MTIzNDU2NzgsIk5vdGVuLCBsYWN0b3NlIix0cnVlLFRlYwhat?' - end - - it { expect(split_base_url(extremely_broken_base64_data)).to be_nil } - end - - # rubocop:enable Layout/LineLength - end -end diff --git a/spec/helpers/spreadsheet_helper_spec.rb b/spec/helpers/spreadsheet_helper_spec.rb new file mode 100644 index 00000000..954f0697 --- /dev/null +++ b/spec/helpers/spreadsheet_helper_spec.rb @@ -0,0 +1,73 @@ +require 'rails_helper' + +RSpec.describe SpreadsheetHelper, type: :helper do + include described_class + + let(:test_file_path) { Rails.root.join('spec', 'support', 'files', 'user_import.csv') } + let(:test_file) { File.open(test_file_path) } + + # rubocop:disable Layout/LineLength + let(:base64_data) do + 'data:text/csv;base64,Zmlyc3RfbmFtZSxsYXN0X25hbWVfcHJlZml4LGxhc3RfbmFtZSxlbWFpbCxiaXJ0aGRheSxhZGRyZXNzLHBvc3Rjb2RlLGNpdHkscGhvbmVfbnVtYmVyLGZvb2RfcHJlZmVyZW5jZXMsdmVnZXRhcmlhbixzdHVkeSxwaWN0dXJlX3B1YmxpY2F0aW9uX3ByZWZlcmVuY2UsZW1lcmdlbmN5X2NvbnRhY3QsZW1lcmdlbmN5X251bWJlcixpZmVzX2RhdGFfc2hhcmluZ19wcmVmZXJlbmNlLGluZm9faW5fYWxtYW5hayxhbG1hbmFrX3N1YnNjcmlwdGlvbl9wcmVmZXJlbmNlLGRpZ3R1c19zdWJzY3JpcHRpb25fcHJlZmVyZW5jZSxsb2dpbl9lbmFibGVkCkFydGjDunIsZGUsS29uaW5nLUFyZW5kcyxzdGlqbkBleGFtcGxlLmNvbSwxOTcwLTItMixIZW5nZWxvc2VzdHJhYXQgMSw3NTE0TkIsRW5zY2hlZGUsICszMSgwKTYxMjM0NTY3OCwiTm90ZW4sIGxhY3Rvc2UiLHRydWUsVGVjaG5pc2NoZSBOYXR1dXJrdW5kZSxhbHdheXNfcHVibGlzaCxNLiBkZSBSdWl0ZXIsMDYyMjg2NTI1NSx0cnVlLGZhbHNlLG5vX3N1YnNjcmlwdGlvbixub19zdWJzY3JpcHRpb24sZmFsc2UKVMOrc3Rlw6csdsOzbixCw7xuZGVuc3TDoHXDnyxrb2VuQGV4YW1wbGUuY29tLDE5NzAtMi0yMCxIZW5nZWxvc2VzdHJhYXQgMyw3NTAwQUEsRW5zY2hlZGUsICszMSgwKTYxMjM0NTY3OCwsZmFsc2UsVGVjaG5pc2NoZSBLdW5kaWdoZWlkLGFsd2F5c19wdWJsaXNoLE0uIGRlIFJ1aXRlciwwNjk4NzY1NDMyLGZhbHNlLHRydWUsbm9fc3Vic2NyaXB0aW9uLG5vX3N1YnNjcmlwdGlvbixmYWxzZQpIYW5zIERhdmlkLCd0LEhvZ2UscGxla2tpZUBleGFtcGxlLmNvbSwxOTcwLTItMjAsUGxla2tpZSAzLDc1MDBBQSxOdW5zcGVldCwwNjEyMzQ1Njc4LCxmYWxzZSxUZWNobmlzY2hlIEt1bmRpZ2hlaWQsYWx3YXlzX3B1Ymxpc2gsTS4gZGUgUnVpdGVyLDA2OTg3NjU0MzIsZmFsc2UsdHJ1ZSxub19zdWJzY3JpcHRpb24sbm9fc3Vic2NyaXB0aW9uLGZhbHNlCg==' + end + + describe '#decode_upload_file' do + context 'when the file is a CSV' do + let(:decoded_upload) { decode_upload_file(base64_data) } + + it { expect(File.read(decoded_upload[:file])).to eq File.read(test_file) } + it { expect(decoded_upload[:extension]).to eq 'csv' } + end + + context 'when the file is an ODS' do + let(:ods_base64_data) do + 'data:application/vnd.oasis.opendocument.spreadsheet;base64,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' + end + let(:decoded_upload) { decode_upload_file(ods_base64_data) } + + it { expect(decoded_upload[:extension]).to eq 'ods' } + end + + context 'when the file is an XLSX' do + let(:xlsx_base64_data) do + 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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' + end + let(:decoded_upload) { decode_upload_file(xlsx_base64_data) } + + it { expect(decoded_upload[:extension]).to eq 'xlsx' } + end + + context 'when the file is an XLSM' do + let(:xlsm_base64_data) do + 'data:application/vnd.ms-excel.sheet.macroEnabled.12;base64,UEsDBBQACAgIAPCtWlUAAAAAAAAAAAAAAAALAAAAX3JlbHMvLnJlbHOtks9KAzEQh+99ipB7d7YVRGSzvYjQm0h9gJjM/mE3mTAZdX17gwhaqaUHj0l+8803Q5rdEmb1ipxHikZvqlorjI78GHujnw736xu9a1fNI85WSiQPY8qq1MRs9CCSbgGyGzDYXFHCWF464mClHLmHZN1ke4RtXV8D/2To9oip9t5o3vuNVof3hJewqetGh3fkXgJGOdHiV6KQLfcoRi8zvBFPz0RTVaAaTrtsL3f5e04IKNZbseCIcZ24VLOMmL91PLmHcp0/E+eErv5zObgIRo/+vJJN6cto1cDRJ2g/AFBLBwhmqoK34AAAADsCAABQSwMEFAAICAgA8K1aVQAAAAAAAAAAAAAAABoAAAB4bC9fcmVscy93b3JrYm9vay54bWwucmVsc62QywrCMBBF9/2KMHub1oWINHUjQrdSPyCk0we2ScjER//eiKIWunDharjzOPcy2fY29OyCjjqjBaRxAgy1MlWnGwHHcr9YwzaPsgP20ocVajtLLNxoEtB6bzeck2pxkBQbizpMauMG6YN0DbdSnWSDfJkkK+6+GZBPmKyoBLiiSoGVo8Vf2KauO4U7o84Daj9jwcmPPVIgStegF/DUceAAn7df/tP+atyJWkT/SfBuhXCPkr7CRBmffDi/A1BLBwiPAgIAvQAAAJgBAABQSwMEFAAICAgA8K1aVQAAAAAAAAAAAAAAAA8AAAB4bC93b3JrYm9vay54bWyNU8lu2zAQvfcrBN5tLV5qG5YDV46QAN0Qp8mZkkYWa4oUyPGWov/eEWWlKdpDDzY5C9+8mXla3pxr6R3BWKFVzMJhwDxQuS6E2sXs22M6mDHPIlcFl1pBzC5g2c3q3fKkzT7Teu/Re2VjViE2C9+3eQU1t0PdgKJIqU3NkUyz821jgBe2AsBa+lEQTP2aC8U6hIX5HwxdliKHjc4PNSjsQAxIjsTeVqKxbLUshYSnriGPN81nXhPthMuc+atX2l+Nl/F8f2hSyo5ZyaUFarTSpy/Zd8iROuJSMq/gCOE8GPcpf0BopEwqQ87W8STgZH/HW9Mh3mkjXrRCLre50VLGDM3hWo2Iosj/Fdm2g3rkme2d52ehCn2KGa3o8uZ+ctdnUWBFC5yOZuPedwdiV2HMZuE8Yh7y7KEdVMwmAT0rhbHoijgUTp0cgeq1FjXkv+nI7aw/PeUG6l6GLVU67wuq7HSCFDoKKzJJjM1CUMDcF5FD7GGo3ZzmLxAM5Sf6oIhC2HIyUH7SBUGsCe0af13O1d6ARE4kh0EQtrBwxo8W3XlVktR0/0tNUmQGOv04KTHvYETMfryfRtNkNo0G0TocDcLwdjL4MBpPBultmtLgkk0yT3+SrBzqgn5JR9+ioW/kAcrthVZ77iS2dpR8yur+HTO/V8TqF1BLBwiLeDPt9wEAAG4DAABQSwMEFAAICAgA8K1aVQAAAAAAAAAAAAAAAA0AAAB4bC9zdHlsZXMueG1s7VhPT9swHL3vU1i+jyQlFJjSIMbUaZcJjSIhTTuYxEks/CeyXWj49Ps5TtOEwiZ1hxWpJ9svv/f88uyodpOLleDokWrDlJzh6CjEiMpM5UyWM3y7mH88w8hYInPClaQz3FCDL9IPibENpzcVpRaBgjQzXFlbfwoCk1VUEHOkairhSaG0IBaGugxMrSnJjSMJHkzCcBoIwiROE7kUc2ENytRSWrDRQ8g333IApzFGXu5K5WDlK5VUE46DNAk6gTQplNzoxNgDaWKe0SPhIBK6ckkE9eNLzbxCQQTjjQcnraQn7kAP94beNi4UxnkfygR7IE1qYi3Vcg4D1PUXTQ3JSlhqL9PW/aW61KSJJicDQtvAvPdK57C1hsvqIZQzUipJ+G09wwXhhuIe+qKe5BpME04LC8KalZVrraoDJ2KtEtBZc9zUXrnvwPQZ5fzG7dO7YvP2IYiuiu19JdsBbH/nvet6pW5A6po3c+VErF7SDvjcloygS85KKeiLwmutLM1s+5m1cJqQdSGqlGbPIO0WsOy2tfsqLcsc5N8XI0tX9oeyxKuApydN6gWAfYhM5u3E8MxUmsmHhZqz/jHEVPc2EFfZA83XJiuWA3VQGayKF0mFm5yiXXPqfL4MaggPk1pvg/djZnIw84aZnb+tg5mDmYOZg5mDmV3MxMf79EsZR3vlJt4rN5N9cnP+n80Ew+O7P8wPzvHRrsf4VbHtfOjnH62/gzN90EU5uCD1sU7xAEXuqjnD392dmw+Su18ybpn0o2CbcKWEIOv66GREOH6TgH6Gv3rSdESavkpaak1l1vSc0xEn/hNnNNfZiHf6Gu+a6gzWoKecjyj+6rsJEwabv0fS31BLBwiMT4YUgwIAAGMRAABQSwMEFAAICAgA8K1aVQAAAAAAAAAAAAAAABgAAAB4bC93b3Jrc2hlZXRzL3NoZWV0MS54bWydVU1v2zwMvr+/QvBhpy1OsqXdR5KhSJd3A7qmaLoN2E2x6FioLGqSnLT99aPkz7U7FMshsUjr4UM+JDP/eFcqdgDrJOpFMhmNEwY6QyH1fpF8u1m/epsw57kWXKGGRXIPLvm4/G9+RHvrCgDPCEC7RVJ4b96nqcsKKLkboQFNnhxtyT0d7T51xgIX8VKp0ul4fJKWXOqkRnhvn4OBeS4zOMesKkH7GsSC4p7ou0Ia16LdiWfhCcuPlGrLZ0DxvPZ0eJM3T/BKmVl0mPtRhmVD7WmW79J3f+R5Z6f/hjSZUaoHGZSatmBl9pwsS25vK/OKsA1VaieV9Pcx4WQ5j/hXluVSebBfUZDIOVcOyGf4Hrbgv5no9zd4RYbWnS7naXN5OReS9AjMmIV8kZxNgjt6v0s4usEzcwUe10SuUty1WNH4v5XiQmogq7dVY7zG4wrVZyoE9ejQ8ROoYq3Byn1B9C4g9x2k57stKMg8iOG9TeUVBdnelztUHYCAnFfKBwoUDm1rPxDjRaJDLRVBogkhVqBUzJFl4d0vhH/yJmEPiOU244oqNBmPB+fLeP2xNdTygt9jFcvSeMNY7RBvgyngjoNCMYtQW8PDCDYsEsbJeoCeTX+urzL3a6BG2kkwfG6lWcd2IZ2bSlAVfkjhC+I1Gc1ezyazk+msqxOp8hlCzck9HdGKeCA1WktTf6wLfQEHUPR+JDS0UYQ6v/QPAg2fc+45kTZWar8xcbxZQV1AI9l3zb7vmMcWattWwwKtfEDtuVrRzgAb+qF5nRafl9lTR1r3/ldu95ICq9hX49Hp29NZ02z9keSIi3M2Pe0+VJEdeo/l3zxFbOYeIEf0g3PazV1lSHEDdisfQk+RnoPuivPYStQcO00SFiA2NsYReNQ3BegNZUsSWEnJxoW5SAxab7mkXtopnt2eafGjkL4bcUbrcTBRGXXWCsuweV0YCg0hrnU+tPJlVe5CNIpdOVg/Nj+W4tzIRfI6JNJq0FsyNDJoGtu2rtY61ogJmeekk/YRv6fZmjdCfDqA7jcYClHvjuULXpoPq/j94leF/sMNrSzHLmkjXWPJ9ctr2NNKsrUzvjeZxp+zedrDBMSazL8hhpqw+HwVYRuseTrMk47d/+vyN1BLBwifqM7iUwMAAKMHAABQSwMEFAAICAgA8K1aVQAAAAAAAAAAAAAAABEAAABkb2NQcm9wcy9jb3JlLnhtbG1SW0+DMBR+91eQvkML02kIsGSa+eISE2c0vtX2jFWhNO3ZcP/eAhvzsrfzXfqd055ms6+6CnZgnWp0TuKIkQC0aKTSZU6eV4vwhgQOuZa8ajTkZA+OzIqLTJhUNBYebWPAogIX+CDtUmFyskE0KaVObKDmLvIO7cV1Y2uOHtqSGi4+eQk0YWxKa0AuOXLaBYZmTCSHSCnGSLO1VR8gBYUKatDoaBzF9ORFsLU7e6BXfjhrhXsDZ61HcXR/OTUa27aN2klv9fPH9HX58NRfNVS6eyoBpMgOg6TCAkeQgQ9Ih3ZH5WVye7dakCJhSRLGLEymq2SSXl6n7Ooto3/Od4FD3diiU0/A1xKcsMqg3+Eg/iI8rrgut/7BC9Dh/by3jFS3yoo7XPqlrxXI+d5nnOE8ZWGnuo9SsN4xwq6F275/gMCh/wh8jQorGOhj+e/zFN9QSwcIvgjeZ1EBAACIAgAAUEsDBBQACAgIAPCtWlUAAAAAAAAAAAAAAAAQAAAAZG9jUHJvcHMvYXBwLnhtbJ2QwWrDMAyG73uKYHpN7IQsK8Vx2Rg7FbZDVnYLnq20HoltYqWkbz+3ZW3P00n6JT5JP1/PQ58cYAzG2ZrkGSMJWOW0sbuafDZv6ZIkAaXVsncWanKEQNbigX+MzsOIBkISCTbUZI/oV5QGtYdBhiy2bex0bhwkxnLcUdd1RsGrU9MAFmnBWEVhRrAadOqvQHIhrg74X6h26nRf2DZHH3mCNzD4XiIITm9p41D2jRlAsChfC/7sfW+UxOiI2JjvEd7PK+hTVmZ5Viw2xk5z+7Ws2qpM7gba+MIPKKQlW7xMptdpwek97ETeXqwW+WPGYpwH/jROb66KX1BLBwjmXhNq+QAAAJoBAABQSwMEFAAICAgA8K1aVQAAAAAAAAAAAAAAABMAAABbQ29udGVudF9UeXBlc10ueG1svZTPT8IwFMfv/BXLrmYteDDGbHBQPCoHPJvSvW2V/kpbcPz3vg4kBidgJJ6W9r3v+3y/bbp80iqZrMF5YXSRjsgwTUBzUwpdF+nL/DG7TSfjQT7fWPAJ9mpfpE0I9o5SzxtQzBNjQWOlMk6xgEtXU8v4ktVAr4fDG8qNDqBDFuKMdJw/QMVWMiTTFre3XJSnyf22L6KKlFkrBWcByzRWaa/OgfRHhGtdHrjLds4IKrse3wjrr34mWF0fAISKyeJ+v+LNQr+kK6DmGY/biRKSGXPhiSlsoK8xCSUXztNHauUO9m7ccmHMkmDzP4G/Ik/QlM+g5SCJbwACUYw7M9VsIQFrTOgTJB82EvwZnINUpqoEh9LwlUIJ8dYBKzsLeEjboWdk7ASedp/RhU3s5x/zgdqZM9bj23PwewOfdxvVmcVB4II4nnxPxNF/TgzxNZVQfmcPctr9isYfUEsHCLPOndFZAQAAuQQAAFBLAQIUABQACAgIAPCtWlVmqoK34AAAADsCAAALAAAAAAAAAAAAAAAAAAAAAABfcmVscy8ucmVsc1BLAQIUABQACAgIAPCtWlWPAgIAvQAAAJgBAAAaAAAAAAAAAAAAAAAAABkBAAB4bC9fcmVscy93b3JrYm9vay54bWwucmVsc1BLAQIUABQACAgIAPCtWlWLeDPt9wEAAG4DAAAPAAAAAAAAAAAAAAAAAB4CAAB4bC93b3JrYm9vay54bWxQSwECFAAUAAgICADwrVpVjE+GFIMCAABjEQAADQAAAAAAAAAAAAAAAABSBAAAeGwvc3R5bGVzLnhtbFBLAQIUABQACAgIAPCtWlWfqM7iUwMAAKMHAAAYAAAAAAAAAAAAAAAAABAHAAB4bC93b3Jrc2hlZXRzL3NoZWV0MS54bWxQSwECFAAUAAgICADwrVpVvgjeZ1EBAACIAgAAEQAAAAAAAAAAAAAAAACpCgAAZG9jUHJvcHMvY29yZS54bWxQSwECFAAUAAgICADwrVpV5l4TavkAAACaAQAAEAAAAAAAAAAAAAAAAAA5DAAAZG9jUHJvcHMvYXBwLnhtbFBLAQIUABQACAgIAPCtWlWzzp3RWQEAALkEAAATAAAAAAAAAAAAAAAAAHANAABbQ29udGVudF9UeXBlc10ueG1sUEsFBgAAAAAIAAgA/QEAAAoPAAAAAA==' + end + let(:decoded_upload) { decode_upload_file(xlsm_base64_data) } + + it { expect(decoded_upload[:extension]).to eq 'xlsm' } + end + end + + describe '#split_base_url' do + it { expect(split_base_url(base64_data)[:data]).to eq Base64.encode64(File.read(test_file_path)).delete("\n") } + end + + context 'with broken base64 strings' do + describe '#decode_upload_file' do + let(:broken_base64_data) do + 'data:text/csv;base64,Zmlyc3RfbmFtZSxsYXN0X25hbWVfcHJlZml4LGxhc3RfbmFtZSxlbWFpbCxiaXJ0aGRheSxhZGRyZXNzLHBvc3Rjb2RlLGNpdHkscGhvbmVfbnVtYmVyLGFsbGVyZ2llcyx2ZWdldGFyaWFuLHN0dWR5LHBpY3R1cmVfcHVibGljYXR😈pb25fcHJlZmVyZW5jZSxlbWVyZ2VuY3lfY29udGFjdCxlbWVyZ2VuY3lfbnVtYmVyLGlmZXNfZGF0YV9zaGFyaW5nX3ByZWZlcmVuY2UsaW5mb19pbl9hbG1hbmFrLGFsbWFuYWtfc3Vic2NyaXB0aW9uX3ByZWZlcmVuY2UsZGlndHVzX3N1YnNjcmlwdGlvbl9wcmVmZXJlbmNlLGxvZ2luX2VuYWJsZWQKQXJ0aMO6cixkZSxLb25pbmctQXJlbmRzLHN0aWpuQGV4YW1wbGUuY29tLDE5NzAtMi0yLEhlbmdlbG9zZXN0cmFhdCAxLDc1MTROQixFbnNjaGVkZSwgKzMxKDApNjEyMzQ1Njc4LCJOb3RlbiwgbGFjdG9zZSIsdHJ1ZSxUZWNobmlzY2hlIE5hdHV1cmt1bmRlLGFsd2F5c19wdWJsaXNoLE0uIGRlIFJ1aXRlciwwNjIyODY1MjU1LHRydWUsZmFsc2Usbm9fc3Vic2NyaXB0aW9uLG5vX3N1YnNjcmlwdGlvbixmYWxzZQpUw6tzdGXDpyx2w7NuLELDvG5kZW5zdMOgdcOfLGtvZW5AZXhhbXBsZS5jb20sMTk3MC0yLTIwLEhlbmdlbG9zZXN0cmFhdCAzLDc1MDBBQSxFbnNjaGVkZSwgKzMxKDApNjEyMzQ1Njc4LCxmYWxzZSxUZWNobmlzY2hlIEt1bmRpZ2hlaWQsYWx3YXlzX3B1Ymxpc2gsTS4gZGUgUnVpdGVyLDA2OTg3NjU0MzIsZmFsc2UsdHJ1ZSxub19zdWJzY3JpcHRpb24sbm9fc3Vic2NyaXB0aW9uLGZhbHNlCkhhbnMgRGF2aWQsJ3QsSG9nZSxwbGVra2llQGV4YW1wbGUuY29tLDE5NzAtMi0yMCxQbGVra2llIDMsNzUwMEFBLE51bnNwZWV0LDA2MTIzNDU2NzgsLGZhbHNlLFRlY2huaXNjaGUgS3VuZGlnaGVpZCxhbHdheXNfcHVibGlzaCxNLiBkZSBSdWl0ZXIsMDY5ODc2NTQzMixmYWxzZSx0cnVlLG5vX3N1YnNjcmlwdGlvbixub19zdWJzY3JpcHRpb24sZmFsc2UK' + end + + it { expect(decode_upload_file(broken_base64_data)[:file]).to be_instance_of Tempfile } + end + + describe '#split_base_url' do + let(:extremely_broken_base64_data) do + 'dataev;base6mlyc3RfbmFtZSxsYXN0X25hbWVfcHJlZml4LGxhc3RfbmFtZSxlbWFpbCxiaXJ0aGRheSxhZGRyZXNzLHBvc3Rjb2RlLGNpdHkscGhvbmVfbnVtYmVyLGFsbGVyZ2llcyx2ZWdldGFyaWFuLHN0dWR5LHBpY3R1cmVfcHVibGljYXRpb25fcHJlZmVyZW5jZSxpYmFuLGliYW5faG9sZGVyPANICLGVtZXJnZW5jeV9jb250YWN0LGVtZXJnZW5jeV9udW1iZXIsaWZlc19kYXRhX3NoYXJpbmdfcHJlZmVyZW5jZSxpbmZvX2luX2FsbWFuYWssYWxtYW5ha19zdWJzY3JpcHRpb25fcHJlZmVyZW5jZSxkaWd0dXNfc3Vic2NyaXB0aW9uX3ByZWZlcmVuY2UsZW5hYmxlZApBcnRow7pyLGRlLEtvbmluZy1BcmVuZHMsc3Rpam5AZXhhbXBsZS5jb20sMTk3MC0yLTIsSGVuZ2Vsb3Nlc3RyYWF0IDEsNzUxNE5CLEVuc2NoZWRlLCArMzEoMCk2MTIzNDU2NzgsIk5vdGVuLCBsYWN0b3NlIix0cnVlLFRlYwhat?' + end + + it { expect(split_base_url(extremely_broken_base64_data)).to be_nil } + end + + # rubocop:enable Layout/LineLength + end +end diff --git a/spec/models/import/transaction_spec.rb b/spec/models/import/transaction_spec.rb index 7a2ec68e..cbe8e631 100644 --- a/spec/models/import/transaction_spec.rb +++ b/spec/models/import/transaction_spec.rb @@ -4,7 +4,7 @@ let(:user) { create(:user, username: 'bestuurder') } let(:test_file) { Rails.root.join('spec', 'support', 'files', 'collection_import.csv') } let(:collection) { create(:collection) } - let(:importer) { described_class.new(test_file, collection) } + let(:importer) { described_class.new({ file: test_file, extension: 'csv' }, collection) } let(:errors) { collection.errors.messages[:import_file] } before do diff --git a/spec/models/import/user_spec.rb b/spec/models/import/user_spec.rb index f109c9ad..e4485584 100644 --- a/spec/models/import/user_spec.rb +++ b/spec/models/import/user_spec.rb @@ -7,7 +7,7 @@ let(:live_run) { true } let(:required_columns) { Import::User::REQUIRED_COLUMNS } - subject(:user_import) { described_class.new(test_file, group) } + subject(:user_import) { described_class.new({ file: test_file, extension: 'csv' }, group) } describe 'when database and required columns are in sync' do it { expect(User.column_names & required_columns).to match_array(required_columns) } From dff6f73e981ad3233fed2a86342a4f026aacb837 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 26 Nov 2022 16:24:10 +0100 Subject: [PATCH 24/46] chore(deps): update dependency paper_trail to v14 (#343) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index 9ace77a5..477c6ceb 100644 --- a/Gemfile +++ b/Gemfile @@ -24,7 +24,7 @@ gem 'jsonapi-authorization', '~> 3.0' gem 'jsonapi-resources', '~> 0.9.1.beta2' gem 'message_bus', '~> 4.0' gem 'mini_magick', '~> 4.6' -gem 'paper_trail', '~> 13.0' +gem 'paper_trail', '~> 14.0' gem 'paranoia', '~> 2.2' gem 'pg', '~> 1.0' gem 'phonelib' diff --git a/Gemfile.lock b/Gemfile.lock index 56534221..6ac6b519 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -273,9 +273,9 @@ GEM nenv (~> 0.1) shellany (~> 0.0) open4 (1.3.4) - paper_trail (13.0.0) - activerecord (>= 5.2) - request_store (~> 1.1) + paper_trail (14.0.0) + activerecord (>= 6.0) + request_store (~> 1.4) parallel (1.22.1) paranoia (2.6.0) activerecord (>= 5.1, < 7.1) @@ -508,7 +508,7 @@ GEM websocket-driver (0.7.5-java) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - zeitwerk (2.6.4) + zeitwerk (2.6.6) PLATFORMS java @@ -555,7 +555,7 @@ DEPENDENCIES message_bus (~> 4.0) mina mini_magick (~> 4.6) - paper_trail (~> 13.0) + paper_trail (~> 14.0) paranoia (~> 2.2) pg (~> 1.0) phonelib From cf57acac7e7862c3c5d799901c6483939df5ff2a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 26 Nov 2022 15:35:50 +0000 Subject: [PATCH 25/46] chore(deps): update all actions (#342) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Wilco --- .github/workflows/continuous-delivery.yml | 4 ++-- .github/workflows/continuous-integration.yml | 6 +++--- .github/workflows/publish-image.yml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index 0705bb4e..a909d1f1 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -166,7 +166,7 @@ jobs: docker-compose up -d - name: Finalize Sentry release - uses: getsentry/action-release@426b54786363ee2ecb27129f04b99cf714a36d38 # tag=v1.2.0 + uses: getsentry/action-release@bd5f874fcda966ba48139b0140fb3ec0cb3aabdd # v1.2.1 env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_PROJECT: ${{ env.PROJECT_NAME }} @@ -205,7 +205,7 @@ jobs: done - name: Update Continuous Delivery check run - uses: guidojw/actions/update-check-run@5faa9418b42a6a77f81e11ae935287900673f98b # tag=v1.3.2 + uses: guidojw/actions/update-check-run@abb0ee8d1336edf73383f2e5a09abd3a22f25b13 # v1.3.3 with: app_id: ${{ env.APP_ID }} private_key: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 50d4b080..05739b1c 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -26,7 +26,7 @@ jobs: ref: ${{ inputs.sha }} - name: Build test image - uses: guidojw/actions/build-docker-image@5faa9418b42a6a77f81e11ae935287900673f98b # tag=v1.3.2 + uses: guidojw/actions/build-docker-image@abb0ee8d1336edf73383f2e5a09abd3a22f25b13 # v1.3.3 with: file: Dockerfile build-args: | @@ -66,7 +66,7 @@ jobs: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.6.21 - name: Load test image - uses: guidojw/actions/load-docker-image@5faa9418b42a6a77f81e11ae935287900673f98b # tag=v1.3.2 + uses: guidojw/actions/load-docker-image@abb0ee8d1336edf73383f2e5a09abd3a22f25b13 # v1.3.3 with: name: app @@ -109,7 +109,7 @@ jobs: echo '::add-matcher::.github/problem-matchers/rspec.json' - name: Load test image - uses: guidojw/actions/load-docker-image@5faa9418b42a6a77f81e11ae935287900673f98b # tag=v1.3.2 + uses: guidojw/actions/load-docker-image@abb0ee8d1336edf73383f2e5a09abd3a22f25b13 # v1.3.3 with: name: app diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index 23b7faac..3d55edde 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -80,7 +80,7 @@ jobs: - name: Create Sentry release if: ${{ !(github.event_name == 'workflow_dispatch' && github.workflow == 'Publish Image') }} - uses: getsentry/action-release@426b54786363ee2ecb27129f04b99cf714a36d38 # tag=v1.2.0 + uses: getsentry/action-release@bd5f874fcda966ba48139b0140fb3ec0cb3aabdd # v1.2.1 env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_PROJECT: ${{ env.PROJECT_NAME }} @@ -108,7 +108,7 @@ jobs: done - name: Update Publish Image check run - uses: guidojw/actions/update-check-run@5faa9418b42a6a77f81e11ae935287900673f98b # tag=v1.3.2 + uses: guidojw/actions/update-check-run@abb0ee8d1336edf73383f2e5a09abd3a22f25b13 # v1.3.3 with: app_id: ${{ env.APP_ID }} private_key: ${{ secrets.APP_PRIVATE_KEY }} From 337cbfb2f636379ff4ad9ec8f8a8d1c5d338876d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 26 Nov 2022 15:46:29 +0000 Subject: [PATCH 26/46] chore(deps): update dependency rhysd/actionlint to v1.6.22 (#339) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 05739b1c..5c32a6e1 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -63,7 +63,7 @@ jobs: - name: Download actionlint run: | - bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.6.21 + bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.6.22 - name: Load test image uses: guidojw/actions/load-docker-image@abb0ee8d1336edf73383f2e5a09abd3a22f25b13 # v1.3.3 From 28bf5b11504f1e4a7aa337659008045a371b6cbd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 26 Nov 2022 15:56:43 +0000 Subject: [PATCH 27/46] chore(deps): update dependency faker to v3 (#340) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 477c6ceb..610736fc 100644 --- a/Gemfile +++ b/Gemfile @@ -54,7 +54,7 @@ group :development, :test do gem 'consistency_fail' gem 'dotenv-rails' gem 'factory_bot_rails' - gem 'faker', '~> 2.9' + gem 'faker', '~> 3.0' gem 'fuubar' gem 'pry-byebug', require: false gem 'pry-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 6ac6b519..25d6306b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -156,7 +156,7 @@ GEM factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) - faker (2.20.0) + faker (3.0.0) i18n (>= 1.8.11, < 2) faraday (2.2.0) faraday-net_http (~> 2.0) @@ -540,7 +540,7 @@ DEPENDENCIES dotenv-rails exifr (~> 1.3) factory_bot_rails - faker (~> 2.9) + faker (~> 3.0) friendly_id (~> 5.2) fuubar guard-rspec From 2fcd251fb361e2e91e012cd509ca022222b95d28 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 26 Nov 2022 16:10:51 +0000 Subject: [PATCH 28/46] chore(deps): update dependency puma to v6 (#332) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 610736fc..21f03c6a 100644 --- a/Gemfile +++ b/Gemfile @@ -28,7 +28,7 @@ gem 'paper_trail', '~> 14.0' gem 'paranoia', '~> 2.2' gem 'pg', '~> 1.0' gem 'phonelib' -gem 'puma', '~> 5.0' +gem 'puma', '~> 6.0' gem 'pundit', '~> 2.0' gem 'rack-attack', '~> 6.0' gem 'rack-cors', '~> 1.0', require: 'rack/cors' diff --git a/Gemfile.lock b/Gemfile.lock index 25d6306b..9dde334a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -298,9 +298,9 @@ GEM pry-rails (0.3.9) pry (>= 0.10.4) public_suffix (4.0.6) - puma (5.6.4) + puma (6.0.0) nio4r (~> 2.0) - puma (5.6.4-java) + puma (6.0.0-java) nio4r (~> 2.0) pundit (2.2.0) activesupport (>= 3.0.0) @@ -561,7 +561,7 @@ DEPENDENCIES phonelib pry-byebug pry-rails - puma (~> 5.0) + puma (~> 6.0) pundit (~> 2.0) rack-attack (~> 6.0) rack-cors (~> 1.0) From 2c35a52cebd720970571439eb91ece7911d4fb86 Mon Sep 17 00:00:00 2001 From: Wilco Date: Wed, 7 Dec 2022 15:49:45 +0100 Subject: [PATCH 29/46] Add nickname (#346) --- app/models/import/user.rb | 2 +- app/models/webdav/contact.rb | 1 + app/resources/v1/user_resource.rb | 8 ++++---- db/migrate/20221204193247_add_nickname_to_users.rb | 5 +++++ db/schema.rb | 3 ++- spec/factories/users.rb | 1 + spec/models/user_spec.rb | 6 ++++++ spec/resources/v1/user_resource_spec.rb | 4 ++-- 8 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 db/migrate/20221204193247_add_nickname_to_users.rb diff --git a/app/models/import/user.rb b/app/models/import/user.rb index 4af3eea1..e9304ba7 100644 --- a/app/models/import/user.rb +++ b/app/models/import/user.rb @@ -64,7 +64,7 @@ def self.lookup_ancestors info_in_almanak almanak_subscription_preference digtus_subscription_preference].freeze - ALLOWED_COLUMNS = %w[first_name last_name_prefix last_name + ALLOWED_COLUMNS = %w[first_name last_name_prefix last_name nickname login_enabled emergency_contact emergency_number ifes_data_sharing_preference info_in_almanak almanak_subscription_preference digtus_subscription_preference diff --git a/app/models/webdav/contact.rb b/app/models/webdav/contact.rb index afc6e528..676b1f4d 100644 --- a/app/models/webdav/contact.rb +++ b/app/models/webdav/contact.rb @@ -47,6 +47,7 @@ def self.user_to_vcard(user) # rubocop:disable Metrics/MethodLength, Metrics/Abc name.family = user.last_name name.additional = user.last_name_prefix if user.last_name_prefix end + maker.nickname = user.nickname maker.birthday = user.birthday if user.birthday maker.add_addr do |address| address.location = 'home' diff --git a/app/resources/v1/user_resource.rb b/app/resources/v1/user_resource.rb index b5bf3f3d..629fba82 100644 --- a/app/resources/v1/user_resource.rb +++ b/app/resources/v1/user_resource.rb @@ -1,5 +1,5 @@ class V1::UserResource < V1::ApplicationResource # rubocop:disable Metrics/ClassLength - attributes :username, :first_name, :last_name_prefix, :last_name, :full_name, + attributes :username, :first_name, :last_name_prefix, :last_name, :full_name, :nickname, :login_enabled, :otp_required, :activated_at, :emergency_contact, :emergency_number, :ifes_data_sharing_preference, :info_in_almanak, :almanak_subscription_preference, :digtus_subscription_preference, :email, :birthday, :address, :postcode, :city, @@ -46,7 +46,7 @@ def avatar_thumb_url # rubocop:disable all def fetchable_fields # Attributes - allowed_keys = %i[username first_name last_name_prefix last_name full_name + allowed_keys = %i[username first_name last_name_prefix last_name full_name nickname avatar_url avatar_thumb_url created_at updated_at id] # Relationships allowed_keys += %i[groups active_groups memberships mail_aliases mandates @@ -69,7 +69,7 @@ def fetchable_fields # rubocop:enable all def self.creatable_fields(context) # rubocop:disable Metrics/MethodLength - attributes = %i[avatar email address postcode city phone_number + attributes = %i[avatar nickname email address postcode city phone_number food_preferences vegetarian study start_study almanak_subscription_preference digtus_subscription_preference emergency_contact emergency_number] @@ -89,7 +89,7 @@ def self.creatable_fields(context) # rubocop:disable Metrics/MethodLength end def self.searchable_fields - %i[email first_name last_name last_name_prefix study] + %i[email first_name last_name last_name_prefix nickname study] end def self.records(options = {}) diff --git a/db/migrate/20221204193247_add_nickname_to_users.rb b/db/migrate/20221204193247_add_nickname_to_users.rb new file mode 100644 index 00000000..4680461d --- /dev/null +++ b/db/migrate/20221204193247_add_nickname_to_users.rb @@ -0,0 +1,5 @@ +class AddNicknameToUsers < ActiveRecord::Migration[6.1] + def change + add_column :users, :nickname, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 0211ec94..84fa6c46 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_10_12_154634) do +ActiveRecord::Schema.define(version: 2022_12_04_193247) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -536,6 +536,7 @@ t.string "user_details_sharing_preference" t.boolean "allow_tomato_sharing" t.string "webdav_secret_key" + t.string "nickname" t.index ["deleted_at"], name: "index_users_on_deleted_at" t.index ["email"], name: "index_users_on_email", unique: true t.index ["login_enabled"], name: "index_users_on_login_enabled" diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 33ca2bf2..1c4f4236 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -3,6 +3,7 @@ first_name { Faker::Name.first_name } last_name_prefix { [nil, 'van', 'de', 'van de'].sample } last_name { Faker::Name.last_name } + nickname { Faker::Name.first_name } birthday { Faker::Date.between(from: 27.years.ago, to: 17.years.ago) } address { Faker::Address.street_address } postcode { Faker::Address.postcode } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f6775dee..e30d0d2b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -36,6 +36,12 @@ it { expect(user).not_to be_valid } end + context 'when without a nickname' do + subject(:user) { build_stubbed(:user, nickname: nil) } + + it { expect(user).to be_valid } + end + context 'when without an address' do subject(:user) { build_stubbed(:user, address: nil) } diff --git a/spec/resources/v1/user_resource_spec.rb b/spec/resources/v1/user_resource_spec.rb index c19a35df..3a7f044d 100644 --- a/spec/resources/v1/user_resource_spec.rb +++ b/spec/resources/v1/user_resource_spec.rb @@ -7,7 +7,7 @@ describe '#fetchable_fields' do let(:basic_fields) do - %i[id created_at updated_at username first_name last_name_prefix last_name full_name + %i[id created_at updated_at username first_name last_name_prefix last_name full_name nickname avatar_url avatar_thumb_url groups active_groups memberships mail_aliases group_mail_aliases permissions user_permissions mandates] end @@ -131,7 +131,7 @@ let(:context) { { user: user, model: another_user } } let(:creatable_fields) { described_class.creatable_fields(context) } let(:basic_fields) do - %i[avatar email address postcode city phone_number + %i[avatar nickname email address postcode city phone_number food_preferences vegetarian study start_study almanak_subscription_preference digtus_subscription_preference emergency_contact emergency_number] From 840b13756272f5700d03756d4fae31243c708270 Mon Sep 17 00:00:00 2001 From: Wilco Date: Mon, 19 Dec 2022 20:34:41 +0100 Subject: [PATCH 30/46] Add vacancies (#344) --- app/controllers/v1/vacancies_controller.rb | 2 + app/models/vacancy.rb | 17 +++++++ app/policies/vacancy_policy.rb | 13 +++++ app/resources/v1/vacancy_resource.rb | 48 +++++++++++++++++++ config/routes.rb | 1 + db/migrate/20221203224253_create_vacancies.rb | 23 +++++++++ db/schema.rb | 17 ++++++- db/seeds/permissions.rb | 9 ++-- spec/factories/vacancy.rb | 19 ++++++++ spec/models/vacancy_spec.rb | 41 ++++++++++++++++ spec/policies/vacancy_policy_spec.rb | 32 +++++++++++++ .../v1/vacancies_controller/create_spec.rb | 16 +++++++ .../v1/vacancies_controller/destroy_spec.rb | 11 +++++ .../v1/vacancies_controller/index_spec.rb | 18 +++++++ .../v1/vacancies_controller/show_spec.rb | 13 +++++ .../v1/vacancies_controller/update_spec.rb | 25 ++++++++++ 16 files changed, 301 insertions(+), 4 deletions(-) create mode 100644 app/controllers/v1/vacancies_controller.rb create mode 100644 app/models/vacancy.rb create mode 100644 app/policies/vacancy_policy.rb create mode 100644 app/resources/v1/vacancy_resource.rb create mode 100644 db/migrate/20221203224253_create_vacancies.rb create mode 100644 spec/factories/vacancy.rb create mode 100644 spec/models/vacancy_spec.rb create mode 100644 spec/policies/vacancy_policy_spec.rb create mode 100644 spec/requests/v1/vacancies_controller/create_spec.rb create mode 100644 spec/requests/v1/vacancies_controller/destroy_spec.rb create mode 100644 spec/requests/v1/vacancies_controller/index_spec.rb create mode 100644 spec/requests/v1/vacancies_controller/show_spec.rb create mode 100644 spec/requests/v1/vacancies_controller/update_spec.rb diff --git a/app/controllers/v1/vacancies_controller.rb b/app/controllers/v1/vacancies_controller.rb new file mode 100644 index 00000000..c40f110e --- /dev/null +++ b/app/controllers/v1/vacancies_controller.rb @@ -0,0 +1,2 @@ +class V1::VacanciesController < V1::ApplicationController +end diff --git a/app/models/vacancy.rb b/app/models/vacancy.rb new file mode 100644 index 00000000..4338b84c --- /dev/null +++ b/app/models/vacancy.rb @@ -0,0 +1,17 @@ +class Vacancy < ApplicationRecord + mount_base64_uploader :cover_photo, CoverPhotoUploader + + belongs_to :author, class_name: 'User' + belongs_to :group, optional: true + + validates :title, presence: true + validates :description, presence: true + + def owners + if group.present? + group.active_users + [author] + else + [author] + end + end +end diff --git a/app/policies/vacancy_policy.rb b/app/policies/vacancy_policy.rb new file mode 100644 index 00000000..c9a35cfc --- /dev/null +++ b/app/policies/vacancy_policy.rb @@ -0,0 +1,13 @@ +class VacancyPolicy < ApplicationPolicy + def update? + record.owners.include?(user) || super + end + + def create_with_group?(_group) + true + end + + def replace_group?(_group) + true + end +end diff --git a/app/resources/v1/vacancy_resource.rb b/app/resources/v1/vacancy_resource.rb new file mode 100644 index 00000000..1789897a --- /dev/null +++ b/app/resources/v1/vacancy_resource.rb @@ -0,0 +1,48 @@ +class V1::VacancyResource < V1::ApplicationResource + attributes :title, :description, :description_camofied, :workload, :workload_peak, + :contact, :deadline, :author_name, :avatar_thumb_url, :cover_photo_url, :cover_photo + + def cover_photo_url + @model.cover_photo.url + end + + def description_camofied + camofy(@model['description']) + end + + def author_name + @model.group ? @model.group.name : @model.author.full_name + end + + def avatar_thumb_url + @model.group ? @model.group.avatar.thumb.url : @model.author.avatar.thumb.url + end + + has_one :group, always_include_linkage_data: true + has_one :author, always_include_linkage_data: true + + def fetchable_fields + super - [:cover_photo] + end + + def self.creatable_fields(_context) + %i[title description group workload workload_peak contact + deadline cover_photo] + end + + before_create do + @model.author_id = current_user.id + end + + before_save do + user_is_member_of_group? + end + + def user_is_member_of_group? + return true unless @model.group + return true if current_user.permission?(:update, @model) + return if current_user.current_group_member?(@model.group) + + raise AmberError::NotMemberOfGroupError + end +end diff --git a/config/routes.rb b/config/routes.rb index 3311afbf..23d33e5d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -71,6 +71,7 @@ get 'users/me/nextcloud', to: 'users#nextcloud' jsonapi_resources :quickpost_messages + jsonapi_resources :vacancies namespace :debit do jsonapi_resources :collections do diff --git a/db/migrate/20221203224253_create_vacancies.rb b/db/migrate/20221203224253_create_vacancies.rb new file mode 100644 index 00000000..2105c86f --- /dev/null +++ b/db/migrate/20221203224253_create_vacancies.rb @@ -0,0 +1,23 @@ +class CreateVacancies < ActiveRecord::Migration[6.1] + def change + create_table :vacancies do |t| + t.string :title, null: false + t.string :description + t.string :workload + t.string :workload_peak + t.string :cover_photo + t.string :contact + t.date :deadline + t.integer :author_id, null: false + t.integer :group_id + + t.timestamps + t.datetime :deleted_at + end + + Permission.create(name: 'vacancy.create') + Permission.create(name: 'vacancy.read') + Permission.create(name: 'vacancy.update') + Permission.create(name: 'vacancy.destroy') + end +end diff --git a/db/schema.rb b/db/schema.rb index 84fa6c46..8cfbe9cc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_12_04_193247) do +ActiveRecord::Schema.define(version: 2022_12_14_162847) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -543,6 +543,21 @@ t.index ["username"], name: "index_users_on_username", unique: true end + create_table "vacancies", force: :cascade do |t| + t.string "title", null: false + t.string "description" + t.string "workload" + t.string "workload_peak" + t.string "cover_photo" + t.string "contact" + t.date "deadline" + t.integer "author_id", null: false + t.integer "group_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.datetime "deleted_at" + end + create_table "versions", id: :serial, force: :cascade do |t| t.string "item_type", null: false t.integer "item_id", null: false diff --git a/db/seeds/permissions.rb b/db/seeds/permissions.rb index 3497ef4d..25a2066a 100644 --- a/db/seeds/permissions.rb +++ b/db/seeds/permissions.rb @@ -50,7 +50,8 @@ def create_permissions(permission_map) 'static_page' => %i[create read update destroy], 'debit/collection' => %i[create read update destroy], 'debit/transaction' => %i[create read update destroy], - 'debit/mandate' => %i[create read update] + 'debit/mandate' => %i[create read update], + 'vacancy' => %i[create read update destroy] } bestuur.permissions = create_permissions(all_permissions_map) @@ -84,7 +85,8 @@ def create_permissions(permission_map) 'permission' => %i[read], 'static_page' => %i[read], 'debit/collection' => %i[read], - 'debit/transaction' => [] + 'debit/transaction' => [], + 'vacancy' => %i[create read] } members.permissions = create_permissions(member_permission_map) @@ -113,7 +115,8 @@ def create_permissions(permission_map) 'form/closed_question_answer' => %i[read], 'form/open_question_answer' => %i[read], 'permission' => [:read], - 'static_page' => %i[read] + 'static_page' => %i[read], + 'vacancy' => [] } old_members.permissions = create_permissions(old_members_permission_map) diff --git a/spec/factories/vacancy.rb b/spec/factories/vacancy.rb new file mode 100644 index 00000000..ebc22263 --- /dev/null +++ b/spec/factories/vacancy.rb @@ -0,0 +1,19 @@ +FactoryBot.define do + factory :vacancy do + author do + FactoryBot.create(:user) + end + + group do + FactoryBot.create(:group, + users: [author]) + end + + title { Faker::Book.title } + description { Faker::Hipster.paragraph } + workload { Faker::Hipster.paragraph } + workload_peak { Faker::Hipster.paragraph } + contact { Faker::PhoneNumber.phone_number } + deadline { Faker::Date.between(from: 1.day.from_now, to: 10.days.from_now) } + end +end diff --git a/spec/models/vacancy_spec.rb b/spec/models/vacancy_spec.rb new file mode 100644 index 00000000..62825c08 --- /dev/null +++ b/spec/models/vacancy_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +RSpec.describe Vacancy, type: :model do + subject(:vacancy) { build_stubbed(:vacancy) } + + describe '#valid' do + it { expect(vacancy).to be_valid } + + context 'when without an author' do + subject(:vacancy) { build_stubbed(:vacancy, author: nil) } + + it { expect(vacancy).not_to be_valid } + end + + context 'when without a group' do + subject(:vacancy) { build_stubbed(:vacancy, group: nil) } + + it { expect(vacancy).to be_valid } + end + + context 'when without a title' do + subject(:vacancy) { build_stubbed(:vacancy, title: nil) } + + it { expect(vacancy).not_to be_valid } + end + + context 'when without a description' do + subject(:vacancy) { build_stubbed(:vacancy, description: nil) } + + it { expect(vacancy).not_to be_valid } + end + end + + describe '#owners' do + it_behaves_like 'a model with group owners' + end + + describe '#save' do + it_behaves_like 'a model accepting a base 64 image as', :cover_photo + end +end diff --git a/spec/policies/vacancy_policy_spec.rb b/spec/policies/vacancy_policy_spec.rb new file mode 100644 index 00000000..16dba5d8 --- /dev/null +++ b/spec/policies/vacancy_policy_spec.rb @@ -0,0 +1,32 @@ +require 'rails_helper' + +RSpec.describe VacancyPolicy, type: :policy do + subject(:policy) { described_class } + + let(:user) { build(:user) } + + permissions :update? do + describe 'when vacancy is not owned' do + it { expect(policy).not_to permit(user, build_stubbed(:vacancy)) } + end + + describe 'when vacancy is owned' do + it { expect(policy).to permit(user, build_stubbed(:vacancy, author: user)) } + end + + describe 'when with permission' do + let(:record_permission) { 'vacancy.update' } + let(:user) { create(:user, user_permission_list: [record_permission]) } + + it { expect(policy).to permit(user, build_stubbed(:vacancy)) } + end + end + + describe '#create_with_group?' do + it { expect(policy.new(nil, nil).create_with_group?(nil)).to be true } + end + + describe '#replace_group?' do + it { expect(policy.new(nil, nil).replace_group?(nil)).to be true } + end +end diff --git a/spec/requests/v1/vacancies_controller/create_spec.rb b/spec/requests/v1/vacancies_controller/create_spec.rb new file mode 100644 index 00000000..cf28fae4 --- /dev/null +++ b/spec/requests/v1/vacancies_controller/create_spec.rb @@ -0,0 +1,16 @@ +require 'rails_helper' + +describe V1::VacanciesController do + describe 'POST /vacancies/:id', version: 1 do + let(:record) { build_stubbed(:vacancy) } + let(:record_url) { '/v1/vacancies' } + let(:record_permission) { 'vacancy.create' } + + it_behaves_like 'a creatable and permissible model' do + let(:invalid_attributes) { { title: '' } } + end + + it_behaves_like 'a creatable model with group' + it_behaves_like 'a creatable model with author' + end +end diff --git a/spec/requests/v1/vacancies_controller/destroy_spec.rb b/spec/requests/v1/vacancies_controller/destroy_spec.rb new file mode 100644 index 00000000..cc810ec6 --- /dev/null +++ b/spec/requests/v1/vacancies_controller/destroy_spec.rb @@ -0,0 +1,11 @@ +require 'rails_helper' + +describe V1::VacanciesController do + describe 'DELETE /vacancies/:id', version: 1 do + let(:record) { create(:vacancy) } + let(:record_url) { "/v1/vacancies/#{record.id}" } + let(:record_permission) { 'vacancy.destroy' } + + it_behaves_like 'a destroyable and permissible model' + end +end diff --git a/spec/requests/v1/vacancies_controller/index_spec.rb b/spec/requests/v1/vacancies_controller/index_spec.rb new file mode 100644 index 00000000..816d53b5 --- /dev/null +++ b/spec/requests/v1/vacancies_controller/index_spec.rb @@ -0,0 +1,18 @@ +require 'rails_helper' + +describe V1::VacanciesController do + describe 'GET /vacancies', version: 1 do + let(:records) { create_list(:vacancy, 3) } + let(:record_url) { '/v1/vacancies' } + let(:record_permission) { 'vacancy.read' } + + before { Bullet.enable = false } + + after { Bullet.enable = true } + + subject(:request) { get(record_url) } + + it_behaves_like 'a permissible model' + it_behaves_like 'an indexable model' + end +end diff --git a/spec/requests/v1/vacancies_controller/show_spec.rb b/spec/requests/v1/vacancies_controller/show_spec.rb new file mode 100644 index 00000000..9023ca77 --- /dev/null +++ b/spec/requests/v1/vacancies_controller/show_spec.rb @@ -0,0 +1,13 @@ +require 'rails_helper' + +describe V1::VacanciesController do + describe 'GET /vacancies/:id', version: 1 do + subject(:request) { get(record_url) } + + let(:record) { create(:vacancy) } + let(:record_url) { "/v1/vacancies/#{record.id}" } + let(:record_permission) { 'vacancy.read' } + + it_behaves_like 'a permissible model' + end +end diff --git a/spec/requests/v1/vacancies_controller/update_spec.rb b/spec/requests/v1/vacancies_controller/update_spec.rb new file mode 100644 index 00000000..95ba2154 --- /dev/null +++ b/spec/requests/v1/vacancies_controller/update_spec.rb @@ -0,0 +1,25 @@ +require 'rails_helper' + +describe V1::VacanciesController do + describe 'PUT /vacancies/:id', version: 1 do + let(:record) { create(:vacancy) } + let(:record_url) { "/v1/vacancies/#{record.id}" } + let(:record_permission) { 'vacancy.update' } + + it_behaves_like 'an updatable and permissible model', response: :ok do + let(:invalid_attributes) { { title: '' } } + end + + it_behaves_like 'an updatable model with group' + + context 'when with permission' do + include_context 'when authenticated' do + let(:user) { create(:user, user_permission_list: [record_permission]) } + end + + subject(:request) { put(record_url) } + + it { expect { request && record.reload }.not_to(change(record, :author)) } + end + end +end From a829fe945d7904963642ab89b063fc5608865a7d Mon Sep 17 00:00:00 2001 From: Wilco Date: Mon, 19 Dec 2022 21:48:03 +0100 Subject: [PATCH 31/46] Update group types (#349) --- app/models/activity.rb | 2 +- app/models/group.rb | 3 ++- .../20221207160057_update_activity_and_group_categories.rb | 6 ++++++ db/schema.rb | 2 +- db/seeds/users_and_groups.rb | 2 +- spec/factories/activities.rb | 2 +- spec/factories/groups.rb | 2 +- spec/models/activity_spec.rb | 2 +- 8 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20221207160057_update_activity_and_group_categories.rb diff --git a/app/models/activity.rb b/app/models/activity.rb index 727a4f82..0bf89606 100644 --- a/app/models/activity.rb +++ b/app/models/activity.rb @@ -34,7 +34,7 @@ class Activity < ApplicationRecord def self.categories %w[algemeen societeit vorming dinsdagkring woensdagkring - choose ifes ozon disputen jaargroepen huizen extern eerstejaars] + choose ifes ozon disputen kiemgroepen huizen extern eerstejaars curiositates] end def full_day? diff --git a/app/models/group.rb b/app/models/group.rb index 8d89a2a6..c442cf52 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -24,6 +24,7 @@ class Group < ApplicationRecord validates :name, presence: true validates :administrative, inclusion: [true, false] validates :kind, presence: true, inclusion: { - in: %w[bestuur commissie dispuut genootschap groep huis jaargroep werkgroep kring lichting] + in: %w[bestuur commissie dispuut genootschap groep huis kiemgroep werkgroep kring lichting + curiositas] } end diff --git a/db/migrate/20221207160057_update_activity_and_group_categories.rb b/db/migrate/20221207160057_update_activity_and_group_categories.rb new file mode 100644 index 00000000..053e4e79 --- /dev/null +++ b/db/migrate/20221207160057_update_activity_and_group_categories.rb @@ -0,0 +1,6 @@ +class UpdateActivityAndGroupCategories < ActiveRecord::Migration[6.1] + def change + Activity.where(category: 'jaargroepen').update(category: 'kiemgroepen') + Group.where(kind: 'jaargroep').update(kind: 'kiemgroep') + end +end diff --git a/db/schema.rb b/db/schema.rb index 8cfbe9cc..146243fa 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_12_14_162847) do +ActiveRecord::Schema.define(version: 2022_12_19_204657) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/db/seeds/users_and_groups.rb b/db/seeds/users_and_groups.rb index 1e373aa4..ef47a2c6 100644 --- a/db/seeds/users_and_groups.rb +++ b/db/seeds/users_and_groups.rb @@ -54,7 +54,7 @@ function: Faker::Job.title) end -FactoryBot.create_list(:group, 6, kind: 'jaargroep', +FactoryBot.create_list(:group, 6, kind: 'kiemgroep', users: leden.sample(6)) FactoryBot.create_list(:group, 3, kind: 'huis', diff --git a/spec/factories/activities.rb b/spec/factories/activities.rb index 9bfc8de3..5b1c464c 100644 --- a/spec/factories/activities.rb +++ b/spec/factories/activities.rb @@ -11,7 +11,7 @@ end_time { Faker::Time.between(from: 1.day.from_now, to: 2.days.from_now) } category do %w[algemeen societeit vorming dinsdagkring woensdagkring - choose ifes ozon disputen jaargroepen huizen extern eerstejaars].sample + choose ifes ozon disputen kiemgroepen huizen extern eerstejaars curiositates].sample end publicly_visible { false } diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb index 812e7b01..ecf2b8f9 100644 --- a/spec/factories/groups.rb +++ b/spec/factories/groups.rb @@ -3,7 +3,7 @@ name { Faker::Team.name } description { Faker::Lorem.paragraph } kind do - %w[bestuur commissie dispuut genootschap groep huis jaargroep werkgroep kring lichting].sample + %w[bestuur commissie dispuut genootschap groep huis kiemgroep werkgroep kring lichting].sample end recognized_at_gma { 'ALV 21' } rejected_at_gma { 'ALV 218' } diff --git a/spec/models/activity_spec.rb b/spec/models/activity_spec.rb index 3cc3047a..35c803de 100644 --- a/spec/models/activity_spec.rb +++ b/spec/models/activity_spec.rb @@ -180,7 +180,7 @@ let(:record) do build_stubbed(:activity, category: %w[algemeen sociëteit vorming dinsdagkring woensdagkring - disputen jaargroepen huizen extern].sample) + disputen kiemgroepen huizen extern curiositates].sample) end it { expect(record.humanized_category).to eq record.category.capitalize } From 69047800236b6f8b5856127ac6dc1f36f783f3a2 Mon Sep 17 00:00:00 2001 From: Wilco Date: Mon, 19 Dec 2022 22:00:40 +0100 Subject: [PATCH 32/46] Add birthday to ical (#350) Co-authored-by: Hein Huijskes <44364301+HeinHuijskes@users.noreply.github.com> --- app/controllers/v1/activities_controller.rb | 34 +++++++++++++++---- app/models/user.rb | 13 +++++++ spec/models/user_spec.rb | 26 ++++++++++++++ .../v1/activities_controller/ical_spec.rb | 25 +++++++++++--- 4 files changed, 87 insertions(+), 11 deletions(-) diff --git a/app/controllers/v1/activities_controller.rb b/app/controllers/v1/activities_controller.rb index 53afc2d5..d4dcdc44 100644 --- a/app/controllers/v1/activities_controller.rb +++ b/app/controllers/v1/activities_controller.rb @@ -18,14 +18,24 @@ def generate_alias render json: alias_response("#{mail_alias}@csvalpha.nl") end - def ical + def ical # rubocop:disable Metrics/AbcSize, Metrics/MethodLength return head :unauthorized unless authenticate_user_by_ical_secret_key - permitted_categories = (params[:categories].try(:split, ',') & Activity.categories) || + requested_categories = params[:categories].try(:split, ',') + + permitted_categories = (requested_categories & Activity.categories) || Activity.categories - activities_for_ical(permitted_categories).map do |act| + activities_for_ical(permitted_categories).each do |act| calendar.add_event(act.to_ical) end + + if ical_add_birthdays?(requested_categories) + users_for_ical.each do |user| + user_ical = user.to_ical + calendar.add_event(user_ical) if user_ical + end + end + render plain: calendar.to_ical, content_type: 'text/calendar' end @@ -65,10 +75,22 @@ def activities_for_ical(categories) .where(start_time: (3.months.ago..1.year.from_now)) end + def users_for_ical + User.active_users_for_group(Group.find_by(name: 'Leden')) + .where.not(birthday: nil) + end + def authenticate_user_by_ical_secret_key - user = User.activated.login_enabled.find_by(id: params[:user_id], ical_secret_key: params[:key]) - return false unless user + @user = User.activated.login_enabled.find_by(id: params[:user_id], + ical_secret_key: params[:key]) + return false unless @user + + @user.permission?(:read, Activity) + end + + def ical_add_birthdays?(requested_categories) + return false unless @user.permission?(:read, User) - user.permission?(:read, Activity) + requested_categories.nil? || requested_categories.include?('birthdays') end end diff --git a/app/models/user.rb b/app/models/user.rb index f6e498e9..b85a726d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -178,6 +178,19 @@ def generate_webdav_secret_key self.webdav_secret_key = SecureRandom.hex(32) end + def to_ical # rubocop:disable Metrics/AbcSize + return unless birthday + + date = birthday.change(year: Time.zone.now.year) + date = date.next_year if date < 3.months.ago + event = Icalendar::Event.new + event.dtstart = Icalendar::Values::Date.new(date) + event.dtend = Icalendar::Values::Date.new(date.tomorrow) + event.summary = "Verjaardag #{full_name}" + event.description = "#{first_name} wordt vandaag #{date.year - birthday.year} jaar!" + event + end + private def downcase_email! diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e30d0d2b..62ebff01 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -634,6 +634,32 @@ end end + describe '#to_ical' do + context 'when without birthday' do + subject(:user) do + build_stubbed(:user, birthday: nil) + end + + it { expect(user.to_ical).to be_nil } + end + + context 'when with birthday' do + subject(:user) do + build_stubbed(:user) + end + + let(:date) do + date = user.birthday.change(year: Time.zone.now.year) + date = date.next_year if date < 3.months.ago + date + end + + it { expect(user.to_ical.description).to include((date.year - user.birthday.year).to_s) } + it { expect(user.to_ical.dtstart).to eq date } + it { expect(user.to_ical.dtend).to eq date.tomorrow } + end + end + describe '.password_reset_url' do it { expect(described_class.password_reset_url).to include('forgot_password') } end diff --git a/spec/requests/v1/activities_controller/ical_spec.rb b/spec/requests/v1/activities_controller/ical_spec.rb index f4577991..39b9eccd 100644 --- a/spec/requests/v1/activities_controller/ical_spec.rb +++ b/spec/requests/v1/activities_controller/ical_spec.rb @@ -3,11 +3,15 @@ describe V1::ActivitiesController, type: :controller do describe 'GET /ical/activities' do let(:activity) { create(:activity) } - let(:permissions) { ['activity.read'] } + let(:permissions) { ['activity.read', 'user.read'] } let(:user) do create(:user, activated_at: Time.zone.now, user_permission_list: permissions) end + let(:birthday_users) do + User.active_users_for_group(Group.find_by(name: 'Leden')) + .where.not(birthday: nil) + end before { activity } @@ -42,7 +46,7 @@ it_behaves_like '401 Unauthorized' end - describe 'without permission' do + describe 'without activity permission' do let(:user) { create(:user, activated_at: Time.zone.now) } subject(:request) { get('ical', params: { user_id: user.id, key: user.ical_secret_key }) } @@ -50,6 +54,17 @@ it_behaves_like '401 Unauthorized' end + describe 'without user permission' do + let(:user) do + create(:user, activated_at: Time.zone.now, user_permission_list: ['activity.read']) + end + let(:ical_events) { Icalendar::Calendar.parse(request.body).first.events } + + subject(:request) { get('ical', params: { user_id: user.id, key: user.ical_secret_key }) } + + it { expect(ical_events.count).to eq Activity.count } + end + describe 'with activated, login_enabled account with permission and correct ical secret' do subject(:request) { get('ical', params: { user_id: user.id, key: user.ical_secret_key }) } @@ -65,13 +80,13 @@ let(:ical_events) { Icalendar::Calendar.parse(request.body).first.events } - it { expect(ical_events.count).to eq Activity.count } + it { expect(ical_events.count).to eq Activity.count + birthday_users.count } end describe 'with a subset of all categories' do subject(:request) do get('ical', params: { user_id: user.id, key: user.ical_secret_key, - categories: 'algemeen,sociëteit,vorming,not_a_valid_category' }) + categories: 'algemeen,sociëteit,vorming,invalid_cat,birthdays' }) end let(:activity) { create(:activity, category: 'algemeen') } @@ -96,7 +111,7 @@ it_behaves_like '200 OK' it { expect(ical_events.map(&:summary).to_s).to include(activity.title) } it { expect(request.body).not_to include(filtered_activity.title) } - it { expect(ical_events.count).to eq 2 } + it { expect(ical_events.count).to eq 2 + birthday_users.count } end end end From 20f3a96b03768877458b39cf1c9b6c8ddbdc4079 Mon Sep 17 00:00:00 2001 From: Wilco Date: Mon, 19 Dec 2022 22:36:56 +0100 Subject: [PATCH 33/46] Update user allow_tomato_sharing when authorized (#347) Co-authored-by: Hein Huijskes <44364301+HeinHuijskes@users.noreply.github.com> --- config/initializers/doorkeeper.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 1120ef98..93e45f79 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -37,4 +37,16 @@ resource_owner_authenticator do User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token end + + after_successful_authorization do |_, auth| + if auth.auth.is_a?(Doorkeeper::OAuth::CodeResponse) + is_tomato = auth.auth.pre_auth.scopes.include?('tomato') + user = auth.auth.pre_auth.resource_owner + + if is_tomato && !user.allow_tomato_sharing + user.allow_tomato_sharing = true + user.save! + end + end + end end From 2a3ae94d33530b58589952cfba7e5e60d3841d1a Mon Sep 17 00:00:00 2001 From: Wilco Date: Mon, 19 Dec 2022 22:54:44 +0100 Subject: [PATCH 34/46] Add documentation for sofia doorkeeper (#352) --- config/initializers/doorkeeper.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 93e45f79..5dad1580 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -38,8 +38,11 @@ User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token end + # https://github.com/doorkeeper-gem/doorkeeper/blob/v5.4.0/app/controllers/doorkeeper/authorizations_controller.rb#L123 after_successful_authorization do |_, auth| + # To SOFIA, a CodeResponse is returned if auth.auth.is_a?(Doorkeeper::OAuth::CodeResponse) + # We are only interested authorization for the tomato scope is_tomato = auth.auth.pre_auth.scopes.include?('tomato') user = auth.auth.pre_auth.resource_owner From 1efc59c9c10641e26f34f7dac3bada1fa75e4b90 Mon Sep 17 00:00:00 2001 From: Guido de Jong <35309288+guidojw@users.noreply.github.com> Date: Wed, 8 Mar 2023 19:56:01 +0100 Subject: [PATCH 35/46] feat: support merge queue (#362) --- .github/workflows/continuous-integration.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 5c32a6e1..dd445983 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -4,6 +4,7 @@ on: push: branches: [staging, master] pull_request: + merge_group: workflow_call: inputs: sha: From 7d0f72910b89f3e1dc7460bcdacedd9ced534b11 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 Mar 2023 20:55:56 +0000 Subject: [PATCH 36/46] chore(deps): update dependency rhysd/actionlint to v1.6.23 (#354) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index dd445983..6da133b5 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -64,7 +64,7 @@ jobs: - name: Download actionlint run: | - bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.6.22 + bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.6.23 - name: Load test image uses: guidojw/actions/load-docker-image@abb0ee8d1336edf73383f2e5a09abd3a22f25b13 # v1.3.3 From c5295dee1bc34f0345a87041141a74908021032a Mon Sep 17 00:00:00 2001 From: Wilco Date: Wed, 8 Mar 2023 22:20:40 +0100 Subject: [PATCH 37/46] Add ImprovMX logs (#363) --- .../ingresses/improvmx/inbound_emails_controller.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/action_mailbox/ingresses/improvmx/inbound_emails_controller.rb b/app/controllers/action_mailbox/ingresses/improvmx/inbound_emails_controller.rb index 34d26e22..9523c91d 100644 --- a/app/controllers/action_mailbox/ingresses/improvmx/inbound_emails_controller.rb +++ b/app/controllers/action_mailbox/ingresses/improvmx/inbound_emails_controller.rb @@ -19,7 +19,7 @@ def raw_email retrieve_email(params.require('raw_url')) end - def retrieve_email(url) + def retrieve_email(url) # rubocop:disable Metrics/AbcSize uri = URI(url) request = Net::HTTP::Get.new uri @@ -28,6 +28,10 @@ def retrieve_email(url) response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http| http.request(request) end + + logger.info response.code + logger.info response.body + response.body end end From bd79893db66fc19c6941efd1121b420018d13b1e Mon Sep 17 00:00:00 2001 From: Wilco Date: Thu, 9 Mar 2023 10:44:48 +0100 Subject: [PATCH 38/46] Update postgres to 14.7 (#364) --- .github/workflows/continuous-integration.yml | 4 ++-- docker-compose.development.yml | 2 +- docker-compose.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 6da133b5..e0b52af9 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -40,7 +40,7 @@ jobs: needs: build services: db: - image: postgres:14.5 + image: postgres:14.7 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -88,7 +88,7 @@ jobs: needs: build services: db: - image: postgres:14.5 + image: postgres:14.7 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres diff --git a/docker-compose.development.yml b/docker-compose.development.yml index fd147745..09578e0f 100644 --- a/docker-compose.development.yml +++ b/docker-compose.development.yml @@ -2,7 +2,7 @@ version: '3' services: db: - image: postgres:14.2 + image: postgres:14.7 env_file: .env volumes: - postgres_data:/var/lib/postgresql/data diff --git a/docker-compose.yml b/docker-compose.yml index 388c9d98..5de27372 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ version: '2' services: db: env_file: .env - image: postgres:14.2 + image: postgres:14.7 volumes: - postgres_data:/var/lib/postgresql/data redis: From dfdbfbcc2615a8cfb6704955b308ea035466cb12 Mon Sep 17 00:00:00 2001 From: Guido de Jong <35309288+guidojw@users.noreply.github.com> Date: Thu, 9 Mar 2023 17:30:49 +0100 Subject: [PATCH 39/46] refactor: use new configuration variables (#361) * refactor: use new configuration variables * refactor: update lint ignore --- .github/workflows/cleanup-registry.yml | 2 +- .github/workflows/continuous-delivery.yml | 7 +++---- .github/workflows/continuous-integration.yml | 2 +- .github/workflows/publish-image.yml | 12 +++++------- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/cleanup-registry.yml b/.github/workflows/cleanup-registry.yml index ae66fdd3..131a5485 100644 --- a/.github/workflows/cleanup-registry.yml +++ b/.github/workflows/cleanup-registry.yml @@ -21,4 +21,4 @@ jobs: account-type: org org-name: ${{ github.repository_owner }} skip-tags: latest,staging - token: ${{ secrets.PAT }} + token: ${{ secrets.GH_PAT }} diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index a909d1f1..bfbadd25 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -13,8 +13,6 @@ concurrency: env: PROJECT_NAME: amber-api - SENTRY_ORG: csvalpha - APP_ID: 152333 jobs: branch_check: @@ -169,6 +167,7 @@ jobs: uses: getsentry/action-release@bd5f874fcda966ba48139b0140fb3ec0cb3aabdd # v1.2.1 env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ vars.SENTRY_ORG_NAME }} SENTRY_PROJECT: ${{ env.PROJECT_NAME }} with: environment: ${{ needs.metadata.outputs.stage }} @@ -207,8 +206,8 @@ jobs: - name: Update Continuous Delivery check run uses: guidojw/actions/update-check-run@abb0ee8d1336edf73383f2e5a09abd3a22f25b13 # v1.3.3 with: - app_id: ${{ env.APP_ID }} - private_key: ${{ secrets.APP_PRIVATE_KEY }} + app_id: ${{ vars.GH_APP_ID }} + private_key: ${{ secrets.GH_APP_PRIVATE_KEY }} sha: ${{ needs.merge.outputs.sha }} name: Continuous Delivery conclusion: ${{ steps.get_conclusion.outputs.conclusion }} diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index e0b52af9..76bee207 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -76,7 +76,7 @@ jobs: RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} run: | EXIT_STATUS=0 - ./actionlint -ignore 'property "app_private_key" is not defined' -ignore 'SC2153:' \ + ./actionlint -ignore 'property "gh_app_private_key" is not defined' -ignore 'SC2153:' \ -ignore 'property "sha" is not defined in object type {}' || EXIT_STATUS=$? docker run -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_HOST=localhost -e \ RAILS_MASTER_KEY --network=host app bin/ci.sh lint || EXIT_STATUS=$? diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index 3d55edde..0a14ec1b 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -17,9 +17,6 @@ on: env: PROJECT_NAME: amber-api - REGISTRY_URL: ghcr.io - SENTRY_ORG: csvalpha - APP_ID: 152333 jobs: metadata: @@ -62,7 +59,7 @@ jobs: - name: Login to GitHub Container Registry uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # tag=v2.1.0 with: - registry: ${{ env.REGISTRY_URL }} + registry: ${{ vars.DOCKER_REGISTRY_URL }} username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} @@ -75,7 +72,7 @@ jobs: cache-from: type=gha,scope=main cache-to: type=gha,scope=main tags: | - ${{ env.REGISTRY_URL }}/${{ github.repository_owner }}/${{ env.PROJECT_NAME }}:${{ + ${{ vars.DOCKER_REGISTRY_URL }}/${{ github.repository_owner }}/${{ env.PROJECT_NAME }}:${{ needs.metadata.outputs.tag }} - name: Create Sentry release @@ -83,6 +80,7 @@ jobs: uses: getsentry/action-release@bd5f874fcda966ba48139b0140fb3ec0cb3aabdd # v1.2.1 env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ vars.SENTRY_ORG_NAME }} SENTRY_PROJECT: ${{ env.PROJECT_NAME }} with: finalize: false @@ -110,8 +108,8 @@ jobs: - name: Update Publish Image check run uses: guidojw/actions/update-check-run@abb0ee8d1336edf73383f2e5a09abd3a22f25b13 # v1.3.3 with: - app_id: ${{ env.APP_ID }} - private_key: ${{ secrets.APP_PRIVATE_KEY }} + app_id: ${{ vars.GH_APP_ID }} + private_key: ${{ secrets.GH_APP_PRIVATE_KEY }} name: Publish Image conclusion: ${{ steps.get_conclusion.outputs.conclusion }} details_url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} From 79fc614ffb3dcdd6fc58b768d1c4323b7eaa411f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Mar 2023 17:31:17 +0100 Subject: [PATCH 40/46] chore(deps): update snok/container-retention-policy action to v2 (#360) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/cleanup-registry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cleanup-registry.yml b/.github/workflows/cleanup-registry.yml index 131a5485..1fc0dcec 100644 --- a/.github/workflows/cleanup-registry.yml +++ b/.github/workflows/cleanup-registry.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Delete old versions - uses: snok/container-retention-policy@6601a342b42bf08909bbd5b48736d4176100365b # tag=v1.5.1 + uses: snok/container-retention-policy@482ce28159f65a8bfad986da1fedcef40169aa75 # v2.0.0 with: image-names: ${{ env.IMAGE_NAMES }} cut-off: 2 days ago UTC From f76b736f1947cf03fc01b9a8049dc15e10a9e4c8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Mar 2023 17:31:50 +0100 Subject: [PATCH 41/46] chore(deps): update all actions (#351) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/continuous-delivery.yml | 14 +++++++------- .github/workflows/continuous-integration.yml | 6 +++--- .github/workflows/publish-image.yml | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index bfbadd25..f4d8e8f2 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -35,7 +35,7 @@ jobs: stage: ${{ steps.get_metadata.outputs.stage }} steps: - name: Checkout code - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - name: Get metadata id: get_metadata @@ -80,7 +80,7 @@ jobs: - name: Checkout code if: fromJSON(needs.metadata.outputs.has_diff) - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - name: Run merge if: fromJSON(needs.metadata.outputs.has_diff) @@ -137,19 +137,19 @@ jobs: fi - name: Checkout code - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 with: ref: ${{ needs.merge.outputs.sha }} - name: Start deployment - uses: bobheadxi/deployments@9d4477fdaa4120020cd10ab7e97f68c801422e73 # tag=v1.3.0 + uses: bobheadxi/deployments@88ce5600046c82542f8246ac287d0a53c461bca3 # v1.4.0 id: start_deployment with: step: start env: ${{ needs.metadata.outputs.stage }} - name: Deploy - uses: appleboy/ssh-action@f9010ff7f1bbd7db1a0b4bab661437550cea20c0 # tag=v0.1.5 + uses: appleboy/ssh-action@b60142998894e495c513803efc6d5d72a72c968a # v0.1.8 env: STAGE: ${{ needs.metadata.outputs.stage }} with: @@ -164,7 +164,7 @@ jobs: docker-compose up -d - name: Finalize Sentry release - uses: getsentry/action-release@bd5f874fcda966ba48139b0140fb3ec0cb3aabdd # v1.2.1 + uses: getsentry/action-release@586b62368d564f25d694ce05fcb9cf53de65ac4f # v1.3.1 env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: ${{ vars.SENTRY_ORG_NAME }} @@ -175,7 +175,7 @@ jobs: set_commits: skip - name: Finish deployment - uses: bobheadxi/deployments@9d4477fdaa4120020cd10ab7e97f68c801422e73 # tag=v1.3.0 + uses: bobheadxi/deployments@88ce5600046c82542f8246ac287d0a53c461bca3 # v1.4.0 if: steps.start_deployment.conclusion == 'success' && always() with: step: finish diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 76bee207..ab9d5059 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 with: ref: ${{ inputs.sha }} @@ -53,7 +53,7 @@ jobs: - 5432:5432 steps: - name: Checkout code - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 with: ref: ${{ inputs.sha }} @@ -101,7 +101,7 @@ jobs: - 5432:5432 steps: - name: Checkout code - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 with: ref: ${{ inputs.sha }} diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index 0a14ec1b..b8c1230d 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -48,13 +48,13 @@ jobs: needs: metadata steps: - name: Checkout code - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 with: ref: ${{ inputs.sha }} fetch-depth: 0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 # tag=v2.2.1 + uses: docker/setup-buildx-action@f03ac48505955848960e80bbb68046aa35c7b9e7 # v2.4.1 - name: Login to GitHub Container Registry uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # tag=v2.1.0 @@ -64,7 +64,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push image - uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 # tag=v3.2.0 + uses: docker/build-push-action@1104d471370f9806843c095c1db02b5a90c5f8b6 # v3.3.1 with: push: true context: . @@ -77,7 +77,7 @@ jobs: - name: Create Sentry release if: ${{ !(github.event_name == 'workflow_dispatch' && github.workflow == 'Publish Image') }} - uses: getsentry/action-release@bd5f874fcda966ba48139b0140fb3ec0cb3aabdd # v1.2.1 + uses: getsentry/action-release@586b62368d564f25d694ce05fcb9cf53de65ac4f # v1.3.1 env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: ${{ vars.SENTRY_ORG_NAME }} From b81e3bbb5d1b8542f5c3c185abe372ac4d177716 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Mar 2023 18:48:15 +0100 Subject: [PATCH 42/46] chore(deps): update docker/build-push-action action to v4 (#357) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/publish-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index b8c1230d..1cb11a44 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -64,7 +64,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push image - uses: docker/build-push-action@1104d471370f9806843c095c1db02b5a90c5f8b6 # v3.3.1 + uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 # v4.0.0 with: push: true context: . From 06357cd33750f5fe805df15f13be9a1574d81434 Mon Sep 17 00:00:00 2001 From: Wilco Date: Sat, 11 Mar 2023 17:59:11 +0100 Subject: [PATCH 43/46] Increase test coverage (#345) Co-authored-by: Hein Huijskes <44364301+HeinHuijskes@users.noreply.github.com> --- app/controllers/v1/groups_controller.rb | 2 ++ app/policies/application_policy.rb | 4 ---- spec/models/mail_alias_spec.rb | 21 ++++++++++++++++++--- spec/models/user_spec.rb | 8 ++++++++ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/controllers/v1/groups_controller.rb b/app/controllers/v1/groups_controller.rb index 411c2bee..9cef1ceb 100644 --- a/app/controllers/v1/groups_controller.rb +++ b/app/controllers/v1/groups_controller.rb @@ -36,9 +36,11 @@ def permitted_serializable_user_attributes end def slack_notification(description) + # :nocov: "User ##{current_user.id} (#{current_user.full_name}) "\ "is exporting users for group #{@model.id} from host #{request.host} with #"\ "#{permitted_serializable_user_attributes.join(',')}. De gebruiker gaf als "\ "reden: #{description}" + # :nocov: end end diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb index b0a52aad..3bcc494d 100644 --- a/app/policies/application_policy.rb +++ b/app/policies/application_policy.rb @@ -31,10 +31,6 @@ def scope Pundit.policy_scope!(@user || @application, record.class) end - def create_or_update? - user.permission?(:create, record) || user.permission?(:update, record) - end - class Scope attr_reader :user, :scope diff --git a/spec/models/mail_alias_spec.rb b/spec/models/mail_alias_spec.rb index 650a60e7..e1be1261 100644 --- a/spec/models/mail_alias_spec.rb +++ b/spec/models/mail_alias_spec.rb @@ -116,11 +116,26 @@ describe '#moderators' do context 'when with open alias' do - subject(:mail_alias) do - build_stubbed(:mail_alias, :with_group, moderation_type: 'open') + context 'when without moderator' do + subject(:mail_alias) do + build_stubbed(:mail_alias, :with_group, moderation_type: 'open') + end + + it { expect(mail_alias.mail_addresses).to be_empty } end - it { expect(mail_alias.mail_addresses).to be_empty } + context 'when with moderator' do + subject(:mail_alias) do + build_stubbed(:mail_alias, :with_user, :with_moderator, + moderation_type: 'open') + end + + before { mail_alias.valid? } + + it { + expect(mail_alias.errors[:base].first).to include 'Must have no moderator' + } + end end context 'when with moderated alias' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 62ebff01..44ab49a5 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -582,6 +582,14 @@ end end + describe '#permissions' do + subject(:user) { create(:user, user_permission_list: ['user.read']) } + + before { create(:group, users: [user], permission_list: ['user.update']) } + + it { expect(user.permissions.map(&:name)).to match_array(['user.read', 'user.update']) } + end + describe '#current_group_member?' do subject(:user) { create(:user) } From 04c6ac9229e33440a68317cba173d82db24faf8b Mon Sep 17 00:00:00 2001 From: Wilco Date: Thu, 23 Mar 2023 23:43:12 +0100 Subject: [PATCH 44/46] Fix spreadsheet currency parsing (#366) * Fix spreadsheet currency parsing * Fix lint * Fix tests * Simplify logic * Improve code --- app/models/import/transaction.rb | 11 +++++++++-- spec/support/files/collection_import.csv | 2 +- .../collection_import_with_incorrect_transactions.csv | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/models/import/transaction.rb b/app/models/import/transaction.rb index 73c5dfd5..7404bc10 100644 --- a/app/models/import/transaction.rb +++ b/app/models/import/transaction.rb @@ -61,11 +61,18 @@ def hash_to_list(hash, user) transactions end - def normalize_amount(amount, description, user) + def normalize_amount(amount, description, user) # rubocop:disable Metrics/MethodLength return amount if amount.nil? begin - amount = amount.tr(',', '.') if amount.instance_of?(String) + if amount.instance_of?(String) + amount = amount.strip + .tr(',', '.') + .tr(' ', '') + amount = '0' if amount == '€-' + amount = amount.tr('€', '') + .delete_suffix('.-') + end raise ArgumentError if Float(amount).nil? # test whether string is numeric amount.to_d diff --git a/spec/support/files/collection_import.csv b/spec/support/files/collection_import.csv index 8316e32f..3bd31076 100644 --- a/spec/support/files/collection_import.csv +++ b/spec/support/files/collection_import.csv @@ -1,2 +1,2 @@ username,Declaraties,Slot BBQ,Slot BBQ Streeplijst,Zeilweek,Voorschot Zeilweek,Gratis Activiteit,Komma,Komma Seperated -bestuurder,-10,5,2.18,212.5,-100,0,,"2,55" +bestuurder,-10,5,2.18,212.5,-100,€ -,,"€2,55" diff --git a/spec/support/files/collection_import_with_incorrect_transactions.csv b/spec/support/files/collection_import_with_incorrect_transactions.csv index 0406a0e5..bbe387d4 100644 --- a/spec/support/files/collection_import_with_incorrect_transactions.csv +++ b/spec/support/files/collection_import_with_incorrect_transactions.csv @@ -1,2 +1,2 @@ username,Declaraties,Slot BBQ,Slot BBQ Streeplijst,Zeilweek,Voorschot Zeilweek, -bestuurder,-10,€5,2.18,212.5,-100,2 +bestuurder,-10,tekst,2.18,212.5,-100,2 From b4c35aad28d1c65d418a0e3f8659541223f7a670 Mon Sep 17 00:00:00 2001 From: Wilco Date: Thu, 20 Apr 2023 10:36:55 +0200 Subject: [PATCH 45/46] Add job for incoming emails (#369) * Add job for incoming emails * Fix file coverage --- .../improvmx/inbound_emails_controller.rb | 30 ++++-------------- app/jobs/mail_moderation_creation_job.rb | 28 +++++++++++++++++ .../jobs/mail_moderation_creation_job_spec.rb | 31 +++++++++++++++++++ 3 files changed, 65 insertions(+), 24 deletions(-) create mode 100644 app/jobs/mail_moderation_creation_job.rb create mode 100644 spec/jobs/mail_moderation_creation_job_spec.rb diff --git a/app/controllers/action_mailbox/ingresses/improvmx/inbound_emails_controller.rb b/app/controllers/action_mailbox/ingresses/improvmx/inbound_emails_controller.rb index 9523c91d..8933e5ef 100644 --- a/app/controllers/action_mailbox/ingresses/improvmx/inbound_emails_controller.rb +++ b/app/controllers/action_mailbox/ingresses/improvmx/inbound_emails_controller.rb @@ -6,33 +6,15 @@ class InboundEmailsController < ActionMailbox::BaseController before_action :authenticate_by_password def create - ActionMailbox::InboundEmail.create_and_extract_message_id! raw_email - end - - private - - def raw_email # Email is in the raw parameter in BASE64, # unless it is too large then it needs to be retrieved - return Base64.decode64(params.require(:raw)) if params.key?(:raw) - - retrieve_email(params.require('raw_url')) - end - - def retrieve_email(url) # rubocop:disable Metrics/AbcSize - uri = URI(url) - - request = Net::HTTP::Get.new uri - request.basic_auth 'api', Rails.application.config.x.improvmx_api_key - - response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http| - http.request(request) + if params.key?(:raw) + raw_email = Base64.decode64(params.require(:raw)) + ActionMailbox::InboundEmail.create_and_extract_message_id! raw_email + else + # Create a job to retrieve the email since it may not be available yet at the given url + MailModerationCreationJob.set(wait: 1.minute).perform_later(params.require('raw_url')) end - - logger.info response.code - logger.info response.body - - response.body end end end diff --git a/app/jobs/mail_moderation_creation_job.rb b/app/jobs/mail_moderation_creation_job.rb new file mode 100644 index 00000000..2815b14d --- /dev/null +++ b/app/jobs/mail_moderation_creation_job.rb @@ -0,0 +1,28 @@ +class MailModerationCreationJob < ApplicationJob + queue_as :mail_handlers + + def perform(raw_url) + ActionMailbox::InboundEmail.create_and_extract_message_id! retrieve_email(raw_url) + end + + private + + def retrieve_email(url) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + uri = URI(url) + + request = Net::HTTP::Get.new uri + request.basic_auth 'api', Rails.application.config.x.improvmx_api_key + + response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http| + # :nocov: + http.request(request) + # :nocov: + end + + if response.is_a? Net::HTTPSuccess + response.body + else + raise "Improvmx API returned #{response.code} #{response.message}" + end + end +end diff --git a/spec/jobs/mail_moderation_creation_job_spec.rb b/spec/jobs/mail_moderation_creation_job_spec.rb new file mode 100644 index 00000000..b4bd577b --- /dev/null +++ b/spec/jobs/mail_moderation_creation_job_spec.rb @@ -0,0 +1,31 @@ +require 'rails_helper' + +RSpec.describe MailModerationCreationJob, type: :job do + let(:raw_url) { 'https://example.com/raw_email' } + let(:response_body) { 'raw_email_data' } + let(:response) do + instance_double(Net::HTTPResponse, code: '200', message: 'OK', body: response_body) + end + + describe '#perform' do + it 'creates and extracts message ID from inbound email' do # rubocop:disable RSpec/ExampleLength + allow(response).to receive(:is_a?).with(Net::HTTPSuccess).and_return(true) + allow(Net::HTTP).to receive(:start).and_return(response) + + allow(ActionMailbox::InboundEmail).to receive(:create_and_extract_message_id!) + + described_class.perform_now(raw_url) + + expect(ActionMailbox::InboundEmail).to have_received(:create_and_extract_message_id!) + .with(response_body) + end + + it 'raises an error when the client does not return a 200 HTTP response' do + response = instance_double(Net::HTTPResponse, code: '404', message: 'Not Found', + body: 'raw_email_not_found') + allow(response).to receive(:is_a?).with(Net::HTTPSuccess).and_return(false) + allow(Net::HTTP).to receive(:start).and_return(response) + expect { described_class.perform_now(raw_url) }.to raise_error(RuntimeError) + end + end +end From d0555ac4c9f44fb86161ed067130c0ee2a20bebc Mon Sep 17 00:00:00 2001 From: Wilco Date: Sat, 22 Apr 2023 14:58:13 +0200 Subject: [PATCH 46/46] Fix name of archived user (#371) --- app/jobs/user_archive_job.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/jobs/user_archive_job.rb b/app/jobs/user_archive_job.rb index 3471a0c5..3a67d642 100644 --- a/app/jobs/user_archive_job.rb +++ b/app/jobs/user_archive_job.rb @@ -21,8 +21,8 @@ def create_global_archive_user User.create!(id: ARCHIVE_USER_ID, username: 'archived.user', email: 'ict@csvalpha.nl', - first_name: 'Gearchiveerde Gebruiker', - last_name: '-', + first_name: 'Gearchiveerde', + last_name: 'Gebruiker', address: 'Onbekend', postcode: 'Onbekend', city: 'Onbekend')