diff --git a/.dockerignore b/.dockerignore index 81a0d0ce..17fd46ae 100644 --- a/.dockerignore +++ b/.dockerignore @@ -23,3 +23,5 @@ docker-compose.prod.yml docker-compose.yml OVERLOADS.md Makefile +public/decidim-packs +public/packs-test diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..603022c2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Stacktrace** +If applicable, add the error stacktrace to help explain your problem. + +**Extra data (please complete the following information):** +- Device: [e.g. iPhone6, Desktop] +- Device OS: [e.g. iOS8.1, Windows 10] +- Browser: [e.g. Chrome, Firefox, Safari] +- Decidim Version: [e.g. 0.10] +- Decidim installation: [e.g. Metadecidim] + +**Additional context** +Add any other context about the problem here. For instance, add Metadecidim link. \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..93d962cb --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,25 @@ +#### :tophat: Description +*Please describe your pull request.* + +#### :pushpin: Related Issues +*Link your PR to an issue* +- Related to #? +- Fixes #? +- [Notion card]() + +#### Testing +*Describe the best way to test or validate your PR.* + +Example: +* Log in as admin +* Access Backoffice +* Go to organization settings +* See ... + +#### Tasks +- [ ] Add specs +- [ ] Add note about overrides in OVERLOADS.md +- [ ] In case of new dependencies or version bump, update related documentation + +### :camera: Screenshots +*Please add screenshots of the changes you're proposing if related to the UI* diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml new file mode 100644 index 00000000..08e95820 --- /dev/null +++ b/.github/workflows/ci_cd.yml @@ -0,0 +1,157 @@ +name: "CI/CD" +on: [push] + +env: + CI: "true" + SIMPLECOV: "true" + RSPEC_FORMAT: "documentation" + RUBY_VERSION: 3.0.2 + RAILS_ENV: test + NODE_VERSION: 16.9.1 + RUBYOPT: '-W:no-deprecated' + +jobs: + lint: + name: Lint code + runs-on: ubuntu-latest + if: "!startsWith(github.head_ref, 'chore/l10n')" + timeout-minutes: 60 + steps: + - uses: rokroskar/workflow-run-cleanup-action@v0.3.0 + if: "github.ref != 'refs/heads/develop'" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + - uses: OpenSourcePolitics/lint-action@master + with: + ruby_version: ${{ env.RUBY_VERSION }} + node_version: ${{ env.NODE_VERSION }} + tests: + name: Tests + runs-on: ubuntu-latest + if: "!startsWith(github.head_ref, 'chore/l10n')" + services: + postgres: + image: postgres:11 + ports: [ "5432:5432" ] + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + POSTGRES_PASSWORD: postgres + env: + DATABASE_USERNAME: postgres + DATABASE_PASSWORD: postgres + DATABASE_HOST: localhost + RAILS_ENV: test + # Set locales available for i18n tasks + ENFORCED_LOCALES: "en,fr" + steps: + - uses: rokroskar/workflow-run-cleanup-action@v0.2.2 + if: "github.ref != 'refs/heads/master' || github.ref != 'refs/heads/develop'" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + - uses: OpenSourcePolitics/rspec-action@master + with: + command: 'bundle exec rspec --exclude-pattern "spec/system/**/*_spec.rb"' + prepare_command: "bundle exec rails db:create db:migrate" + system_tests: + name: System tests + runs-on: ubuntu-latest + services: + postgres: + image: postgres:11 + ports: [ "5432:5432" ] + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + POSTGRES_PASSWORD: postgres + env: + DATABASE_USERNAME: postgres + DATABASE_PASSWORD: postgres + DATABASE_HOST: localhost + RAILS_ENV: test + # Set locales available for i18n tasks + ENFORCED_LOCALES: "en" + steps: + - uses: rokroskar/workflow-run-cleanup-action@v0.2.2 + if: "github.ref != 'refs/heads/master' || github.ref != 'refs/heads/develop'" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + - uses: OpenSourcePolitics/rspec-action@master + with: + command: 'bundle exec rspec "spec/system"' + prepare_command: "bundle exec rails db:create db:migrate" + test_build: + name: Test build docker image + runs-on: ubuntu-latest + services: + postgres: + image: postgres:11 + ports: [ "5432:5432" ] + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + POSTGRES_PASSWORD: postgres + env: + DATABASE_USERNAME: postgres + DATABASE_PASSWORD: postgres + DATABASE_HOST: host.docker.internal + steps: + - uses: OpenSourcePolitics/build-and-test-images-action@master + with: + registry: ${{ vars.REGISTRY_ENDPOINT }} + namespace: ${{ vars.REGISTRY_NAMESPACE }} + image_name: ${{ vars.IMAGE_NAME }} + tag: ${{ github.ref }} + password: ${{ secrets.TOKEN }} + database_username: ${{ env.DATABASE_USERNAME }} + database_password: ${{ env.DATABASE_PASSWORD }} + database_host: ${{ env.DATABASE_HOST }} + build_and_push_image_dev: + name: Build and push image to Registry + if: "github.ref == 'refs/heads/develop'" + needs: [ lint, tests, system_tests, test_build ] + runs-on: ubuntu-latest + steps: + - uses: OpenSourcePolitics/build-and-push-images-action@master + with: + registry: ${{ vars.REGISTRY_ENDPOINT }} + namespace: ${{ vars.REGISTRY_NAMESPACE }} + password: ${{ secrets.TOKEN }} + image_name: ${{ vars.IMAGE_NAME }} + tag: "develop-${{ github.sha }}" + generate_release: + name: Generate release + needs: [ lint, tests, system_tests, test_build ] + if: "github.ref == 'refs/heads/master'" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: mathieudutour/github-tag-action@v6.1 + name: Bump vaersion and push tag + id: tag_version + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + - uses: ncipollo/release-action@v1 + name: Create a GitHub release + with: + generateReleaseNotes: true + tag: ${{ steps.tag_version.outputs.new_tag }} + name: Release ${{ steps.tag_version.outputs.new_tag }} + body: ${{ steps.tag_version.outputs.changelog }} + - uses: OpenSourcePolitics/build-and-push-images-action@master + with: + registry: ${{ vars.REGISTRY_ENDPOINT }} + namespace: ${{ vars.REGISTRY_NAMESPACE }} + password: ${{ secrets.TOKEN }} + image_name: ${{ vars.IMAGE_NAME }} + tag: ${{ steps.tag_version.outputs.new_tag }} + diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 911e1656..00000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: "CI/CD" -on: [push] - -env: - CI: "true" - SIMPLECOV: "true" - RUBY_VERSION: 3.0.2 - RAILS_ENV: test - NODE_VERSION: 16.9.1 - RUBYOPT: '-W:no-deprecated' - RSPEC_FORMATTER: 'documentation' - -jobs: - lint: - name: Lint code - runs-on: ubuntu-latest - if: "!startsWith(github.head_ref, 'chore/l10n')" - timeout-minutes: 60 - steps: - - uses: rokroskar/workflow-run-cleanup-action@v0.3.0 - if: "github.ref != 'refs/heads/develop'" - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - - uses: OpenSourcePolitics/lint-action@master - tests: - name: Tests - runs-on: ubuntu-latest - if: "!startsWith(github.head_ref, 'chore/l10n')" - services: - postgres: - image: postgres:11 - ports: ["5432:5432"] - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - env: - POSTGRES_PASSWORD: postgres - env: - DATABASE_USERNAME: postgres - DATABASE_PASSWORD: postgres - DATABASE_HOST: localhost - RAILS_ENV: test - # Set locales available for i18n tasks - ENFORCED_LOCALES: "en,fr" - steps: - - uses: rokroskar/workflow-run-cleanup-action@v0.2.2 - if: "github.ref != 'refs/heads/master' || github.ref != 'refs/heads/develop'" - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - - uses: OpenSourcePolitics/rspec-action@master - with: - command: 'bundle exec rspec --exclude-pattern "spec/system/**/*_spec.rb"' - prepare_command: "bundle exec rails db:create db:migrate" - system_tests: - name: System tests - runs-on: ubuntu-latest - services: - postgres: - image: postgres:11 - ports: ["5432:5432"] - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - env: - POSTGRES_PASSWORD: postgres - env: - DATABASE_USERNAME: postgres - DATABASE_PASSWORD: postgres - DATABASE_HOST: localhost - RAILS_ENV: test - # Set locales available for i18n tasks - ENFORCED_LOCALES: "en" - steps: - - uses: rokroskar/workflow-run-cleanup-action@v0.2.2 - if: "github.ref != 'refs/heads/master' || github.ref != 'refs/heads/develop'" - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - - uses: OpenSourcePolitics/rspec-action@master - with: - command: 'bundle exec rspec "spec/system"' - prepare_command: "bundle exec rails db:create db:migrate" diff --git a/Dockerfile b/Dockerfile index c85c00bd..9f83d411 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,31 +1,34 @@ -FROM ruby:2.7.1 +FROM ruby:3.0.2 -# Install NodeJS -RUN curl https://deb.nodesource.com/setup_16.x | bash -RUN apt install -y nodejs - -# Install Yarn -RUN curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /usr/share/keyrings/yarnkey.gpg >/dev/null -RUN echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" | tee /etc/apt/sources.list.d/yarn.list -RUN apt update && apt install -y yarn - -# Decidim dependencies -RUN apt install -y libicu-dev postgresql-client - -# Install npm -RUN npm install -g npm@7.21.0 -# Install bundler -RUN gem install bundler:2.2.29 +ENV RAILS_ENV=production \ + SECRET_KEY_BASE=dummy -# Copy all decidim-app content to /app -ADD . /app WORKDIR /app -RUN bundle install && yarn install && bundle exec rails assets:precompile - -# Perform assets compilation +# Install NodeJS +RUN --mount=type=cache,target=/var/cache/apt \ + curl https://deb.nodesource.com/setup_16.x | bash && \ + apt install -y nodejs && \ + apt update && \ + npm install -g npm@8.19.2 && \ + npm install --global yarn && \ + apt install -y libicu-dev postgresql-client && \ + gem install bundler:2.2.17 && \ + rm -rf /var/lib/apt/lists/* + +COPY Gemfile* ./ +RUN bundle config set --local without 'development test' && bundle install + +COPY package* ./ +COPY yarn.lock . +COPY packages packages RUN yarn install -RUN SECRET_KEY_BASE=dummy bundle exec rails assets:precompile + +COPY . . + +RUN bundle exec bootsnap precompile --gemfile app/ lib/ config/ bin/ db/ && \ + bundle exec rails assets:precompile && \ + bundle exec rails deface:precompile # Configure endpoint. COPY ./entrypoint.sh /usr/bin/ @@ -33,4 +36,4 @@ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 -CMD ["rails", "server", "-b", "0.0.0.0"] +CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"] diff --git a/Gemfile b/Gemfile index 30fd2829..f0a4cae9 100644 --- a/Gemfile +++ b/Gemfile @@ -41,13 +41,14 @@ gem "omniauth-rails_csrf_protection", "~> 1.0" gem "activerecord-session_store" gem "bootsnap", "~> 1.4" +gem "deface" gem "puma", ">= 5.6.2" -gem "deface" gem "faker", "~> 2.14" group :development, :test do gem "byebug", "~> 11.0", platform: :mri + gem "climate_control", "~> 1.2" gem "brakeman", "~> 5.2" gem "decidim-dev", DECIDIM_VERSION @@ -65,6 +66,7 @@ end group :production do gem "dalli" + gem "health_check", "~> 3.1" gem "lograge" gem "newrelic_rpm" gem "passenger" @@ -73,5 +75,6 @@ group :production do gem "sentry-ruby" gem "sentry-sidekiq" gem "sidekiq", "~> 6.0" + gem "sidekiq_alive", "~> 2.2" gem "sidekiq-scheduler", "~> 5.0" end diff --git a/Gemfile.lock b/Gemfile.lock index 8cdd094e..d64d87d7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -223,6 +223,7 @@ GEM chef-utils (18.1.29) concurrent-ruby childprocess (4.1.0) + climate_control (1.2.0) coercible (1.0.0) descendants_tracker (~> 0.0.1) coffee-rails (5.0.0) @@ -535,6 +536,8 @@ GEM sass (~> 3.4) hashdiff (1.0.1) hashie (5.0.0) + health_check (3.1.0) + railties (>= 5.0) highline (2.1.0) hkdf (0.3.0) html-pipeline (2.14.3) @@ -628,6 +631,7 @@ GEM mime-types-data (3.2023.0218.1) mini_magick (4.12.0) mini_mime (1.1.2) + mini_portile2 (2.8.2) minitest (5.18.0) mixlib-cli (2.1.8) mixlib-config (3.0.27) @@ -649,6 +653,9 @@ GEM net-protocol newrelic_rpm (9.0.0) nio4r (2.5.8) + nokogiri (1.13.10) + mini_portile2 (~> 2.8.0) + racc (~> 1.4) nokogiri (1.13.10-arm64-darwin) racc (~> 1.4) nokogiri (1.13.10-x86_64-darwin) @@ -901,6 +908,10 @@ GEM rufus-scheduler (~> 3.2) sidekiq (>= 6, < 8) tilt (>= 1.4.0) + sidekiq_alive (2.2.0) + rack (< 3) + sidekiq (>= 5, < 8) + webrick (>= 1, < 2) simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) @@ -990,6 +1001,7 @@ GEM webpush (1.1.0) hkdf (~> 0.2) jwt (~> 2.0) + webrick (1.8.1) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -1007,6 +1019,7 @@ GEM PLATFORMS arm64-darwin-21 arm64-darwin-22 + ruby x86_64-darwin-19 x86_64-darwin-20 x86_64-darwin-21 @@ -1019,6 +1032,7 @@ DEPENDENCIES bootsnap (~> 1.4) brakeman (~> 5.2) byebug (~> 11.0) + climate_control (~> 1.2) dalli decidim (= 0.27.1) decidim-blog_author_petition! @@ -1035,6 +1049,7 @@ DEPENDENCIES faker (~> 2.14) fog-aws foundation_rails_helper! + health_check (~> 3.1) letter_opener_web (~> 2.0) listen (~> 3.1) lograge @@ -1052,6 +1067,7 @@ DEPENDENCIES sentry-sidekiq sidekiq (~> 6.0) sidekiq-scheduler (~> 5.0) + sidekiq_alive (~> 2.2) spring (~> 2.0) spring-watcher-listen (~> 2.0) sys-filesystem diff --git a/config/environments/production.rb b/config/environments/production.rb index ee74acea..06a1723f 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -140,4 +140,6 @@ # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false + + config.deface.enabled = ENV.fetch("DEFACE_ENABLED", "false") == "true" end diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 5157d0d4..83c0c7b5 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -99,6 +99,8 @@ ignore_missing: - decidim.proposals.omniauth.france_connect.external.{link,text} - decidim.proposals.omniauth.france_connect.forgot_password.ok_text - decidim.initiatives.initiatives.show.illegal.{title,description} + - decidim.initiatives.create_initiative.select_initiative_type.* + - decidim.admin.organization_appearance.form.images.* # Consider these keys used: ignore_unused: @@ -130,3 +132,4 @@ ignore_unused: - rack_attack.too_many_requests.* - activemodel.attributes.confirmation.* - activemodel.attributes.mobile_phone.* + - decidim.admin.participatory_space_private_users.create.* diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb new file mode 100644 index 00000000..7bd0f9ce --- /dev/null +++ b/config/initializers/health_check.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +return unless Rails.env.production? + +HealthCheck.setup do |config| + # uri prefix (no leading slash) + config.uri = "health_check" + + # Text output upon success + config.success = "success" + + # Text output upon failure + config.failure = "health_check failed" + + # Disable the error message to prevent /health_check from leaking + # sensitive information + config.include_error_in_response_body = false + + # Log level (success or failure message with error details is sent to rails log unless this is set to nil) + config.log_level = "info" + + # Timeout in seconds used when checking smtp server + config.smtp_timeout = 30.0 + + config.http_status_for_error_object = 500 + + # You can customize which checks happen on a standard health check, eg to set an explicit list use: + config.standard_checks = %w(database migrations) +end diff --git a/config/initializers/sidekiq_alive.rb b/config/initializers/sidekiq_alive.rb new file mode 100644 index 00000000..944ae08e --- /dev/null +++ b/config/initializers/sidekiq_alive.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +return unless Rails.env.production? + +SidekiqAlive.setup do |config| + config.path = "/sidekiq_alive" +end diff --git a/lib/decidim/admin_creator.rb b/lib/decidim/admin_creator.rb new file mode 100644 index 00000000..81947bfb --- /dev/null +++ b/lib/decidim/admin_creator.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "decidim/user_creator" + +module Decidim + class AdminCreator < Decidim::UserCreator + def self.create!(env) + new({ organization: env_organization_or_first(env["organization_id"]), + name: env["name"], + nickname: env["nickname"], + email: env["email"], + password: env["password"] }).create! + end + + def create! + super + + Decidim::User.create!(@attributes.merge({ tos_agreement: "1", admin: true })) + end + + def self.env_organization_or_first(organization_id) + Decidim::Organization.find(organization_id) + rescue ActiveRecord::RecordNotFound + Decidim::Organization.first + end + end +end diff --git a/lib/decidim/system_admin_creator.rb b/lib/decidim/system_admin_creator.rb new file mode 100644 index 00000000..3e25c9a2 --- /dev/null +++ b/lib/decidim/system_admin_creator.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "decidim/user_creator" + +module Decidim + class SystemAdminCreator < Decidim::UserCreator + def self.create!(env) + new({ email: env["email"], password: env["password"] }).create! + end + + def create! + super + + Decidim::System::Admin.create!(@attributes) + end + end +end diff --git a/lib/decidim/user_creator.rb b/lib/decidim/user_creator.rb new file mode 100644 index 00000000..a5a7c587 --- /dev/null +++ b/lib/decidim/user_creator.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Decidim + class UserCreator + def initialize(attributes) + @attributes = attributes + end + + def create! + missing = @attributes.select { |_k, v| v.nil? }.keys + + raise "Missing parameters: #{missing.join(", ")}" unless missing.empty? + end + end +end diff --git a/lib/tasks/decidim_app.rake b/lib/tasks/decidim_app.rake new file mode 100644 index 00000000..1920c71a --- /dev/null +++ b/lib/tasks/decidim_app.rake @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "decidim/admin_creator" +require "decidim/system_admin_creator" + +namespace :decidim_app do + desc "Create admin user with decidim_app:create_admin name='John Doe' nickname='johndoe' email='john@example.org', password='decidim123456' organization_id='1'" + task create_admin: :environment do + Decidim::AdminCreator.create!(ENV) ? puts("Admin created successfully") : puts("Admin creation failed") + end + + desc "Create system user with decidim_app:create_system_admin email='john@example.org', password='decidim123456'" + task create_system_admin: :environment do + Decidim::SystemAdminCreator.create!(ENV) ? puts("System admin created successfully") : puts("System admin creation failed") + end +end diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake deleted file mode 100644 index e2e5d793..00000000 --- a/lib/tasks/import.rake +++ /dev/null @@ -1,225 +0,0 @@ -# frozen_string_literal: true - -require "ruby-progressbar" - -namespace :import do - desc "Usage: rake import:user FILE='' ORG= ADMIN= PROCESS= [VERBOSE=true]'" - task user: :environment do - def validate_input - validate_file - validate_process - validate_admin - validate_org - end - - def validate_org - if @org.class != Integer - puts "You must pass an organization id as an integer" - return - end - - unless current_organization - puts "Organization does not exist" - nil - end - end - - def validate_admin - if @admin.class != Integer - puts "You must pass an admin id as an integer" - return - end - - unless current_user - puts "Admin does not exist" - nil - end - end - - def validate_process - if @process.class != Integer - puts "You must pass a process id as an integer" - return - end - - unless current_process - puts "Process does not exist" - nil - end - end - - def validate_file - unless File.exist?(@file) - puts "File does not exist, be sure to pass a full path." - return - end - - if File.extname(@file) != ".csv" - puts "You must pass a CSV file" - nil - end - end - - def display_help - puts <<~HEREDOC - Help: - Usage: rake import:user FILE='' ORG= ADMIN= PROCESS= - HEREDOC - nil - end - - def check_csv(file) - file.each do |row| - # Check if id, first_name, last_name are nil - next unless row[0].nil? || row[1].nil? || row[2].nil? - - puts "Something went wrong, empty field(s) on line #{$INPUT_LINE_NUMBER}" - puts row.inspect - exit 1 - end - end - - def import_data(id, first_name, last_name, email) - # Extends are only loaded at the last time - require "extends/commands/decidim/admin/create_participatory_space_private_user_extends" - require "extends/commands/decidim/admin/impersonate_user_extends" - - if email.nil? - import_without_email(id, first_name, last_name) - else - import_with_email(id, first_name, last_name, email) - end - end - - def import_without_email(id, first_name, last_name) - new_user = Decidim::User.new( - managed: true, - name: set_name(first_name, last_name), - organization: current_organization, - admin: false, - roles: [], - tos_agreement: true - ) - form = Decidim::Admin::ImpersonateUserForm.from_params( - user: new_user, - name: new_user.name, - reason: "import", - handler_name: "osp_authorization_handler", - authorization: Decidim::AuthorizationHandler.handler_for( - "osp_authorization_handler", - { - user: new_user, - document_number: id - } - ) - ).with_context( - current_organization: current_organization, - current_user: current_user - ) - - privatable_to = current_process - - Decidim::Admin::ImpersonateUser.call(form) do - on(:ok) do |user| - Decidim::ParticipatorySpacePrivateUser.find_or_create_by!( - user: user, - privatable_to: privatable_to - ) - Rails.logger.debug I18n.t("participatory_space_private_users.create.success", scope: "decidim.admin") - Rails.logger.debug { "Registered user with id: #{id}, first_name: #{first_name}, last_name: #{last_name} --> #{user.id}" } - end - - on(:invalid) do - Rails.logger.debug I18n.t("participatory_space_private_users.create.error", scope: "decidim.admin") - Rails.logger.debug user.errors.full_messages if user.invalid? - Rails.logger.debug form.errors.full_messages if form.invalid? - Rails.logger.debug { "Failed to register user with id: #{id}, first_name: #{first_name}, last_name: #{last_name} !!" } - # return - end - end - end - - def import_with_email(id, first_name, last_name, email) - form = Decidim::Admin::ParticipatorySpacePrivateUserForm.from_params( - { - name: set_name(first_name, last_name), - email: email - }, - privatable_to: current_process - ) - Decidim::Admin::CreateParticipatorySpacePrivateUser.call(form, current_user, current_process) do - on(:ok) do |user| - Decidim::Authorization.create_or_update_from( - Decidim::AuthorizationHandler.handler_for( - "osp_authorization_handler", - { - user: user, - document_number: id - } - ) - ) - Rails.logger.debug I18n.t("participatory_space_private_users.create.success", scope: "decidim.admin") - Rails.logger.debug { "Registered user with id: #{id}, first_name: #{first_name}, last_name: #{last_name}, email: #{email} --> #{user.id}" } - end - - on(:invalid) do - Rails.logger.debug I18n.t("participatory_space_private_users.create.error", scope: "decidim.admin") - Rails.logger.debug form.errors.full_messages if form.invalid? - Rails.logger.debug { "Failed to register user with id: #{id}, first_name: #{first_name}, last_name: #{last_name}, email: #{email} !!" } - # return - end - end - end - - def set_name(first_name, last_name) - "#{first_name} #{last_name}" - end - - def current_user - @current_user ||= Decidim::User.find(@admin) - end - - def current_organization - @current_organization ||= Decidim::Organization.find(@org) - end - - def current_process - @current_process ||= Decidim::ParticipatoryProcess.find(@process) - end - - Rails.application.config.active_job.queue_adapter = :inline - - @verbose = ENV["VERBOSE"].to_s == "true" - Rails.logger = if @verbose - Logger.new($stdout) - else - Logger.new("log/import-user-#{Time.zone.now.strftime "%Y-%m-%d-%H:%M:%S"}.log") - end - - display_help unless ENV.fetch("FILE", nil) && ENV.fetch("ORG", nil) && ENV.fetch("ADMIN", nil) && ENV.fetch("PROCESS", nil) - @file = ENV.fetch("FILE", nil) - @org = ENV["ORG"].to_i - @admin = ENV["ADMIN"].to_i - @process = ENV["PROCESS"].to_i - @auth_handler = ENV.fetch("AUTH_HANDLER", nil) - - validate_input - - csv = CSV.read(@file, col_sep: ",", headers: true, skip_blanks: true) - check_csv(csv) - - count = CSV.read(@file).count - - puts "CSV file is #{count} lines long" - - progressbar = ProgressBar.create(title: "Importing User", total: count, format: "%t%e%B%p%%") unless @verbose - - csv.each do |row| - progressbar.increment unless @verbose - # Import user with parsed informations id, first_name, last_name, email - import_data(row[0], row[1], row[2], row[3]) - end - - Rails.logger.close - end -end diff --git a/spec/lib/decidim/admin_creator_spec.rb b/spec/lib/decidim/admin_creator_spec.rb new file mode 100644 index 00000000..9cc0ee93 --- /dev/null +++ b/spec/lib/decidim/admin_creator_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "spec_helper" +require "decidim/admin_creator" + +module Decidim + describe AdminCreator do + let!(:organization) { create(:organization) } + let(:name) { "John Doe" } + let(:nickname) { "JD" } + let(:email) { "john@example.org" } + let(:password) { "decidim123456789" } + let(:organization_id) { organization.id.to_s } + + let(:environment) do + { + "organization_id" => organization_id, + "name" => name, + "nickname" => nickname, + "email" => email, + "password" => password + } + end + + it "creates admin" do + expect { described_class.create!(environment) }.to change(Decidim::User, :count).by(1) + expect(Decidim::User.last.admin).to be(true) + expect(Decidim::User.last.nickname).to eq(nickname) + expect(Decidim::User.last.organization).to eq(organization) + expect(Decidim::User.last.email).to eq(email) + end + + context "when organization is missing" do + let(:organization_id) { nil } + + it "creates admins with first organization" do + expect { described_class.create!(environment) }.to change(Decidim::User, :count).by(1) + expect(Decidim::User.last.admin).to be(true) + expect(Decidim::User.last.nickname).to eq(nickname) + expect(Decidim::User.last.organization).to eq(organization) + expect(Decidim::User.last.email).to eq(email) + end + end + end +end diff --git a/spec/lib/decidim/system_admin_creator_spec.rb b/spec/lib/decidim/system_admin_creator_spec.rb new file mode 100644 index 00000000..9a90aa9d --- /dev/null +++ b/spec/lib/decidim/system_admin_creator_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "spec_helper" +require "decidim/system_admin_creator" + +module Decidim + describe SystemAdminCreator do + let(:email) { "john@example.org" } + let(:password) { "decidim123456789" } + + let(:environment) do + { + "email" => email, + "password" => password + } + end + + it "creates admin" do + expect { described_class.create!(environment) }.to change(Decidim::System::Admin, :count).by(1) + expect(Decidim::System::Admin.last.email).to eq(email) + end + end +end diff --git a/spec/lib/tasks/create_admin_task_spec.rb b/spec/lib/tasks/create_admin_task_spec.rb new file mode 100644 index 00000000..69eca204 --- /dev/null +++ b/spec/lib/tasks/create_admin_task_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "rake decidim_app:create_admin", type: :task do + before do + allow(Decidim::AdminCreator).to receive(:create!).with(ENV).and_return(true) + end + + it "preloads the Rails environment" do + expect(task.prerequisites).to include "environment" + end + + it "invokes the admin creator" do + task.execute + expect($stdout.string).to include("Admin created successfully\n") + end +end diff --git a/spec/lib/tasks/create_system_admin_task_spec.rb b/spec/lib/tasks/create_system_admin_task_spec.rb new file mode 100644 index 00000000..24928eca --- /dev/null +++ b/spec/lib/tasks/create_system_admin_task_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "rake decidim_app:create_system_admin", type: :task do + before do + allow(Decidim::SystemAdminCreator).to receive(:create!).with(ENV).and_return(true) + end + + it "preloads the Rails environment" do + expect(task.prerequisites).to include "environment" + end + + it "invokes the admin creator" do + task.execute + expect($stdout.string).to include("System admin created successfully\n") + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0a0a7804..96fb4ced 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,7 +4,12 @@ Decidim::Dev.dummy_app_path = File.expand_path(Rails.root.to_s) require "decidim/dev/test/base_spec_helper" +Dir.glob("./spec/support/**/*.rb").each { |f| require f } + RSpec.configure do |config| + config.formatter = ENV.fetch("RSPEC_FORMAT", "progress").to_sym + config.include EnvironmentVariablesHelper + config.before do # Initializers configs SocialShareButton.configure do |social_share_button| diff --git a/spec/support/environment_variables_helper.rb b/spec/support/environment_variables_helper.rb new file mode 100644 index 00000000..3d833d05 --- /dev/null +++ b/spec/support/environment_variables_helper.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module EnvironmentVariablesHelper + def with_modified_env(options = {}, &block) + ClimateControl.modify(options, &block) + end +end