diff --git a/.env-example b/.env-example
index f3e2b77287..1046e2eaaa 100644
--- a/.env-example
+++ b/.env-example
@@ -13,6 +13,18 @@ BACKUP_S3SYNC_ACCESS_KEY=
BACKUP_S3SYNC_SECRET_KEY=
BACKUP_S3SYNC_BUCKET=
BACKUP_S3RETENTION_ENABLED=
+INITIATIVES_CREATION_ENABLED=
+INITIATIVES_SIMILARITY_THRESHOLD=
+INITIATIVES_SIMILARITY_LIMIT=
+INITIATIVES_MINIMUM_COMMITTEE_MEMBERS=
+INITIATIVES_DEFAULT_SIGNATURE_TIME_PERIOD_LENGTH=
+INITIATIVES_DEFAULT_COMPONENTS=
+INITIATIVES_FIRST_NOTIFICATION_PERCENTAGE=
+INITIATIVES_SECOND_NOTIFICATION_PERCENTAGE=
+INITIATIVES_STATS_CACHE_EXPIRATION_TIME=
+INITIATIVES_MAX_TIME_IN_VALIDATING_STATE=
+INITIATIVES_PRINT_ENABLED=
+INITIATIVES_DO_NOT_REQUIRE_AUTHORIZATION=
SPAM_DETECTION_API_AUTH_TOKEN=
SPAM_DETECTION_API_URL=
SPAM_DETECTION_NAME=
@@ -47,4 +59,4 @@ TRANSLATOR_ENABLED=0
TRANSLATOR_API_KEY=
TRANSLATOR_HOST=
TRANSLATOR_DELAY=0
-GALLERY_ANIMATION_ENABLE=0
\ No newline at end of file
+GALLERY_ANIMATION_ENABLE=0
diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml
index f9c18054fd..42ce0dfe73 100644
--- a/.github/workflows/ci_cd.yml
+++ b/.github/workflows/ci_cd.yml
@@ -5,7 +5,7 @@ env:
CI: "true"
SIMPLECOV: "true"
RSPEC_FORMAT: "documentation"
- RUBY_VERSION: 2.7.5
+ RUBY_VERSION: 3.0.6
RAILS_ENV: test
NODE_VERSION: 16.9.1
RUBYOPT: '-W:no-deprecated'
@@ -50,7 +50,7 @@ jobs:
with:
ruby-version: ${{ env.RUBY_VERSION }}
bundler-cache: true
- - name: Create db
+ - name: Check for Zeitwerk errors
run: |
bundle exec rails zeitwerk:check
tests:
@@ -216,50 +216,10 @@ jobs:
database_host: ${{ env.DATABASE_HOST }}
# We don't want to upload the image to the registry if the build fails, but we don't care when on a PR for speed reasons
push: ${{ github.ref != 'refs/heads/develop' || github.ref != 'refs/heads/master' }}
- deploy_develop:
- if: "github.ref == 'refs/heads/develop'"
- needs: [lint, tests, system_tests, test_build]
- name: Deploy develop branch on develop instance
- runs-on: ubuntu-latest
- steps:
- - name: Run Ansible playbook
- uses: appleboy/ssh-action@v0.1.4
- with:
- host: ${{ secrets.ANSIBLE_HOST }}
- username: ${{ secrets.ANSIBLE_USERNAME }}
- key: ${{ secrets.ANSIBLE_KEY }}
- port: ${{ secrets.SSH_PORT }}
- script: ansible-playbook -u ${{ secrets.ANSIBLE_USERNAME }} --private-key="~/.ssh/ansible-deploy/ansible-deploy" -i /home/${{ secrets.ANSIBLE_USERNAME }}/ansible/decidim/inventories/develop.yml /home/${{ secrets.ANSIBLE_USERNAME }}/ansible/decidim/playbooks/update_decidim_app.yml
- deploy_rc:
- if: "github.ref == 'refs/heads/rc'"
- needs: [lint, tests, system_tests, test_build]
- name: Deploy rc branch on RC instance
- runs-on: ubuntu-latest
- steps:
- - name: Run Ansible playbook
- uses: appleboy/ssh-action@v0.1.4
- with:
- host: ${{ secrets.ANSIBLE_HOST }}
- username: ${{ secrets.ANSIBLE_USERNAME }}
- key: ${{ secrets.ANSIBLE_KEY }}
- port: ${{ secrets.SSH_PORT }}
- script: ansible-playbook -u ${{ secrets.ANSIBLE_USERNAME }} --private-key="~/.ssh/ansible-deploy/ansible-deploy" -i /home/${{ secrets.ANSIBLE_USERNAME }}/ansible/decidim/inventories/rc.yml /home/${{ secrets.ANSIBLE_USERNAME }}/ansible/decidim/playbooks/update_decidim_app.yml
- deploy_staging:
- if: "github.ref == 'refs/heads/master'"
- needs: [lint, tests, system_tests, test_build]
- name: Deploy staging branch on staging instance
- runs-on: ubuntu-latest
- steps:
- - name: Run Ansible playbook
- uses: appleboy/ssh-action@v0.1.4
- with:
- host: ${{ secrets.ANSIBLE_HOST }}
- username: ${{ secrets.ANSIBLE_USERNAME }}
- key: ${{ secrets.ANSIBLE_KEY }}
- port: ${{ secrets.SSH_PORT }}
- script: ansible-playbook -u ${{ secrets.ANSIBLE_USERNAME }} --private-key="~/.ssh/ansible-deploy/ansible-deploy" -i /home/${{ secrets.ANSIBLE_USERNAME }}/ansible/decidim/inventories/staging.yml /home/${{ secrets.ANSIBLE_USERNAME }}/ansible/decidim/playbooks/update_decidim_app.yml
build_and_push_image_dev:
- if: "github.ref == 'refs/heads/develop'"
+ if: "contains('
+ refs/heads/develop
+ refs/heads/rc/', github.ref)"
name: Build and push image to Registry
needs: [ lint, tests, system_tests, test_build ]
runs-on: ubuntu-latest
diff --git a/.github/workflows/deploy_production.yml b/.github/workflows/deploy_production.yml
index 1437709b12..8a11132d5c 100644
--- a/.github/workflows/deploy_production.yml
+++ b/.github/workflows/deploy_production.yml
@@ -4,7 +4,7 @@ on: [ workflow_dispatch ]
env:
CI: "true"
SIMPLECOV: "true"
- RUBY_VERSION: 2.7.5
+ RUBY_VERSION: 3.0.6
RAILS_ENV: test
NODE_VERSION: 16.9.1
RUBYOPT: '-W:no-deprecated'
diff --git a/.gitignore b/.gitignore
index 4190cd3fe8..a1d968549e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -95,3 +95,4 @@ yarn-debug.log*
.yarn-integrity
.idea
coverage/
+public/sw.js*
diff --git a/.rubocop_rails.yml b/.rubocop_rails.yml
index 0ec984f454..ed08098e93 100644
--- a/.rubocop_rails.yml
+++ b/.rubocop_rails.yml
@@ -94,6 +94,9 @@ Rails/Validation:
Include:
- app/models/**/*.rb
+Rails/CompactBlank:
+ Enabled: false
+
Rails/BulkChangeTable:
Exclude:
- db/**/*
diff --git a/.rubocop_ruby.yml b/.rubocop_ruby.yml
index e00f4fb87c..21dd42d78f 100644
--- a/.rubocop_ruby.yml
+++ b/.rubocop_ruby.yml
@@ -16,6 +16,7 @@ AllCops:
- "vendor/**/*"
- "node_modules/**/*"
- "db/schema.rb"
+ - "db/migrate/*.rb"
- "bin/*"
# Default formatter will be used if no -f/--format option is given.
DefaultFormatter: progress
@@ -67,12 +68,7 @@ AllCops:
# If a value is specified for TargetRubyVersion then it is used.
# Else if .ruby-version exists and it contains an MRI version it is used.
# Otherwise we fallback to the oldest officially supported Ruby version (2.0).
- TargetRubyVersion: 2.7
-
- RSpec:
- Patterns:
- - "(?:^|/)spec/"
- - "(?:^|/)test/"
+ TargetRubyVersion: 3.0
Lint/SafeNavigationChain:
Exclude:
@@ -287,6 +283,9 @@ Style/HashEachMethods:
Style/HashLikeCase:
MinBranchesCount: 5
+Style/OpenStructUse:
+ Enabled: false
+
# Indentation of `when`.
Layout/CaseIndentation:
EnforcedStyle: case
@@ -1272,10 +1271,11 @@ RSpec/NamedSubject:
Enabled: false
RSpec/RepeatedExampleGroupDescription:
- Enabled: false
+ Enabled: true
RSpec/RepeatedExampleGroupBody:
- Enabled: false
+ Enabled: true
+
RSpec/VerifiedDoubles:
Enabled: false
diff --git a/.ruby-version b/.ruby-version
index a603bb50a2..818bd47abf 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.7.5
+3.0.6
diff --git a/Dockerfile b/Dockerfile
index 9f6576cb75..f62346cd5b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,16 +1,20 @@
-FROM rg.fr-par.scw.cloud/decidim-app-base/decidim-app-base:2.7.5-alpine-jemalloc as builder
+FROM ruby:3.0.6-slim as builder
ENV RAILS_ENV=production \
SECRET_KEY_BASE=dummy
WORKDIR /app
-# TODO: Use repository version of jemalloc when available
-RUN apk add --no-cache --update nodejs yarn tzdata git icu-dev libpq-dev build-base proj proj-dev postgresql-client imagemagick && \
+RUN apt-get update && \
+ apt-get -y install libpq-dev curl git libicu-dev build-essential && \
+ curl https://deb.nodesource.com/setup_16.x | bash && \
+ apt-get install -y nodejs && \
+ npm install --global yarn && \
gem install bundler:2.4.9
COPY Gemfile* ./
-RUN bundle config set --local without 'development test' && bundle install
+RUN bundle config set --local without 'development test' && \
+ bundle install -j"$(nproc)"
COPY package* ./
COPY yarn.lock .
@@ -30,22 +34,22 @@ RUN rm -rf node_modules tmp/cache vendor/bundle spec \
&& find /usr/local/bundle/gems/ -type d -name "spec" -prune -exec rm -rf {} \; \
&& rm -rf log/*.log
-FROM ruby:2.7.5-alpine as runner
+FROM ruby:3.0.6-slim as runner
ENV RAILS_ENV=production \
SECRET_KEY_BASE=dummy \
RAILS_LOG_TO_STDOUT=true
-RUN apk add --no-cache --update icu-dev tzdata postgresql-client proj proj-dev imagemagick && \
+RUN apt update && \
+ apt install -y postgresql-client imagemagick libproj-dev proj-bin libjemalloc2 && \
gem install bundler:2.4.9
WORKDIR /app
-COPY --from=builder /usr/local/lib/libjemalloc.so.2 /usr/local/lib/
COPY --from=builder /usr/local/bundle /usr/local/bundle
COPY --from=builder /app /app
-ENV LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 \
+ENV LD_PRELOAD="libjemalloc.so.2" \
MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:5000,muzzy_decay_ms:5000,narenas:2"
EXPOSE 3000
diff --git a/Gemfile b/Gemfile
index 8d0225b937..50b0539436 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,29 +2,31 @@
source "https://rubygems.org"
-DECIDIM_VERSION = "0.26"
-DECIDIM_BRANCH = "release/#{DECIDIM_VERSION}-stable"
+DECIDIM_VERSION = "0.27"
+DECIDIM_BRANCH = "release/#{DECIDIM_VERSION}-stable".freeze
ruby RUBY_VERSION
-# Many gems depend on environment variables, so we load them as soon as possible
-gem "dotenv-rails", require: "dotenv/rails-now"
-
# Core gems
gem "decidim", "~> #{DECIDIM_VERSION}.0"
gem "decidim-conferences", "~> #{DECIDIM_VERSION}.0"
+gem "decidim-initiatives", "~> #{DECIDIM_VERSION}.0"
gem "decidim-templates", "~> #{DECIDIM_VERSION}.0"
# External Decidim gems
gem "decidim-cache_cleaner"
gem "decidim-decidim_awesome"
+gem "decidim-extended_socio_demographic_authorization_handler", git: "https://github.com/OpenSourcePolitics/decidim-module-extended_socio_demographic_authorization_handler.git",
+ branch: DECIDIM_BRANCH
+gem "decidim-extra_user_fields", git: "https://github.com/paulinebessoles/decidim-module-extra_user_fields", branch: "feat/add_tests_0_27"
gem "decidim-friendly_signup", git: "https://github.com/OpenSourcePolitics/decidim-module-friendly_signup.git"
-gem "decidim-gallery"
+# TODO: Bump to 0.27.0 when released
+# gem "decidim-gallery"
gem "decidim-homepage_interactive_map", git: "https://github.com/OpenSourcePolitics/decidim-module-homepage_interactive_map.git", branch: DECIDIM_BRANCH
gem "decidim-ludens", git: "https://github.com/OpenSourcePolitics/decidim-ludens.git", branch: DECIDIM_BRANCH
gem "decidim-phone_authorization_handler", git: "https://github.com/OpenSourcePolitics/decidim-module_phone_authorization_handler", branch: DECIDIM_BRANCH
gem "decidim-spam_detection"
-gem "decidim-term_customizer", git: "https://github.com/armandfardeau/decidim-module-term_customizer.git", branch: "fix/precompile-on-docker-0.26"
+gem "decidim-term_customizer", git: "https://github.com/armandfardeau/decidim-module-term_customizer.git", branch: "fix/precompile-on-docker"
# Omniauth gems
gem "omniauth-france_connect", git: "https://github.com/OpenSourcePolitics/omniauth-france_connect"
@@ -35,6 +37,8 @@ gem "activejob-uniqueness", require: "active_job/uniqueness/sidekiq_patch"
gem "aws-sdk-s3", require: false
gem "bootsnap", "~> 1.4"
gem "deepl-rb", require: "deepl"
+gem "deface"
+gem "dotenv-rails", "~> 2.7"
gem "faker", "~> 2.14"
gem "fog-aws"
gem "foundation_rails_helper", git: "https://github.com/sgruhier/foundation_rails_helper.git"
diff --git a/Gemfile.lock b/Gemfile.lock
index 55a67494c4..1840ab3988 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,37 +1,45 @@
GIT
remote: https://github.com/OpenSourcePolitics/decidim-ludens.git
- revision: d5bebe0c01f02a7f6cf032adbde1812f8ccd2cdf
- branch: release/0.26-stable
+ revision: ef88d556cf788135ceab7986a89b929e3722321b
+ branch: release/0.27-stable
specs:
decidim-ludens (1.0.0)
- decidim-core (~> 0.26.0)
+ decidim-core (~> 0.27.0)
+
+GIT
+ remote: https://github.com/OpenSourcePolitics/decidim-module-extended_socio_demographic_authorization_handler.git
+ revision: adec5e66cd07b5e5fdce5562453a7e8d6de88013
+ branch: release/0.27-stable
+ specs:
+ decidim-extended_socio_demographic_authorization_handler (0.27.0)
+ decidim-core (~> 0.27)
GIT
remote: https://github.com/OpenSourcePolitics/decidim-module-friendly_signup.git
- revision: 455543991dbf98936c1653dfcb04ac95b464e5a9
+ revision: 9b8ece180ca97bf97c5d401a42a79b1dc4b8b536
specs:
- decidim-friendly_signup (0.4.4)
- decidim-core (~> 0.26.0)
+ decidim-friendly_signup (0.4.5)
+ decidim-core (~> 0.27)
GIT
remote: https://github.com/OpenSourcePolitics/decidim-module-homepage_interactive_map.git
- revision: 4e6b7a1a5fcfa0661ba93d1bedb2e09da66d5326
- branch: release/0.26-stable
+ revision: dd685166fdf953a11bd6a9e0dac56feca3bd0708
+ branch: release/0.27-stable
specs:
decidim-homepage_interactive_map (2.0.0)
- decidim-admin (>= 0.25.0, < 0.27)
- decidim-core (>= 0.25.0, < 0.27)
- decidim-dev (>= 0.25.0, < 0.27)
+ decidim-admin (>= 0.25.0, < 0.28)
+ decidim-core (>= 0.25.0, < 0.28)
+ decidim-dev (>= 0.25.0, < 0.28)
rgeo (~> 2.4)
rgeo-proj4 (~> 3.1)
GIT
remote: https://github.com/OpenSourcePolitics/decidim-module_phone_authorization_handler
- revision: 488cc8827845ec1c5266aa499df2ebf9b20e02a3
- branch: release/0.26-stable
+ revision: 79f2a5f6c3357d63f92423a2b173893f4c4d06d8
+ branch: release/0.27-stable
specs:
decidim-phone_authorization_handler (1.0.0)
- decidim-core (~> 0.26)
+ decidim-core (~> 0.27)
GIT
remote: https://github.com/OpenSourcePolitics/omniauth-france_connect
@@ -50,12 +58,22 @@ GIT
GIT
remote: https://github.com/armandfardeau/decidim-module-term_customizer.git
- revision: 63170f69b51bb7e7f60f20856e944ae1357f4dc7
- branch: fix/precompile-on-docker-0.26
+ revision: 5c2b648e07c5fd51e2598256886895b9a83d698c
+ branch: fix/precompile-on-docker
+ specs:
+ decidim-term_customizer (0.27.0)
+ decidim-admin (~> 0.27.0)
+ decidim-core (~> 0.27.0)
+
+GIT
+ remote: https://github.com/paulinebessoles/decidim-module-extra_user_fields
+ revision: 8fea55957c88073242026175d78aa4a0633e68e8
+ branch: feat/add_tests_0_27
specs:
- decidim-term_customizer (0.26.0)
- decidim-admin (~> 0.26.0)
- decidim-core (~> 0.26.0)
+ decidim-extra_user_fields (0.27.2)
+ country_select (~> 4.0)
+ decidim-core (>= 0.27.0, < 0.28)
+ deface (~> 1.5)
GIT
remote: https://github.com/sgruhier/foundation_rails_helper.git
@@ -70,38 +88,40 @@ GIT
GEM
remote: https://rubygems.org/
specs:
- actioncable (6.0.6.1)
- actionpack (= 6.0.6.1)
+ actioncable (6.1.7.6)
+ actionpack (= 6.1.7.6)
+ activesupport (= 6.1.7.6)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
- actionmailbox (6.0.6.1)
- actionpack (= 6.0.6.1)
- activejob (= 6.0.6.1)
- activerecord (= 6.0.6.1)
- activestorage (= 6.0.6.1)
- activesupport (= 6.0.6.1)
+ actionmailbox (6.1.7.6)
+ actionpack (= 6.1.7.6)
+ activejob (= 6.1.7.6)
+ activerecord (= 6.1.7.6)
+ activestorage (= 6.1.7.6)
+ activesupport (= 6.1.7.6)
mail (>= 2.7.1)
- actionmailer (6.0.6.1)
- actionpack (= 6.0.6.1)
- actionview (= 6.0.6.1)
- activejob (= 6.0.6.1)
+ actionmailer (6.1.7.6)
+ actionpack (= 6.1.7.6)
+ actionview (= 6.1.7.6)
+ activejob (= 6.1.7.6)
+ activesupport (= 6.1.7.6)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (6.0.6.1)
- actionview (= 6.0.6.1)
- activesupport (= 6.0.6.1)
- rack (~> 2.0, >= 2.0.8)
+ actionpack (6.1.7.6)
+ actionview (= 6.1.7.6)
+ activesupport (= 6.1.7.6)
+ rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
- actiontext (6.0.6.1)
- actionpack (= 6.0.6.1)
- activerecord (= 6.0.6.1)
- activestorage (= 6.0.6.1)
- activesupport (= 6.0.6.1)
+ actiontext (6.1.7.6)
+ actionpack (= 6.1.7.6)
+ activerecord (= 6.1.7.6)
+ activestorage (= 6.1.7.6)
+ activesupport (= 6.1.7.6)
nokogiri (>= 1.8.5)
- actionview (6.0.6.1)
- activesupport (= 6.0.6.1)
+ actionview (6.1.7.6)
+ activesupport (= 6.1.7.6)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@@ -109,50 +129,52 @@ GEM
active_link_to (1.0.5)
actionpack
addressable
- activejob (6.0.6.1)
- activesupport (= 6.0.6.1)
+ activejob (6.1.7.6)
+ activesupport (= 6.1.7.6)
globalid (>= 0.3.6)
activejob-uniqueness (0.2.5)
activejob (>= 4.2, < 7.1)
redlock (>= 1.2, < 2)
- activemodel (6.0.6.1)
- activesupport (= 6.0.6.1)
- activerecord (6.0.6.1)
- activemodel (= 6.0.6.1)
- activesupport (= 6.0.6.1)
- activestorage (6.0.6.1)
- actionpack (= 6.0.6.1)
- activejob (= 6.0.6.1)
- activerecord (= 6.0.6.1)
+ activemodel (6.1.7.6)
+ activesupport (= 6.1.7.6)
+ activerecord (6.1.7.6)
+ activemodel (= 6.1.7.6)
+ activesupport (= 6.1.7.6)
+ activestorage (6.1.7.6)
+ actionpack (= 6.1.7.6)
+ activejob (= 6.1.7.6)
+ activerecord (= 6.1.7.6)
+ activesupport (= 6.1.7.6)
marcel (~> 1.0)
- activesupport (6.0.6.1)
+ mini_mime (>= 1.1.0)
+ activesupport (6.1.7.6)
concurrent-ruby (~> 1.0, >= 1.0.2)
- i18n (>= 0.7, < 2)
- minitest (~> 5.1)
- tzinfo (~> 1.1)
- zeitwerk (~> 2.2, >= 2.2.2)
+ i18n (>= 1.6, < 2)
+ minitest (>= 5.1)
+ tzinfo (~> 2.0)
+ zeitwerk (~> 2.3)
acts_as_list (0.9.19)
activerecord (>= 3.0)
- addressable (2.8.4)
+ addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0)
aes_key_wrap (1.1.0)
ast (2.4.2)
attr_required (1.0.1)
aws-eventstream (1.2.0)
- aws-partitions (1.767.0)
- aws-sdk-core (3.173.0)
+ aws-partitions (1.814.0)
+ aws-sdk-core (3.181.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
- aws-sdk-kms (1.64.0)
- aws-sdk-core (~> 3, >= 3.165.0)
+ aws-sdk-kms (1.71.0)
+ aws-sdk-core (~> 3, >= 3.177.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.122.0)
- aws-sdk-core (~> 3, >= 3.165.0)
+ aws-sdk-s3 (1.134.0)
+ aws-sdk-core (~> 3, >= 3.181.0)
aws-sdk-kms (~> 1)
- aws-sigv4 (~> 1.4)
- aws-sigv4 (1.5.2)
+ aws-sigv4 (~> 1.6)
+ aws-sigv4 (1.6.0)
aws-eventstream (~> 1, >= 1.0.2)
axe-core-api (4.7.0)
dumb_delegator
@@ -183,7 +205,7 @@ GEM
browser (2.7.1)
builder (3.2.4)
byebug (11.1.3)
- capybara (3.39.1)
+ capybara (3.39.2)
addressable
matrix
mini_mime (>= 0.1.3)
@@ -214,7 +236,7 @@ GEM
charlock_holmes (0.7.7)
chef-utils (18.2.7)
concurrent-ruby
- childprocess (3.0.0)
+ childprocess (4.1.0)
climate_control (1.2.0)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
@@ -225,75 +247,84 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.12.2)
+ colorize (0.8.1)
+ commonmarker (0.23.10)
concurrent-ruby (1.2.2)
connection_pool (2.4.1)
+ countries (3.1.0)
+ i18n_data (~> 0.11.0)
+ sixarm_ruby_unaccent (~> 1.1)
+ unicode_utils (~> 1.4)
+ country_select (4.0.0)
+ countries (~> 3.0)
+ sort_alphabetical (~> 1.0)
crack (0.4.5)
rexml
crass (1.0.6)
- css_parser (1.14.0)
+ css_parser (1.15.0)
addressable
- dalli (3.2.4)
+ dalli (3.2.5)
date (3.3.3)
- date_validator (0.9.0)
- activemodel
- activesupport
+ date_validator (0.12.0)
+ activemodel (>= 3)
+ activesupport (>= 3)
db-query-matchers (0.10.0)
activesupport (>= 4.0, < 7)
rspec (~> 3.0)
- decidim (0.26.7)
- decidim-accountability (= 0.26.7)
- decidim-admin (= 0.26.7)
- decidim-api (= 0.26.7)
- decidim-assemblies (= 0.26.7)
- decidim-blogs (= 0.26.7)
- decidim-budgets (= 0.26.7)
- decidim-comments (= 0.26.7)
- decidim-core (= 0.26.7)
- decidim-debates (= 0.26.7)
- decidim-forms (= 0.26.7)
- decidim-generators (= 0.26.7)
- decidim-meetings (= 0.26.7)
- decidim-pages (= 0.26.7)
- decidim-participatory_processes (= 0.26.7)
- decidim-proposals (= 0.26.7)
- decidim-sortitions (= 0.26.7)
- decidim-surveys (= 0.26.7)
- decidim-system (= 0.26.7)
- decidim-templates (= 0.26.7)
- decidim-verifications (= 0.26.7)
- decidim-accountability (0.26.7)
- decidim-comments (= 0.26.7)
- decidim-core (= 0.26.7)
- decidim-admin (0.26.7)
+ decidim (0.27.4)
+ decidim-accountability (= 0.27.4)
+ decidim-admin (= 0.27.4)
+ decidim-api (= 0.27.4)
+ decidim-assemblies (= 0.27.4)
+ decidim-blogs (= 0.27.4)
+ decidim-budgets (= 0.27.4)
+ decidim-comments (= 0.27.4)
+ decidim-core (= 0.27.4)
+ decidim-debates (= 0.27.4)
+ decidim-forms (= 0.27.4)
+ decidim-generators (= 0.27.4)
+ decidim-meetings (= 0.27.4)
+ decidim-pages (= 0.27.4)
+ decidim-participatory_processes (= 0.27.4)
+ decidim-proposals (= 0.27.4)
+ decidim-sortitions (= 0.27.4)
+ decidim-surveys (= 0.27.4)
+ decidim-system (= 0.27.4)
+ decidim-templates (= 0.27.4)
+ decidim-verifications (= 0.27.4)
+ decidim-accountability (0.27.4)
+ decidim-comments (= 0.27.4)
+ decidim-core (= 0.27.4)
+ decidim-admin (0.27.4)
active_link_to (~> 1.0)
- decidim-core (= 0.26.7)
+ decidim-core (= 0.27.4)
devise (~> 4.7)
devise-i18n (~> 1.2)
devise_invitable (~> 2.0)
- decidim-api (0.26.7)
+ decidim-api (0.27.4)
graphql (~> 1.12, < 1.13)
+ graphql-docs (~> 2.1.0)
rack-cors (~> 1.0)
- redcarpet (~> 3.5, >= 3.5.1)
- decidim-assemblies (0.26.7)
- decidim-core (= 0.26.7)
- decidim-blogs (0.26.7)
- decidim-admin (= 0.26.7)
- decidim-comments (= 0.26.7)
- decidim-core (= 0.26.7)
- decidim-budgets (0.26.7)
- decidim-comments (= 0.26.7)
- decidim-core (= 0.26.7)
+ decidim-assemblies (0.27.4)
+ decidim-core (= 0.27.4)
+ decidim-blogs (0.27.4)
+ decidim-admin (= 0.27.4)
+ decidim-comments (= 0.27.4)
+ decidim-core (= 0.27.4)
+ decidim-budgets (0.27.4)
+ decidim-comments (= 0.27.4)
+ decidim-core (= 0.27.4)
decidim-cache_cleaner (1.0.4)
decidim-core (~> 0.26)
- decidim-comments (0.26.7)
- decidim-core (= 0.26.7)
+ decidim-comments (0.27.4)
+ decidim-core (= 0.27.4)
redcarpet (~> 3.5, >= 3.5.1)
- decidim-conferences (0.26.7)
- decidim-core (= 0.26.7)
- decidim-meetings (= 0.26.7)
+ decidim-conferences (0.27.4)
+ decidim-core (= 0.27.4)
+ decidim-meetings (= 0.27.4)
wicked_pdf (~> 2.1)
wkhtmltopdf-binary (~> 0.12)
- decidim-core (0.26.7)
+ decidim-core (0.27.4)
active_link_to (~> 1.0)
acts_as_list (~> 0.9)
batch-loader (~> 1.2)
@@ -302,21 +333,21 @@ GEM
cells-erb (~> 0.1.0)
cells-rails (~> 0.1.3)
charlock_holmes (~> 0.7)
- date_validator (~> 0.9.0)
- decidim-api (= 0.26.7)
+ date_validator (~> 0.12.0)
+ decidim-api (= 0.27.4)
devise (~> 4.7)
devise-i18n (~> 1.2)
diffy (~> 3.3)
doorkeeper (~> 5.1)
doorkeeper-i18n (~> 4.0)
- file_validators (~> 2.1)
+ file_validators (~> 3.0)
fog-local (~> 0.6)
- foundation_rails_helper
- geocoder (~> 1.7.5)
+ foundation_rails_helper (~> 4.0)
+ geocoder (~> 1.8)
hashdiff (>= 0.4.0, < 2.0.0)
invisible_captcha (~> 0.12)
kaminari (~> 1.2, >= 1.2.1)
- loofah (~> 2.3.1)
+ loofah (~> 2.19.0)
mime-types (>= 1.16, < 4.0)
mini_magick (~> 4.9)
mustache (~> 1.1.0)
@@ -331,100 +362,105 @@ GEM
premailer-rails (~> 1.10)
rack (~> 2.2, >= 2.2.3)
rack-attack (~> 6.0)
- rails (~> 6.0.4)
+ rails (~> 6.1.0)
rails-i18n (~> 6.0)
ransack (~> 2.4.1)
- rectify (~> 0.13.0)
redis (~> 4.1)
request_store (~> 1.5.0)
rubyXL (~> 3.4)
rubyzip (~> 2.0)
- searchlight (~> 4.1)
seven_zip_ruby (~> 1.3)
social-share-button (~> 1.2, >= 1.2.1)
valid_email2 (~> 2.1)
webpacker (= 6.0.0.rc.5)
+ webpush (~> 1.1)
wisper (~> 2.0)
- decidim-debates (0.26.7)
- decidim-comments (= 0.26.7)
- decidim-core (= 0.26.7)
- decidim-decidim_awesome (0.9.1)
+ decidim-debates (0.27.4)
+ decidim-comments (= 0.27.4)
+ decidim-core (= 0.27.4)
+ decidim-decidim_awesome (0.9.3)
decidim-admin (>= 0.26.0, < 0.28)
decidim-core (>= 0.26.0, < 0.28)
sassc (~> 2.3)
- decidim-dev (0.26.7)
+ decidim-dev (0.27.4)
axe-core-rspec (~> 4.1.0)
byebug (~> 11.0)
capybara (~> 3.24)
db-query-matchers (~> 0.10.0)
- decidim (= 0.26.7)
+ decidim (= 0.27.4)
erb_lint (~> 0.0.35)
factory_bot_rails (~> 4.8)
i18n-tasks (~> 0.9.18)
mdl (~> 0.5)
- nokogiri (~> 1.12)
+ nokogiri (~> 1.13)
+ parallel_tests (~> 3.7)
puma (~> 5.0)
rails-controller-testing (~> 1.0)
- rspec-cells (~> 0.3.4)
+ rspec-cells (~> 0.3.7)
rspec-html-matchers (~> 0.9.1)
rspec-rails (~> 4.0)
rspec-retry (~> 0.6.2)
rspec_junit_formatter (~> 0.3.0)
- rubocop (~> 0.92.0)
- rubocop-rails (~> 2.8)
- rubocop-rspec (= 1.43.2)
- selenium-webdriver (~> 3.142)
- simplecov (~> 0.19.0)
- simplecov-cobertura (~> 1.3.1)
- system_test_html_screenshots (~> 0.2)
+ rubocop (~> 1.28.0)
+ rubocop-rails (~> 2.14)
+ rubocop-rspec (~> 2.10)
+ selenium-webdriver (~> 4.1.0)
+ simplecov (~> 0.21.0)
+ simplecov-cobertura (~> 2.1.0)
w3c_rspec_validators (~> 0.3.0)
webmock (~> 3.6)
wisper-rspec (~> 1.0)
- decidim-forms (0.26.7)
- decidim-core (= 0.26.7)
+ decidim-forms (0.27.4)
+ decidim-core (= 0.27.4)
wicked_pdf (~> 2.1)
wkhtmltopdf-binary (~> 0.12)
- decidim-gallery (0.0.4)
- decidim-admin (~> 0.26.0)
- decidim-core (~> 0.26.0)
- deface (~> 1.9)
- decidim-generators (0.26.7)
- decidim-core (= 0.26.7)
- decidim-meetings (0.26.7)
- decidim-core (= 0.26.7)
- decidim-forms (= 0.26.7)
+ decidim-generators (0.27.4)
+ decidim-core (= 0.27.4)
+ decidim-initiatives (0.27.4)
+ decidim-admin (= 0.27.4)
+ decidim-comments (= 0.27.4)
+ decidim-core (= 0.27.4)
+ decidim-verifications (= 0.27.4)
+ origami (~> 2.1)
+ rexml (~> 3.2.5)
+ wicked (~> 1.3)
+ wicked_pdf (~> 2.1)
+ wkhtmltopdf-binary (~> 0.12)
+ decidim-meetings (0.27.4)
+ decidim-core (= 0.27.4)
+ decidim-forms (= 0.27.4)
icalendar (~> 2.5)
- decidim-pages (0.26.7)
- decidim-core (= 0.26.7)
- decidim-participatory_processes (0.26.7)
- decidim-core (= 0.26.7)
- decidim-proposals (0.26.7)
- decidim-comments (= 0.26.7)
- decidim-core (= 0.26.7)
- doc2text (~> 0.4.4)
+ decidim-pages (0.27.4)
+ decidim-core (= 0.27.4)
+ decidim-participatory_processes (0.27.4)
+ decidim-core (= 0.27.4)
+ decidim-proposals (0.27.4)
+ decidim-comments (= 0.27.4)
+ decidim-core (= 0.27.4)
+ doc2text (~> 0.4.5)
redcarpet (~> 3.5, >= 3.5.1)
- decidim-sortitions (0.26.7)
- decidim-admin (= 0.26.7)
- decidim-comments (= 0.26.7)
- decidim-core (= 0.26.7)
- decidim-proposals (= 0.26.7)
- decidim-spam_detection (3.1.0)
- decidim-core (~> 0.26.0)
- decidim-surveys (0.26.7)
- decidim-core (= 0.26.7)
- decidim-forms (= 0.26.7)
- decidim-templates (= 0.26.7)
- decidim-system (0.26.7)
+ decidim-sortitions (0.27.4)
+ decidim-admin (= 0.27.4)
+ decidim-comments (= 0.27.4)
+ decidim-core (= 0.27.4)
+ decidim-proposals (= 0.27.4)
+ decidim-spam_detection (4.0.0)
+ decidim-core (~> 0.27.0)
+ decidim-surveys (0.27.4)
+ decidim-core (= 0.27.4)
+ decidim-forms (= 0.27.4)
+ decidim-templates (= 0.27.4)
+ decidim-system (0.27.4)
active_link_to (~> 1.0)
- decidim-core (= 0.26.7)
+ decidim-core (= 0.27.4)
devise (~> 4.7)
devise-i18n (~> 1.2)
devise_invitable (~> 2.0)
- decidim-templates (0.26.7)
- decidim-core (= 0.26.7)
- decidim-forms (= 0.26.7)
- decidim-verifications (0.26.7)
- decidim-core (= 0.26.7)
+ decidim-templates (0.27.4)
+ decidim-core (= 0.27.4)
+ decidim-forms (= 0.27.4)
+ decidim-verifications (0.27.4)
+ decidim-core (= 0.27.4)
declarative-builder (0.1.0)
declarative-option (< 0.2.0)
declarative-option (0.1.0)
@@ -462,7 +498,6 @@ GEM
dotenv (= 2.8.1)
railties (>= 3.2)
dumb_delegator (1.0.0)
- equalizer (0.0.11)
erb_lint (0.0.37)
activesupport
better_html (~> 1.0.7)
@@ -474,10 +509,13 @@ GEM
erbse (0.1.4)
temple
erubi (1.12.0)
+ escape_utils (1.3.0)
et-orbi (1.2.7)
tzinfo
- excon (0.100.0)
+ excon (0.102.0)
execjs (2.8.1)
+ extended-markdown-filter (0.7.0)
+ html-pipeline (~> 2.9)
factory_bot (4.11.1)
activesupport (>= 3.0.0)
factory_bot_rails (4.11.1)
@@ -492,7 +530,7 @@ GEM
faraday (>= 1, < 3)
faraday-net_http (3.0.2)
ffi (1.15.5)
- file_validators (2.3.0)
+ file_validators (3.0.0)
activemodel (>= 3.2)
mime-types (>= 1.0)
fog-aws (3.19.0)
@@ -516,15 +554,28 @@ GEM
fugit (1.8.1)
et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
- geocoder (1.7.5)
+ gemoji (3.0.1)
+ geocoder (1.8.2)
globalid (1.1.0)
activesupport (>= 5.0)
graphql (1.12.24)
+ graphql-docs (2.1.0)
+ commonmarker (~> 0.16)
+ escape_utils (~> 1.2)
+ extended-markdown-filter (~> 0.4)
+ gemoji (~> 3.0)
+ graphql (~> 1.12)
+ html-pipeline (~> 2.9)
+ 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)
+ activesupport (>= 2)
+ nokogiri (>= 1.4)
html_tokenizer (0.0.7)
htmlentities (4.3.4)
httpclient (2.8.3)
@@ -540,7 +591,8 @@ GEM
rails-i18n
rainbow (>= 2.2.2, < 4.0)
terminal-table (>= 1.5.1)
- icalendar (2.8.0)
+ i18n_data (0.11.0)
+ icalendar (2.9.0)
ice_cube (~> 0.16)
ice_cube (0.16.4)
ice_nine (0.11.2)
@@ -585,12 +637,12 @@ GEM
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
- lograge (0.12.0)
+ lograge (0.13.0)
actionpack (>= 4)
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
- loofah (2.3.1)
+ loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.8.1)
@@ -607,22 +659,22 @@ GEM
mixlib-config (>= 2.2.1, < 4)
mixlib-shellout
method_source (1.0.0)
- mime-types (3.4.1)
+ mime-types (3.5.1)
mime-types-data (~> 3.2015)
- mime-types-data (3.2023.0218.1)
+ mime-types-data (3.2023.0808)
mini_magick (4.12.0)
- mini_mime (1.1.2)
- minitest (5.18.1)
+ mini_mime (1.1.5)
+ minitest (5.19.0)
mixlib-cli (2.1.8)
mixlib-config (3.0.27)
tomlrb
mixlib-shellout (3.2.7)
chef-utils
- msgpack (1.7.1)
+ msgpack (1.7.2)
multi_json (1.15.0)
multi_xml (0.6.0)
mustache (1.1.1)
- net-imap (0.3.6)
+ net-imap (0.3.7)
date
net-protocol
net-pop (0.1.2)
@@ -632,6 +684,8 @@ GEM
net-smtp (0.3.3)
net-protocol
nio4r (2.5.9)
+ nokogiri (1.13.4-aarch64-linux)
+ racc (~> 1.4)
nokogiri (1.13.4-arm64-darwin)
racc (~> 1.4)
nokogiri (1.13.4-x86_64-darwin)
@@ -689,15 +743,18 @@ GEM
validate_email
validate_url
webfinger (~> 1.2)
+ origami (2.1.0)
+ colorize (~> 0.7)
orm_adapter (0.5.0)
paper_trail (12.3.0)
activerecord (>= 5.2)
request_store (~> 1.1)
parallel (1.23.0)
- parallel_tests (4.2.1)
+ parallel_tests (3.13.0)
parallel
- parser (3.2.2.1)
+ parser (3.2.2.3)
ast (~> 2.4.1)
+ racc
pg (1.1.4)
pg_search (2.3.6)
activerecord (>= 5.2)
@@ -712,13 +769,13 @@ GEM
net-smtp
premailer (~> 1.7, >= 1.7.9)
public_suffix (5.0.3)
- puma (5.6.5)
+ puma (5.6.7)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.7.1)
- rack (2.2.7)
- rack-attack (6.6.1)
- rack (>= 1.0, < 3)
+ rack (2.2.8)
+ rack-attack (6.7.0)
+ rack (>= 1.0, < 4)
rack-cors (1.1.1)
rack (>= 2.0.0)
rack-oauth2 (1.21.3)
@@ -727,46 +784,46 @@ GEM
httpclient
json-jwt (>= 1.11.0)
rack (>= 2.1.0)
- rack-protection (3.0.6)
- rack
+ rack-protection (3.1.0)
+ rack (~> 2.2, >= 2.2.4)
rack-proxy (0.7.6)
rack
rack-test (2.1.0)
rack (>= 1.3)
- rails (6.0.6.1)
- actioncable (= 6.0.6.1)
- actionmailbox (= 6.0.6.1)
- actionmailer (= 6.0.6.1)
- actionpack (= 6.0.6.1)
- actiontext (= 6.0.6.1)
- actionview (= 6.0.6.1)
- activejob (= 6.0.6.1)
- activemodel (= 6.0.6.1)
- activerecord (= 6.0.6.1)
- activestorage (= 6.0.6.1)
- activesupport (= 6.0.6.1)
- bundler (>= 1.3.0)
- railties (= 6.0.6.1)
+ rails (6.1.7.6)
+ actioncable (= 6.1.7.6)
+ actionmailbox (= 6.1.7.6)
+ actionmailer (= 6.1.7.6)
+ actionpack (= 6.1.7.6)
+ actiontext (= 6.1.7.6)
+ actionview (= 6.1.7.6)
+ activejob (= 6.1.7.6)
+ activemodel (= 6.1.7.6)
+ activerecord (= 6.1.7.6)
+ activestorage (= 6.1.7.6)
+ activesupport (= 6.1.7.6)
+ bundler (>= 1.15.0)
+ railties (= 6.1.7.6)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
- rails-dom-testing (2.1.1)
+ rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
- rails-html-sanitizer (1.4.3)
- loofah (~> 2.3)
+ rails-html-sanitizer (1.5.0)
+ loofah (~> 2.19, >= 2.19.1)
rails-i18n (6.0.0)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 7)
- railties (6.0.6.1)
- actionpack (= 6.0.6.1)
- activesupport (= 6.0.6.1)
+ railties (6.1.7.6)
+ actionpack (= 6.1.7.6)
+ activesupport (= 6.1.7.6)
method_source
- rake (>= 0.8.7)
- thor (>= 0.20.3, < 2.0)
+ rake (>= 12.2)
+ thor (~> 1.0)
rainbow (3.1.1)
rake (13.0.6)
ransack (2.4.2)
@@ -776,23 +833,17 @@ GEM
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
- rectify (0.13.0)
- activemodel (>= 4.1.0)
- activerecord (>= 4.1.0)
- activesupport (>= 4.1.0)
- virtus (~> 1.0.5)
- wisper (>= 1.6.1)
redcarpet (3.6.0)
redis (4.8.1)
redlock (1.3.2)
redis (>= 3.0.0, < 6.0)
- regexp_parser (2.8.0)
+ regexp_parser (2.8.1)
request_store (1.5.1)
rack (>= 1.4)
responders (3.1.0)
actionpack (>= 5.2)
railties (>= 5.2)
- rexml (3.2.5)
+ rexml (3.2.6)
rgeo (2.4.0)
rgeo-proj4 (3.1.1)
rgeo (~> 2.0)
@@ -811,7 +862,7 @@ GEM
rspec-html-matchers (0.9.4)
nokogiri (~> 1)
rspec (>= 3.0.0.a, < 4)
- rspec-mocks (3.12.5)
+ rspec-mocks (3.12.6)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-rails (4.1.2)
@@ -824,29 +875,29 @@ GEM
rspec-support (~> 3.10)
rspec-retry (0.6.2)
rspec-core (> 3.3)
- rspec-support (3.12.0)
+ rspec-support (3.12.1)
rspec_junit_formatter (0.3.0)
rspec-core (>= 2, < 4, != 2.12.0)
- rubocop (0.92.0)
+ rubocop (1.28.2)
parallel (~> 1.10)
- parser (>= 2.7.1.5)
+ parser (>= 3.1.0.0)
rainbow (>= 2.2.2, < 4.0)
- regexp_parser (>= 1.7)
+ regexp_parser (>= 1.8, < 3.0)
rexml
- rubocop-ast (>= 0.5.0)
+ rubocop-ast (>= 1.17.0, < 2.0)
ruby-progressbar (~> 1.7)
- unicode-display_width (>= 1.4.0, < 2.0)
- rubocop-ast (1.28.1)
+ unicode-display_width (>= 1.4.0, < 3.0)
+ rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
rubocop-faker (1.1.0)
faker (>= 2.12.0)
rubocop (>= 0.82.0)
- rubocop-rails (2.9.1)
+ rubocop-rails (2.15.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
- rubocop (>= 0.90.0, < 2.0)
- rubocop-rspec (1.43.2)
- rubocop (~> 0.87)
+ rubocop (>= 1.7.0, < 2.0)
+ rubocop-rspec (2.11.1)
+ rubocop (~> 1.19)
ruby-progressbar (1.13.0)
ruby-vips (2.1.4)
ffi (~> 1.12)
@@ -856,50 +907,61 @@ GEM
rubyzip (>= 1.3.0)
ruby_http_client (3.5.5)
rubyzip (2.3.2)
- rufus-scheduler (3.8.2)
+ rufus-scheduler (3.9.1)
fugit (~> 1.1, >= 1.1.6)
+ sass (3.7.4)
+ sass-listen (~> 4.0.0)
+ sass-listen (4.0.0)
+ rb-fsevent (~> 0.9, >= 0.9.4)
+ rb-inotify (~> 0.9, >= 0.9.7)
sassc (2.4.0)
ffi (~> 1.9)
- searchlight (4.1.0)
- selenium-webdriver (3.142.7)
- childprocess (>= 0.5, < 4.0)
+ selenium-webdriver (4.1.0)
+ childprocess (>= 0.5, < 5.0)
+ rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2)
semantic_range (3.0.0)
sendgrid-ruby (6.6.2)
ruby_http_client (~> 3.4)
- sentry-rails (5.9.0)
+ sentry-rails (5.10.0)
railties (>= 5.0)
- sentry-ruby (~> 5.9.0)
- sentry-ruby (5.9.0)
+ sentry-ruby (~> 5.10.0)
+ sentry-ruby (5.10.0)
concurrent-ruby (~> 1.0, >= 1.0.2)
- sentry-sidekiq (5.9.0)
- sentry-ruby (~> 5.9.0)
+ sentry-sidekiq (5.10.0)
+ sentry-ruby (~> 5.10.0)
sidekiq (>= 3.0)
seven_zip_ruby (1.3.0)
- sidekiq (6.5.8)
+ sidekiq (6.5.9)
connection_pool (>= 2.2.5, < 3)
rack (~> 2.0)
redis (>= 4.5.0, < 5)
- sidekiq-scheduler (5.0.2)
+ sidekiq-scheduler (5.0.3)
rufus-scheduler (~> 3.2)
sidekiq (>= 6, < 8)
tilt (>= 1.4.0)
- sidekiq_alive (2.2.1)
+ sidekiq_alive (2.2.3)
rack (< 3)
sidekiq (>= 5, < 8)
webrick (>= 1, < 2)
- simplecov (0.19.1)
+ simplecov (0.21.2)
docile (~> 1.1)
simplecov-html (~> 0.11)
- simplecov-cobertura (1.3.1)
- simplecov (~> 0.8)
+ simplecov_json_formatter (~> 0.1)
+ simplecov-cobertura (2.1.0)
+ rexml
+ simplecov (~> 0.19)
simplecov-html (0.12.3)
+ simplecov_json_formatter (0.1.4)
+ sixarm_ruby_unaccent (1.2.2)
smart_properties (1.17.0)
snaky_hash (2.0.1)
hashie
version_gem (~> 1.1, >= 1.1.1)
social-share-button (1.2.4)
coffee-rails
+ sort_alphabetical (1.1.0)
+ unicode_utils (>= 1.2.2)
spring (2.1.1)
spring-watcher-listen (2.0.1)
listen (>= 2.7, < 4.0)
@@ -918,8 +980,6 @@ GEM
httpclient (>= 2.4)
sys-filesystem (1.4.3)
ffi (~> 1.1)
- system_test_html_screenshots (0.2.0)
- actionpack (>= 5.2, < 6.1.a)
temple (0.10.2)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
@@ -928,10 +988,11 @@ GEM
tilt (2.2.0)
timeout (0.4.0)
tomlrb (2.0.3)
- tzinfo (1.2.11)
- thread_safe (~> 0.1)
+ tzinfo (2.0.6)
+ concurrent-ruby (~> 1.0)
uber (0.1.0)
- unicode-display_width (1.8.0)
+ unicode-display_width (2.4.2)
+ unicode_utils (1.4.0)
valid_email2 (2.3.1)
activemodel (>= 3.2)
mail (~> 2.5)
@@ -942,11 +1003,10 @@ GEM
activemodel (>= 3.0.0)
public_suffix
version_gem (1.1.3)
- virtus (1.0.5)
+ virtus (2.0.0)
axiom-types (~> 0.1)
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
- equalizer (~> 0.0, >= 0.0.9)
w3c_rspec_validators (0.3.0)
rails
rspec
@@ -965,7 +1025,7 @@ GEM
webfinger (1.2.0)
activesupport
httpclient (>= 2.4)
- webmock (3.18.1)
+ webmock (3.19.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
@@ -974,20 +1034,26 @@ GEM
rack-proxy (>= 0.6.1)
railties (>= 5.2)
semantic_range (>= 2.3.0)
+ webpush (1.1.0)
+ hkdf (~> 0.2)
+ jwt (~> 2.0)
webrick (1.8.1)
- websocket-driver (0.7.5)
+ websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
- wicked_pdf (2.6.3)
+ wicked (1.4.0)
+ railties (>= 3.0.7)
+ wicked_pdf (2.7.0)
activesupport
wisper (2.0.1)
wisper-rspec (1.1.0)
wkhtmltopdf-binary (0.12.6.6)
xpath (3.2.0)
nokogiri (~> 1.8)
- zeitwerk (2.6.8)
+ zeitwerk (2.6.11)
PLATFORMS
+ aarch64-linux
arm64-darwin-21
arm64-darwin-22
x86_64-darwin-20
@@ -1002,21 +1068,24 @@ DEPENDENCIES
byebug (~> 11.0)
climate_control (~> 1.2)
dalli
- decidim (~> 0.26.0)
+ decidim (~> 0.27.0)
decidim-cache_cleaner
- decidim-conferences (~> 0.26.0)
+ decidim-conferences (~> 0.27.0)
decidim-decidim_awesome
- decidim-dev (~> 0.26.0)
+ decidim-dev (~> 0.27.0)
+ decidim-extended_socio_demographic_authorization_handler!
+ decidim-extra_user_fields!
decidim-friendly_signup!
- decidim-gallery
decidim-homepage_interactive_map!
+ decidim-initiatives (~> 0.27.0)
decidim-ludens!
decidim-phone_authorization_handler!
decidim-spam_detection
- decidim-templates (~> 0.26.0)
+ decidim-templates (~> 0.27.0)
decidim-term_customizer!
deepl-rb
- dotenv-rails
+ deface
+ dotenv-rails (~> 2.7)
faker (~> 2.14)
fog-aws
foundation_rails_helper!
@@ -1045,7 +1114,7 @@ DEPENDENCIES
web-console (= 4.0.4)
RUBY VERSION
- ruby 2.7.5p203
+ ruby 3.0.6p216
BUNDLED WITH
- 2.4.9
+ 2.2.33
diff --git a/Makefile b/Makefile
index 711044e212..983320fea3 100644
--- a/Makefile
+++ b/Makefile
@@ -29,7 +29,7 @@ login:
build-classic:
docker buildx build -t $(IMAGE_NAME):$(VERSION) . --platform linux/amd64
build-scw:
- docker buildx build -t $(TAG) . --platform linux/amd64
+ docker build -t $(TAG) .
push:
@make build-scw
@make login
@@ -44,7 +44,7 @@ create-database:
run-migrations:
docker-compose run app bundle exec rails db:migrate
create-seeds:
- docker-compose run app bundle exec rails db:seed
+ docker-compose exec -e RAILS_ENV=development app /bin/bash -c '/usr/local/bundle/bin/bundle exec rake db:seed'
# Database commands
restore-dump:
@@ -69,15 +69,12 @@ start-clean-decidim:
@make run-migrations
@make start
-# Utils commands
-rails-console:
- docker exec -it decidim-app_app_1 rails c
-connect-app:
- docker exec -it decidim-app_app_1 bash
+teardown:
+ docker-compose down -v --rmi all
-# Stop and delete commands
-stop:
- docker-compose down
-delete:
- @make stop
- docker volume prune
+# TODO: Fix seeds for local-dev make command
+local-dev:
+ docker-compose -f docker-compose.dev.yml up -d
+ @make create-database
+ @make run-migrations
+ #@make create-seeds
diff --git a/OVERLOADS.md b/OVERLOADS.md
index a53d222288..7bf88b0dac 100644
--- a/OVERLOADS.md
+++ b/OVERLOADS.md
@@ -46,118 +46,6 @@ de6d804 - fix multipart object tagging (#40) (#41), 2021-12-24
* `lib/tasks/restore_dump.rake`
705e0ad - Run rubocop, 2021-12-01
-## Fix collaborative draft
-* `app/controllers/decidim/proposals/collaborative_drafts_controller.rb`
-* `app/views/decidim/proposals/collaborative_drafts/_wizard_aside.html.erb`
-* `app/views/v0.26/decidim/proposals/collaborative_drafts/_show.html.erb`
-* `spec/system/collaborative_drafts_fields_spec.rb`
-
-## Add budget reminder(#170) backport #8621
-* `app/commands/decidim/budgets/admin/create_order_reminders.rb`
-* `app/controllers/decidim/admin/components/base_controller.rb`
-* `app/controllers/decidim/admin/reminders_controller.rb`
-* `app/controllers/decidim/assemblies/admin/reminders_controller.rb`
-* `app/controllers/decidim/conferences/admin/reminders_controller.rb`
-* `app/controllers/decidim/participatory_processes/admin/reminders_controller.rb`
-* `app/forms/decidim/budgets/admin/order_reminder_form.rb`
-* `app/helpers/decidim/admin/reminders_helper.rb`
-* `app/jobs/decidim/budgets/send_vote_reminder_job.rb`
-* `app/jobs/decidim/reminder_generator_job.rb`
-* `app/mailers/decidim/budgets/vote_reminder_mailer.rb`
-* `app/models/decidim/reminder.rb`
-* `app/models/decidim/reminder_delivery.rb`
-* `app/models/decidim/reminder_record.rb`
-* `app/models/decidim/user.rb`
-* `app/permissions/decidim/admin/permissions.rb`
-* `app/permissions/decidim/budgets/admin/permissions.rb`
-* `app/services/decidim/budgets/order_reminder_generator.rb`
-* `app/views/decidim/admin/reminders/new.html.erb`
-* `app/views/decidim/budgets/admin/budgets/index.html.erb`
-* `app/views/decidim/budgets/vote_reminder_mailer/vote_reminder.html.erb`
-* `config/i18n-tasks.yml`
-```ruby
- - decidim.budgets.admin.reminders.orders.*
-```
-* `config/initializers/decidim.rb`
-```ruby
-Decidim.module_eval do
- autoload :ReminderRegistry, "decidim/reminder_registry"
- autoload :ReminderManifest, "decidim/reminder_manifest"
- autoload :ManifestMessages, "decidim/manifest_messages"
-
- def self.reminders_registry
- @reminders_registry ||= Decidim::ReminderRegistry.new
- end
-end
-
-Decidim.reminders_registry.register(:orders) do |reminder_registry|
- reminder_registry.generator_class_name = "Decidim::Budgets::OrderReminderGenerator"
- reminder_registry.form_class_name = "Decidim::Budgets::Admin::OrderReminderForm"
- reminder_registry.command_class_name = "Decidim::Budgets::Admin::CreateOrderReminders"
-
- reminder_registry.settings do |settings|
- settings.attribute :reminder_times, type: :array, default: [2.hours, 1.week, 2.weeks]
- end
-
- reminder_registry.messages do |msg|
- msg.set(:title) { |count: 0| I18n.t("decidim.budgets.admin.reminders.orders.title", count: count) }
- msg.set(:description) { I18n.t("decidim.budgets.admin.reminders.orders.description") }
- end
-end
-```
-* `config/locales/en.yaml`
-* `config/locales/fr.yaml`
-* `config/routes.rb`
-```ruby
-Decidim::Assemblies::AdminEngine.class_eval do
- routes do
- scope "/assemblies/:assembly_slug" do
- resources :components do
- resources :reminders, only: [:new, :create]
- end
- end
- end
-end
-
-Decidim::Conferences::AdminEngine.class_eval do
- routes do
- scope "/conferences/:conference_slug" do
- resources :components do
- resources :reminders, only: [:new, :create]
- end
- end
- end
-end
-
-Decidim::ParticipatoryProcesses::AdminEngine.class_eval do
- routes do
- scope "/participatory_processes/:participatory_process_slug" do
- resources :components do
- resources :reminders, only: [:new, :create]
- end
- end
- end
-end
-```
-* `db/migrate/20211208155453_create_decidim_reminders.rb`
-* `db/migrate/20211209121025_create_decidim_reminder_records.rb`
-* `db/migrate/20211209121040_create_decidim_reminder_deliveries.rb`
-* `lib/decidim/core/test/factories.rb`
-* `lib/decidim/importers/import_manifest.rb`
-* `lib/decidim/manifest_messages.rb`
-* `lib/decidim/reminder_manifest.rb`
-* `lib/decidim/reminder_registry.rb`
-* `lib/tasks/decidim_reminders_tasks.rake`
-* `spec/commands/decidim/budgets/admin/create_order_reminders_spec.rb`
-* `spec/forms/decidim/budgets/admin/order_reminder_form_spec.rb`
-* `spec/jobs/decidim/budgets/send_vote_reminder_job_spec.rb`
-* `spec/jobs/decidim/reminder_generator_job_spec.rb`
-* `spec/lib/importers/import_manifest_spec.rb`
-* `spec/lib/reminder_registry_spec.rb`
-* `spec/mailers/decidim/budgets/vote_reminder_mailer_spec.rb`
-* `spec/services/decidim/budgets/order_reminder_generator_spec.rb`
-* `spec/system/admin_reminds_users_with_pending_orders_spec.rb`
-
## Fix survey validation (#228)
* `app/cells/decidim/forms/step_navigation/show.erb`
* `app/packs/src/decidim/decidim_application.js`
@@ -165,8 +53,3 @@ end
* `config/initializers/decidim_verifications.rb`
* `spec/shared/has_questionnaire.rb`
* `spec/system/survey_spec.rb`
-
-## Fix survey conditional question saving, backport #10386
-* `spec/commands/decidim/forms/answer_questionnaire_spec.rb`
-* `app/forms/decidim/forms/answer_form.rb`
-* `app/forms/decidim/forms/questionnaire_form.rb`
diff --git a/app/commands/decidim/budgets/admin/create_order_reminders.rb b/app/commands/decidim/budgets/admin/create_order_reminders.rb
deleted file mode 100644
index b346245987..0000000000
--- a/app/commands/decidim/budgets/admin/create_order_reminders.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module Budgets
- module Admin
- # This command is executed when admin sends vote reminders.
- class CreateOrderReminders < Rectify::Command
- def initialize(form)
- @form = form
- end
-
- def call
- return broadcast(:invalid) if form.invalid?
- return broadcast(:invalid) unless voting_enabled?
- return broadcast(:invalid) if voting_ends_soon?
-
- generator.generate_for(current_component, &alternative_refresh_state)
-
- broadcast(:ok, generator.reminder_jobs_queued)
- end
-
- private
-
- attr_reader :form
-
- def alternative_refresh_state
- proc do |reminder|
- reminder.records.each do |record|
- next if %w(active pending).exclude? record.state
-
- record.state = begin
- if record.remindable.created_at > minimum_time_between_reminders ||
- (reminder.deliveries.present? && reminder.deliveries.last.created_at > minimum_time_between_reminders)
- "pending"
- else
- "active"
- end
- end
- record.save if record.changed?
- end
- end
- end
-
- def minimum_time_between_reminders
- form.minimum_interval_between_reminders.ago
- end
-
- def generator
- @generator ||= Decidim::Budgets::OrderReminderGenerator.new
- end
-
- def current_component
- form.current_component
- end
-
- def voting_enabled?
- form.voting_enabled?
- end
-
- def voting_ends_soon?
- form.voting_ends_soon?
- end
- end
- end
- end
-end
diff --git a/app/controllers/decidim/admin/components/base_controller.rb b/app/controllers/decidim/admin/components/base_controller.rb
deleted file mode 100644
index 66f5129f66..0000000000
--- a/app/controllers/decidim/admin/components/base_controller.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module Admin
- module Components
- # This controller is the abstract class from which all component
- # controllers in their admin engines should inherit from.
- class BaseController < Decidim::Admin::ApplicationController
- include Settings
-
- include Decidim::Admin::ParticipatorySpaceAdminContext
- include Decidim::NeedsPermission
- participatory_space_admin_layout
-
- helper Decidim::ResourceHelper
- helper Decidim::Admin::ExportsHelper
- helper Decidim::Admin::ImportsHelper
- helper Decidim::Admin::RemindersHelper
- helper Decidim::Admin::BulkActionsHelper
- helper Decidim::Admin::ResourcePermissionsHelper
-
- helper_method :current_component,
- :current_participatory_space,
- :parent_path
-
- before_action except: [:index, :show] do
- enforce_permission_to :manage, :component, component: current_component unless skip_manage_component_permission
- end
-
- before_action on: [:index, :show] do
- enforce_permission_to :read, :component, component: current_component
- end
-
- def permissions_context
- super.merge(
- current_participatory_space: current_participatory_space,
- participatory_space: current_participatory_space
- )
- end
-
- def permission_class_chain
- [
- current_component.manifest.permissions_class,
- current_participatory_space.manifest.permissions_class,
- Decidim::Admin::Permissions
- ]
- end
-
- def permission_scope
- :admin
- end
-
- def current_component
- request.env["decidim.current_component"]
- end
-
- def current_participatory_space
- current_component.participatory_space
- end
-
- def parent_path
- @parent_path ||= ::Decidim::EngineRouter.admin_proxy(current_participatory_space).components_path
- end
-
- def skip_manage_component_permission
- false
- end
- end
- end
- end
-end
diff --git a/app/controllers/decidim/admin/reminders_controller.rb b/app/controllers/decidim/admin/reminders_controller.rb
deleted file mode 100644
index 57b5065810..0000000000
--- a/app/controllers/decidim/admin/reminders_controller.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module Admin
- class RemindersController < Admin::ApplicationController
- include Decidim::ComponentPathHelper
-
- helper_method :reminder_manifest
-
- def new
- enforce_permission_to :create, :reminder
-
- @form = reminder_form_from_params(name: reminder_manifest.name)
- render :new
- end
-
- def create
- enforce_permission_to :create, :reminder
-
- @form = reminder_form_from_params(params)
-
- command_class.call(@form) do
- on(:ok) do |reminders_queued|
- flash[:notice] = t("decidim.admin.reminders.create.success", count: reminders_queued)
- redirect_to manage_component_path(current_component)
- end
-
- on(:invalid) do
- flash.now[:alert] = t("decidim.admin.reminders.create.error")
- render :new
- end
- end
- end
-
- private
-
- def reminder_form_from_params(params)
- form(reminder_manifest.form_class).from_params(
- params,
- current_component: current_component
- )
- end
-
- def reminder_manifest
- @reminder_manifest ||= Decidim.reminders_registry.for(reminder_name)
- end
-
- def reminder_name
- params[:name]
- end
-
- def command_class
- reminder_manifest.command_class
- end
-
- def current_component
- @current_component ||= current_participatory_space.components.find(params[:component_id])
- end
- end
- end
-end
diff --git a/app/controllers/decidim/assemblies/admin/reminders_controller.rb b/app/controllers/decidim/assemblies/admin/reminders_controller.rb
deleted file mode 100644
index 4c28446e1d..0000000000
--- a/app/controllers/decidim/assemblies/admin/reminders_controller.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module Assemblies
- module Admin
- # This controller allows to send reminders.
- # It is targeted for customizations for reminder things that lives under
- # an assembly.
- class RemindersController < Decidim::Admin::RemindersController
- include Concerns::AssemblyAdmin
- end
- end
- end
-end
diff --git a/app/controllers/decidim/conferences/admin/reminders_controller.rb b/app/controllers/decidim/conferences/admin/reminders_controller.rb
deleted file mode 100644
index c66ed0ab35..0000000000
--- a/app/controllers/decidim/conferences/admin/reminders_controller.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module Conferences
- module Admin
- # This controller allows to send reminders.
- # It is targeted for customizations for reminder things that lives under
- # a conference.
- class RemindersController < Decidim::Admin::RemindersController
- include Concerns::ConferenceAdmin
- end
- end
- end
-end
diff --git a/app/controllers/decidim/participatory_processes/admin/reminders_controller.rb b/app/controllers/decidim/participatory_processes/admin/reminders_controller.rb
deleted file mode 100644
index acab605189..0000000000
--- a/app/controllers/decidim/participatory_processes/admin/reminders_controller.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module ParticipatoryProcesses
- module Admin
- # This controller allows to send reminders.
- # It is targeted for customizations for reminder things that lives under
- # a participatory process.
- class RemindersController < Decidim::Admin::RemindersController
- include Concerns::ParticipatoryProcessAdmin
- end
- end
- end
-end
diff --git a/app/controllers/decidim/proposals/collaborative_drafts_controller.rb b/app/controllers/decidim/proposals/collaborative_drafts_controller.rb
deleted file mode 100644
index 52e77750cf..0000000000
--- a/app/controllers/decidim/proposals/collaborative_drafts_controller.rb
+++ /dev/null
@@ -1,161 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module Proposals
- # Exposes Collaborative Drafts resource so users can view and create them.
- class CollaborativeDraftsController < Decidim::Proposals::ApplicationController
- helper Decidim::WidgetUrlsHelper
- helper ProposalWizardHelper
- helper TooltipHelper
- helper UserGroupHelper
-
- include Decidim::ApplicationHelper
- include FormFactory
- include Flaggable
- include FilterResource
- include CollaborativeOrderable
- include Paginable
-
- helper_method :form_presenter
-
- helper_method :geocoded_collaborative_draft, :collaborative_draft
- before_action :collaborative_drafts_enabled?
- before_action :authenticate_user!, only: [:new, :create]
- before_action :retrieve_collaborative_draft, only: [:show, :edit, :update, :withdraw, :publish]
-
- def index
- @collaborative_drafts = search
- .results
- .not_hidden
- .includes(:category)
- .includes(:scope)
-
- @collaborative_drafts = paginate(@collaborative_drafts)
- @collaborative_drafts = reorder(@collaborative_drafts)
- end
-
- def show
- raise ActionController::RoutingError, "Not Found" unless retrieve_collaborative_draft
-
- @request_access_form = form(RequestAccessToCollaborativeDraftForm).from_params({})
- @accept_request_form = form(AcceptAccessToCollaborativeDraftForm).from_params({})
- @reject_request_form = form(RejectAccessToCollaborativeDraftForm).from_params({})
- end
-
- def new
- enforce_permission_to :create, :collaborative_draft
-
- @form = form(CollaborativeDraftForm).from_params(
- attachment: form(AttachmentForm).from_params({})
- )
- end
-
- def create
- enforce_permission_to :create, :collaborative_draft
- @form = form(CollaborativeDraftForm).from_params(params)
-
- CreateCollaborativeDraft.call(@form, current_user) do
- on(:ok) do |collaborative_draft|
- flash[:notice] = I18n.t("proposals.collaborative_drafts.create.success", scope: "decidim")
-
- redirect_to Decidim::ResourceLocatorPresenter.new(collaborative_draft).path
- end
-
- on(:invalid) do
- flash.now[:alert] = I18n.t("proposals.collaborative_drafts.create.error", scope: "decidim")
- render :new
- end
- end
- end
-
- def edit
- enforce_permission_to :edit, :collaborative_draft, collaborative_draft: @collaborative_draft
-
- @form = form(CollaborativeDraftForm).from_model(@collaborative_draft)
- @form.attachment = form(AttachmentForm).from_model(@collaborative_draft.attachments.first)
- end
-
- def update
- enforce_permission_to :edit, :collaborative_draft, collaborative_draft: @collaborative_draft
-
- @form = form(CollaborativeDraftForm).from_params(params)
- UpdateCollaborativeDraft.call(@form, current_user, @collaborative_draft) do
- on(:ok) do |collaborative_draft|
- flash[:notice] = I18n.t("proposals.collaborative_drafts.update.success", scope: "decidim")
- redirect_to Decidim::ResourceLocatorPresenter.new(collaborative_draft).path
- end
-
- on(:invalid) do
- flash.now[:alert] = I18n.t("proposals.collaborative_drafts.update.error", scope: "decidim")
- render :edit
- end
- end
- end
-
- def withdraw
- WithdrawCollaborativeDraft.call(@collaborative_draft, current_user) do
- on(:ok) do
- flash[:notice] = t("withdraw.success", scope: "decidim.proposals.collaborative_drafts.collaborative_draft")
- end
-
- on(:invalid) do
- flash.now[:alert] = t("withdraw.error", scope: "decidim.proposals.collaborative_drafts.collaborative_draft")
- end
- end
- redirect_to Decidim::ResourceLocatorPresenter.new(@collaborative_draft).path
- end
-
- def publish
- PublishCollaborativeDraft.call(@collaborative_draft, current_user) do
- on(:ok) do |proposal|
- flash[:notice] = I18n.t("publish.success", scope: "decidim.proposals.collaborative_drafts.collaborative_draft")
- redirect_to Decidim::ResourceLocatorPresenter.new(proposal).path
- end
-
- on(:invalid) do
- flash.now[:alert] = t("publish.error", scope: "decidim.proposals.collaborative_drafts.collaborative_draft")
- redirect_to Decidim::ResourceLocatorPresenter.new(@collaborative_draft).path
- end
- end
- end
-
- private
-
- def form_presenter
- @form_presenter ||= present(@form, presenter_class: Decidim::Proposals::CollaborativeDraftPresenter)
- end
-
- def collaborative_drafts_enabled?
- raise ActionController::RoutingError, "Not Found" unless component_settings.collaborative_drafts_enabled?
- end
-
- def retrieve_collaborative_draft
- @collaborative_draft = CollaborativeDraft.not_hidden.where(component: current_component).find_by(id: params[:id])
- end
-
- def geocoded_collaborative_draft
- @geocoded_collaborative_draft ||= search.result.not_hidden.select(&:geocoded_and_valid?)
- end
-
- def search_klass
- CollaborativeDraftSearch
- end
-
- def default_filter_params
- {
- search_text: "",
- category_id: default_filter_category_params,
- state: %w(open),
- scope_id: default_filter_scope_params,
- related_to: ""
- }
- end
-
- def default_filter_category_params
- return unless current_component.participatory_space.categories.any?
-
- ["without"] + current_component.participatory_space.categories.map { |category| category.id.to_s }
- end
- end
- end
-end
diff --git a/app/forms/decidim/budgets/admin/order_reminder_form.rb b/app/forms/decidim/budgets/admin/order_reminder_form.rb
deleted file mode 100644
index 579e1720b1..0000000000
--- a/app/forms/decidim/budgets/admin/order_reminder_form.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module Budgets
- module Admin
- class OrderReminderForm < Decidim::Form
- def reminder_amount
- @reminder_amount ||= begin
- return 0 if !voting_enabled? || voting_ends_soon?
-
- user_ids = []
- unfinished_orders.each do |order|
- reminder = Decidim::Reminder.find_by(component: current_component, user: order.user)
- user_ids << order.user.id if !reminder || (reminder.deliveries.present? && reminder.deliveries.last.created_at < minimum_interval_between_reminders.ago)
- end
- user_ids.uniq.count
- end
- end
-
- def voting_enabled?
- current_component.current_settings.votes == "enabled"
- end
-
- def voting_ends_soon?
- return false unless participatory_space.respond_to? :active_step
- return false if participatory_space.active_step.blank?
-
- time_zone = current_organization.time_zone
- return false if time_zone.blank?
-
- end_time = current_component.participatory_space.active_step[:end_date].in_time_zone(time_zone).end_of_day
-
- Time.current + 6.hours >= end_time
- end
-
- def minimum_interval_between_reminders
- 24.hours
- end
-
- def minimum_time_before_first_reminder
- @minimum_time_before_first_reminder ||= begin
- reminder_manifest = Decidim.reminders_registry.for(:orders)
- return minimum_interval_between_reminders if reminder_manifest.blank?
-
- Array(reminder_manifest.settings.attributes[:reminder_times].default).first
- end
- end
-
- private
-
- def participatory_space
- @participatory_space ||= current_component.participatory_space
- end
-
- def unfinished_orders
- @unfinished_orders ||= Decidim::Budgets::Order.where(
- budget: budgets,
- checked_out_at: nil,
- created_at: Time.zone.at(0)..minimum_time_before_first_reminder.ago
- ).select do |order|
- order.user.email.present?
- end
- end
-
- def budgets
- @budgets ||= Decidim::Budgets::Budget.where(component: current_component)
- end
- end
- end
- end
-end
diff --git a/app/forms/decidim/forms/answer_form.rb b/app/forms/decidim/forms/answer_form.rb
deleted file mode 100644
index 1f7a4aad4e..0000000000
--- a/app/forms/decidim/forms/answer_form.rb
+++ /dev/null
@@ -1,144 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module Forms
- # This class holds a Form to save the questionnaire answers from Decidim's public page
- class AnswerForm < Decidim::Form
- include Decidim::TranslationsHelper
- include Decidim::AttachmentAttributes
-
- attribute :question_id, String
- attribute :body, String
- attribute :choices, Array[AnswerChoiceForm]
- attribute :matrix_choices, Array[AnswerChoiceForm]
-
- attachments_attribute :documents
-
- validates :body, presence: true, if: :mandatory_body?
- validates :selected_choices, presence: true, if: :mandatory_choices?
-
- validate :max_choices, if: -> { question.max_choices }
- validate :all_choices, if: -> { question.question_type == "sorting" }
- validate :min_choices, if: -> { question.matrix? && question.mandatory? }
- validate :documents_present, if: -> { question.question_type == "files" && question.mandatory? }
- validate :max_characters, if: -> { question.max_characters.positive? }
-
- delegate :mandatory_body?, :mandatory_choices?, :matrix?, to: :question
-
- attr_writer :question
-
- def question
- @question ||= Decidim::Forms::Question.find(question_id)
- end
-
- def label(idx)
- base = "#{idx + 1}. #{translated_attribute(question.body)}"
- base += " #{mandatory_label}" if question.mandatory?
- base += " (#{max_choices_label})" if question.max_choices
- base
- end
-
- # Public: Map the correct fields.
- #
- # Returns nothing.
- def map_model(model)
- self.question_id = model.decidim_question_id
- self.question = model.question
-
- self.choices = model.choices.map do |choice|
- AnswerChoiceForm.from_model(choice)
- end
- end
-
- def selected_choices
- choices.select(&:body)
- end
-
- def custom_choices
- choices.select(&:custom_body)
- end
-
- def display_conditions_fulfilled?
- return optional_conditions_fulfilled? unless question.display_conditions.where(mandatory: true).any?
-
- mandatory_conditions_fulfilled?
- end
-
- def mandatory_conditions_fulfilled?
- question.display_conditions.where(mandatory: true).all? do |condition|
- answer = context.responses&.find { |r| r.question_id&.to_i == condition.condition_question.id }
- condition.fulfilled?(answer)
- end
- end
-
- def optional_conditions_fulfilled?
- return true unless question.display_conditions.where(mandatory: false).any?
-
- question.display_conditions.where(mandatory: false).any? do |condition|
- answer = context.responses&.find { |r| r.question_id&.to_i == condition.condition_question.id }
- condition.fulfilled?(answer)
- end
- end
-
- def has_attachments?
- question.has_attachments? && errors[:add_documents].empty? && add_documents.present?
- end
-
- def has_error_in_attachments?
- errors[:add_documents].present?
- end
-
- private
-
- def mandatory_body?
- question.mandatory_body? if display_conditions_fulfilled?
- end
-
- def mandatory_choices?
- question.mandatory_choices? if display_conditions_fulfilled?
- end
-
- def grouped_choices
- selected_choices.group_by(&:matrix_row_id).values
- end
-
- def max_choices
- if matrix?
- errors.add(:choices, :too_many) if grouped_choices.any? { |choices| choices.count > question.max_choices }
- elsif selected_choices.size > question.max_choices
- errors.add(:choices, :too_many)
- end
- end
-
- def max_characters
- if body.present?
- errors.add(:body, :too_long) if body.size > question.max_characters
- elsif custom_choices.any?
- custom_choices.each do |choice|
- errors.add(:body, :too_long) if choice.custom_body.size > question.max_characters
- end
- end
- end
-
- def min_choices
- errors.add(:choices, :missing) if grouped_choices.count != question.matrix_rows.count
- end
-
- def all_choices
- errors.add(:choices, :missing) if selected_choices.size != question.number_of_options
- end
-
- def mandatory_label
- "*"
- end
-
- def max_choices_label
- I18n.t("questionnaires.question.max_choices", scope: "decidim.forms", n: question.max_choices)
- end
-
- def documents_present
- errors.add(:add_documents, :blank) if add_documents.empty? && errors[:add_documents].empty?
- end
- end
- end
-end
diff --git a/app/forms/decidim/forms/questionnaire_form.rb b/app/forms/decidim/forms/questionnaire_form.rb
deleted file mode 100644
index d600c8a664..0000000000
--- a/app/forms/decidim/forms/questionnaire_form.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module Forms
- # This class holds a Form to answer a questionnaire from Decidim's public page.
- class QuestionnaireForm < Decidim::Form
- include ActiveModel::Validations::Callbacks
-
- # as questionnaire uses "answers" for the database relationships is
- # important not to use the same word here to avoid querying all the entries, resulting in a high performance penalty
- attribute :responses, Array[AnswerForm]
- attribute :user_group_id, Integer
- attribute :public_participation, Boolean, default: false
-
- attribute :tos_agreement, Boolean
-
- before_validation :before_validation
-
- validates :tos_agreement, allow_nil: false, acceptance: true
- validate :session_token_in_context
-
- # Private: Create the responses from the questionnaire questions
- #
- # Returns nothing.
- def map_model(model)
- self.responses = model.questions.map do |question|
- AnswerForm.from_model(Decidim::Forms::Answer.new(question: question))
- end
- end
-
- # Add other responses to the context so AnswerForm can validate conditional questions
- def before_validation
- context.responses = attributes[:responses]
- end
-
- # Public: Splits reponses by step, keeping the separator.
- #
- # Returns an array of steps. Each step is a list of the questions in that
- # step, including the separator.
- def responses_by_step
- @responses_by_step ||=
- begin
- steps = responses.chunk_while do |a, b|
- !a.question.separator? || b.question.separator?
- end.to_a
-
- steps = [[]] if steps == []
- steps
- end
- end
-
- def total_steps
- responses_by_step.count
- end
-
- def session_token_in_context
- return if context&.session_token
-
- errors.add(:tos_agreement, I18n.t("activemodel.errors.models.questionnaire.request_invalid"))
- end
- end
- end
-end
diff --git a/app/helpers/decidim/admin/reminders_helper.rb b/app/helpers/decidim/admin/reminders_helper.rb
deleted file mode 100644
index 8963daf4f8..0000000000
--- a/app/helpers/decidim/admin/reminders_helper.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module Admin
- module RemindersHelper
- # Route to the correct reminder for a component.
- def admin_reminders_path(component, options = {})
- EngineRouter.admin_proxy(component.participatory_space).new_component_reminder_path(options.merge(component_id: component))
- end
- end
- end
-end
diff --git a/app/jobs/decidim/budgets/send_vote_reminder_job.rb b/app/jobs/decidim/budgets/send_vote_reminder_job.rb
deleted file mode 100644
index e7a40215b6..0000000000
--- a/app/jobs/decidim/budgets/send_vote_reminder_job.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module Budgets
- class SendVoteReminderJob < ApplicationJob
- queue_as :vote_reminder
-
- def perform(reminder)
- return if reminder.records.where(string: "active").blank?
-
- ::Decidim::ReminderDelivery.create(reminder: reminder)
- ::Decidim::Budgets::VoteReminderMailer.vote_reminder(reminder).deliver_now
- end
- end
- end
-end
diff --git a/app/jobs/decidim/reminder_generator_job.rb b/app/jobs/decidim/reminder_generator_job.rb
deleted file mode 100644
index 53097cc617..0000000000
--- a/app/jobs/decidim/reminder_generator_job.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- class ReminderGeneratorJob < ApplicationJob
- queue_as :reminders
-
- def perform(generator_class_name)
- generator = generator_class_name.constantize.new
- generator.generate
- end
- end
-end
diff --git a/app/jobs/notifications_digest_mail_job.rb b/app/jobs/notifications_digest_mail_job.rb
new file mode 100644
index 0000000000..8408f42b26
--- /dev/null
+++ b/app/jobs/notifications_digest_mail_job.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require "decidim/notifications_digest"
+
+class NotificationsDigestMailJob < ApplicationJob
+ queue_as :scheduled
+
+ def perform(frequency)
+ Decidim::NotificationsDigest.notifications_digest(frequency)
+ end
+end
diff --git a/app/mailers/decidim/budgets/vote_reminder_mailer.rb b/app/mailers/decidim/budgets/vote_reminder_mailer.rb
deleted file mode 100644
index 3a470ac0a5..0000000000
--- a/app/mailers/decidim/budgets/vote_reminder_mailer.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module Budgets
- class VoteReminderMailer < Decidim::ApplicationMailer
- include Decidim::TranslationsHelper
- include Decidim::SanitizeHelper
-
- helper Decidim::TranslationsHelper
-
- helper_method :routes
-
- # Send the user an email reminder to finish voting
- #
- # reminder - the reminder to send.
- def vote_reminder(reminder)
- @reminder = reminder
- @user = reminder.user
- with_user(@user) do
- @orders = reminder.records.where(string: "active").map(&:remindable)
- @organization = @user.organization
-
- subject = I18n.t(
- "decidim.budgets.vote_reminder_mailer.vote_reminder.email_subject",
- count: @orders.count
- )
-
- mail(to: @user.email, subject: subject)
- end
- end
-
- private
-
- def routes
- @routes ||= Decidim::EngineRouter.main_proxy(@reminder.component)
- end
- end
- end
-end
diff --git a/app/models/decidim/reminder.rb b/app/models/decidim/reminder.rb
deleted file mode 100644
index 809fd23875..0000000000
--- a/app/models/decidim/reminder.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- class Reminder < ApplicationRecord
- belongs_to :user, foreign_key: "decidim_user_id", class_name: "Decidim::User"
- belongs_to :component, foreign_key: "decidim_component_id", class_name: "Decidim::Component"
- has_many :records, foreign_key: "decidim_reminder_id", class_name: "Decidim::ReminderRecord", dependent: :destroy
- has_many :deliveries, foreign_key: "decidim_reminder_id", class_name: "Decidim::ReminderDelivery", dependent: :destroy
- end
-end
diff --git a/app/models/decidim/reminder_delivery.rb b/app/models/decidim/reminder_delivery.rb
deleted file mode 100644
index 47a8e84ae6..0000000000
--- a/app/models/decidim/reminder_delivery.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- class ReminderDelivery < ApplicationRecord
- belongs_to :reminder, foreign_key: "decidim_reminder_id", class_name: "Decidim::Reminder"
- end
-end
diff --git a/app/models/decidim/reminder_record.rb b/app/models/decidim/reminder_record.rb
deleted file mode 100644
index 8033290623..0000000000
--- a/app/models/decidim/reminder_record.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- class ReminderRecord < ApplicationRecord
- belongs_to :reminder, foreign_key: "decidim_reminder_id", class_name: "Decidim::Reminder"
- belongs_to :remindable, foreign_type: "remindable_type", polymorphic: true, optional: true
-
- scope :active, -> { where(state: "active") }
- scope :pending, -> { where(state: "pending") }
- scope :completed, -> { where(state: "completed") }
- scope :deleted, -> { where(state: "deleted") }
-
- def active?
- state == "active"
- end
-
- def pending?
- state == "pending"
- end
-
- def completed?
- state == "completed"
- end
-
- def deleted?
- state == "deleted"
- end
- end
-end
diff --git a/app/models/decidim/user.rb b/app/models/decidim/user.rb
deleted file mode 100644
index 86ea8da4b0..0000000000
--- a/app/models/decidim/user.rb
+++ /dev/null
@@ -1,316 +0,0 @@
-# frozen_string_literal: true
-
-require "devise/models/decidim_validatable"
-require "devise/models/decidim_newsletterable"
-require "valid_email2"
-
-module Decidim
- # A User is a participant that wants to join the platform to engage.
- class User < UserBaseEntity
- include Decidim::DataPortability
- include Decidim::Searchable
- include Decidim::ActsAsAuthor
- include Decidim::UserReportable
- include Decidim::Traceable
-
- REGEXP_NICKNAME = /\A[\w\-]+\z/.freeze
-
- class Roles
- def self.all
- Decidim.config.user_roles
- end
- end
-
- devise :invitable, :database_authenticatable, :registerable, :confirmable, :timeoutable,
- :recoverable, :trackable, :lockable,
- :decidim_validatable, :decidim_newsletterable,
- :omniauthable, omniauth_providers: Decidim::OmniauthProvider.available.keys,
- request_keys: [:env], reset_password_keys: [:decidim_organization_id, :email],
- confirmation_keys: [:decidim_organization_id, :email]
- devise :rememberable if Decidim.enable_remember_me
-
- has_many :identities, foreign_key: "decidim_user_id", class_name: "Decidim::Identity", dependent: :destroy
- has_many :memberships, class_name: "Decidim::UserGroupMembership", foreign_key: :decidim_user_id, dependent: :destroy
- has_many :user_groups, through: :memberships, class_name: "Decidim::UserGroup", foreign_key: :decidim_user_group_id
- has_many :access_grants, class_name: "Doorkeeper::AccessGrant", foreign_key: :resource_owner_id, dependent: :destroy
- has_many :access_tokens, class_name: "Doorkeeper::AccessToken", foreign_key: :resource_owner_id, dependent: :destroy
- has_many :reminders, foreign_key: "decidim_user_id", class_name: "Decidim::Reminder", dependent: :destroy
-
- has_one :blocking, class_name: "Decidim::UserBlock", foreign_key: :id, primary_key: :block_id, dependent: :destroy
-
- validates :name, presence: true, unless: -> { deleted? }
- validates :nickname,
- presence: true,
- format: { with: REGEXP_NICKNAME },
- length: { maximum: Decidim::User.nickname_max_length },
- unless: -> { deleted? || managed? }
- validates :locale, inclusion: { in: :available_locales }, allow_blank: true
- validates :tos_agreement, acceptance: true, allow_nil: false, on: :create
- validates :tos_agreement, acceptance: true, if: :user_invited?
- validates :email, :nickname, uniqueness: { scope: :organization }, unless: -> { deleted? || managed? || nickname.blank? }
-
- validate :all_roles_are_valid
-
- has_one_attached :avatar
- validates_upload :avatar, uploader: Decidim::AvatarUploader
-
- has_one_attached :data_portability_file
-
- scope :not_deleted, -> { where(deleted_at: nil) }
-
- scope :managed, -> { where(managed: true) }
- scope :not_managed, -> { where(managed: false) }
-
- scope :officialized, -> { where.not(officialized_at: nil) }
- scope :not_officialized, -> { where(officialized_at: nil) }
-
- scope :confirmed, -> { where.not(confirmed_at: nil) }
- scope :not_confirmed, -> { where(confirmed_at: nil) }
-
- scope :blocked, -> { where(blocked: true) }
- scope :not_blocked, -> { where(blocked: false) }
-
- scope :interested_in_scopes, lambda { |scope_ids|
- actual_ids = scope_ids.select(&:presence)
- if actual_ids.count.positive?
- ids = actual_ids.map(&:to_i).join(",")
- where(Arel.sql("extended_data->'interested_scopes' @> ANY('{#{ids}}')").to_s)
- else
- # Do not apply the scope filter when there are scope ids available. Note
- # that the active record scope must always return an active record
- # collection.
- self
- end
- }
-
- scope :org_admins_except_me, ->(user) { where(organization: user.organization, admin: true).where.not(id: user.id) }
-
- attr_accessor :newsletter_notifications
-
- searchable_fields({
- # scope_id: :decidim_scope_id,
- organization_id: :decidim_organization_id,
- A: :name,
- datetime: :created_at
- },
- index_on_create: ->(user) { !(user.deleted? || user.blocked?) },
- index_on_update: ->(user) { !(user.deleted? || user.blocked?) })
-
- before_save :ensure_encrypted_password
-
- def user_invited?
- invitation_token_changed? && invitation_accepted_at_changed?
- end
-
- # Public: Allows customizing the invitation instruction email content when
- # inviting a user.
- #
- # Returns a String.
- attr_accessor :invitation_instructions
-
- def invitation_pending?
- invited_to_sign_up? && !invitation_accepted?
- end
-
- # Returns the user corresponding to the given +email+ if it exists and has pending invitations,
- # otherwise returns nil.
- def self.has_pending_invitations?(organization_id, email)
- invitation_not_accepted.find_by(decidim_organization_id: organization_id, email: email)
- end
-
- # Returns the presenter for this author, to be used in the views.
- # Required by ActsAsAuthor.
- def presenter
- Decidim::UserPresenter.new(self)
- end
-
- def self.log_presenter_class_for(_log)
- Decidim::AdminLog::UserPresenter
- end
-
- # Checks if the user has the given `role` or not.
- #
- # role - a String or a Symbol that represents the role that is being
- # checked
- #
- # Returns a boolean.
- def role?(role)
- roles.include?(role.to_s)
- end
-
- # Public: Returns the active role of the user
- def active_role
- admin ? "admin" : roles.first
- end
-
- # Public: returns the user's name or the default one
- def name
- super || I18n.t("decidim.anonymous_user")
- end
-
- # Check if the user account has been deleted or not
- def deleted?
- deleted_at.present?
- end
-
- # Public: whether the user has been officialized or not
- def officialized?
- !officialized_at.nil?
- end
-
- def follows?(followable)
- Decidim::Follow.where(user: self, followable: followable).any?
- end
-
- # Public: whether the user accepts direct messages from another
- def accepts_conversation?(user)
- return follows?(user) if direct_message_types == "followed-only"
-
- true
- end
-
- def unread_conversations
- Decidim::Messaging::Conversation.unread_by(self)
- end
-
- def unread_messages_count
- @unread_messages_count ||= Decidim::Messaging::Receipt.unread_count(self)
- end
-
- # Check if the user exists with the given email and the current organization
- #
- # warden_conditions - A hash with the authentication conditions
- # * email - a String that represents user's email.
- # * env - A Hash containing environment variables.
- # Returns a User.
- def self.find_for_authentication(warden_conditions)
- organization = warden_conditions.dig(:env, "decidim.current_organization")
- find_by(
- email: warden_conditions[:email].to_s.downcase,
- decidim_organization_id: organization.id
- )
- end
-
- def self.user_collection(user)
- where(id: user.id)
- end
-
- def self.export_serializer
- Decidim::DataPortabilitySerializers::DataPortabilityUserSerializer
- end
-
- def self.data_portability_images(user)
- user_collection(user).map(&:avatar)
- end
-
- def tos_accepted?
- return true if managed
- return false if accepted_tos_version.nil?
-
- # For some reason, if we don't use `#to_i` here we get some
- # cases where the comparison returns false, but calling `#to_i` returns
- # the same number :/
- accepted_tos_version.to_i >= organization.tos_version.to_i
- end
-
- def admin_terms_accepted?
- return true if admin_terms_accepted_at
- end
-
- # Whether this user can be verified against some authorization or not.
- def verifiable?
- confirmed? || managed? || being_impersonated?
- end
-
- def being_impersonated?
- ImpersonationLog.active.exists?(user: self)
- end
-
- def interested_scopes_ids
- extended_data["interested_scopes"] || []
- end
-
- def interested_scopes
- @interested_scopes ||= organization.scopes.where(id: interested_scopes_ids)
- end
-
- def user_name
- extended_data["user_name"] || name
- end
-
- # return the groups where this user has been accepted
- def accepted_user_groups
- UserGroups::AcceptedUserGroups.for(self)
- end
-
- # return the groups where this user has admin permissions
- def manageable_user_groups
- UserGroups::ManageableUserGroups.for(self)
- end
-
- def authenticatable_salt
- "#{super}#{session_token}"
- end
-
- def invalidate_all_sessions!
- self.session_token = SecureRandom.hex
- save!
- end
-
- ransacker :invitation_accepted_at do
- Arel.sql(%{("decidim_users"."invitation_accepted_at")::text})
- end
-
- ransacker :last_sign_in_at do
- Arel.sql(%{("decidim_users"."last_sign_in_at")::text})
- end
-
- protected
-
- # Overrides devise email required validation.
- # If the user has been deleted or it is managed the email field is not required anymore.
- def email_required?
- return false if deleted? || managed?
-
- super
- end
-
- # Overrides devise password required validation.
- # If the user is managed the password field is not required anymore.
- def password_required?
- return false if managed?
-
- super
- end
-
- def after_confirmation
- return unless organization.send_welcome_notification?
-
- Decidim::EventsManager.publish(
- event: "decidim.events.core.welcome_notification",
- event_class: WelcomeNotificationEvent,
- resource: self,
- affected_users: [self]
- )
- end
-
- private
-
- # Changes default Devise behaviour to use ActiveJob to send async emails.
- def send_devise_notification(notification, *args)
- devise_mailer.send(notification, self, *args).deliver_later
- end
-
- def all_roles_are_valid
- errors.add(:roles, :invalid) unless roles.compact.all? { |role| Roles.all.include?(role) }
- end
-
- def available_locales
- Decidim.available_locales.map(&:to_s)
- end
-
- def ensure_encrypted_password
- restore_encrypted_password! if will_save_change_to_encrypted_password? && encrypted_password.blank?
- end
- end
-end
diff --git a/app/permissions/decidim/admin/permissions.rb b/app/permissions/decidim/admin/permissions.rb
deleted file mode 100644
index c5b88a0c47..0000000000
--- a/app/permissions/decidim/admin/permissions.rb
+++ /dev/null
@@ -1,221 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module Admin
- class Permissions < Decidim::DefaultPermissions
- def permissions
- return permission_action if managed_user_action?
-
- unless permission_action.scope == :admin
- read_admin_dashboard_action?
- return permission_action
- end
-
- unless user
- disallow!
- return permission_action
- end
-
- if user_manager?
- begin
- allow! if user_manager_permissions.allowed?
- rescue Decidim::PermissionAction::PermissionNotSetError
- nil
- end
- end
-
- allow! if user_can_enter_space_area?(require_admin_terms_accepted: true)
-
- read_admin_dashboard_action?
- apply_newsletter_permissions_for_admin!
-
- allow! if permission_action.subject == :global_moderation
-
- if user.admin? && admin_terms_accepted?
- allow! if read_admin_log_action?
- allow! if read_metrics_action?
- allow! if static_page_action?
- allow! if organization_action?
- allow! if templates_action?
- allow! if user_action?
-
- allow! if permission_action.subject == :category
- allow! if permission_action.subject == :component
- allow! if permission_action.subject == :admin_user
- allow! if permission_action.subject == :attachment
- allow! if permission_action.subject == :editor_image
- allow! if permission_action.subject == :attachment_collection
- allow! if permission_action.subject == :scope
- allow! if permission_action.subject == :scope_type
- allow! if permission_action.subject == :area
- allow! if permission_action.subject == :area_type
- allow! if permission_action.subject == :user_group
- allow! if permission_action.subject == :officialization
- allow! if permission_action.subject == :moderate_users
- allow! if permission_action.subject == :authorization
- allow! if permission_action.subject == :authorization_workflow
- allow! if permission_action.subject == :static_page_topic
- allow! if permission_action.subject == :help_sections
- allow! if permission_action.subject == :share_token
- allow! if permission_action.subject == :reminder
- end
-
- permission_action
- end
-
- private
-
- def user_manager?
- user && !user.admin? && user.role?("user_manager")
- end
-
- def templates_action?
- permission_action.subject == :templates &&
- permission_action.action == :read
- end
-
- def read_admin_dashboard_action?
- return unless permission_action.subject == :admin_dashboard &&
- permission_action.action == :read
-
- return user_manager_permissions if user_manager?
-
- toggle_allow(user.admin? || space_allows_admin_access_to_current_action?)
- end
-
- def apply_newsletter_permissions_for_admin!
- return unless admin_terms_accepted?
- return unless permission_action.subject == :newsletter
- return allow! if user.admin?
- return unless space_allows_admin_access?
-
- newsletter = context.fetch(:newsletter, nil)
-
- case permission_action.action
- when :index, :create
- allow!
- when :read, :update, :destroy
- toggle_allow(user == newsletter.author)
- end
- end
-
- def space_allows_admin_access?
- Decidim.participatory_space_manifests.any? do |manifest|
- Decidim.find_participatory_space_manifest(manifest.name)
- .participatory_spaces.call(organization)&.any? do |space|
- space.admins.exists?(id: user.id)
- end
- end
- end
-
- def read_metrics_action?
- permission_action.subject == :metrics &&
- permission_action.action == :read
- end
-
- def read_admin_log_action?
- permission_action.subject == :admin_log &&
- permission_action.action == :read
- end
-
- def static_page_action?
- return unless permission_action.subject == :static_page
-
- static_page = context.fetch(:static_page, nil)
-
- case permission_action.action
- when :update
- static_page.present?
- when :update_slug, :destroy
- static_page.present? && !StaticPage.default?(static_page.slug)
- when :update_notable_changes
- static_page.slug == "terms-and-conditions" && static_page.persisted?
- else
- true
- end
- end
-
- def organization_action?
- return unless permission_action.subject == :organization
- return unless permission_action.action == :update
-
- organization == user.organization
- end
-
- def managed_user_action?
- return unless permission_action.subject == :managed_user
- return user_manager_permissions if user_manager?
- return unless user&.admin?
-
- case permission_action.action
- when :create
- toggle_allow(!organization.available_authorizations.empty?)
- else
- allow!
- end
-
- true
- end
-
- def user_action?
- return unless [:user, :impersonatable_user].include?(permission_action.subject)
-
- subject_user = context.fetch(:user, nil)
-
- case permission_action.action
- when :promote
- subject_user.managed? && Decidim::ImpersonationLog.active.where(admin: user).empty?
- when :impersonate
- available_authorization_handlers? &&
- !subject_user.admin? &&
- subject_user.roles.empty? &&
- Decidim::ImpersonationLog.active.where(admin: user).empty?
- when :destroy
- subject_user != user
- else
- true
- end
- end
-
- def organization
- @organization ||= context.fetch(:organization, nil) || context.fetch(:current_organization, nil)
- end
-
- def user_can_enter_space_area?(**args)
- return unless permission_action.action == :enter &&
- permission_action.subject == :space_area
-
- space_allows_admin_access_to_current_action?(**args)
- end
-
- def space_allows_admin_access_to_current_action?(require_admin_terms_accepted: false)
- Decidim.participatory_space_manifests.any? do |manifest|
- next if manifest.name != :initiatives && require_admin_terms_accepted && !admin_terms_accepted?
-
- new_permission_action = Decidim::PermissionAction.new(
- action: permission_action.action,
- scope: permission_action.scope,
- subject: permission_action.subject
- )
- manifest.permissions_class.new(user, new_permission_action, context).permissions.allowed?
- rescue Decidim::PermissionAction::PermissionNotSetError
- nil
- end
- end
-
- def user_manager_permissions
- Decidim::Admin::UserManagerPermissions.new(user, permission_action, context).permissions
- end
-
- def admin_terms_accepted?
- return unless permission_action.scope == :admin
-
- user&.admin_terms_accepted?
- end
-
- def available_authorization_handlers?
- user.organization.available_authorization_handlers.any?
- end
- end
- end
-end
diff --git a/app/permissions/decidim/budgets/admin/permissions.rb b/app/permissions/decidim/budgets/admin/permissions.rb
deleted file mode 100644
index fb4bf323d1..0000000000
--- a/app/permissions/decidim/budgets/admin/permissions.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module Budgets
- module Admin
- class Permissions < Decidim::DefaultPermissions
- def permissions
- return permission_action if permission_action.scope != :admin
-
- case permission_action.subject
- when :budget
- case permission_action.action
- when :create, :read, :export
- allow!
- when :update
- toggle_allow(budget)
- when :delete, :publish, :unpublish
- toggle_allow(budget && budget.projects.empty?)
- end
- when :project, :projects
- case permission_action.action
- when :create
- permission_action.allow!
- when :import_proposals
- permission_action.allow!
- when :update, :destroy
- permission_action.allow! if project.present?
- end
- when :order
- case permission_action.action
- when :remind
- permission_action.allow!
- end
- end
-
- permission_action
- end
-
- private
-
- def budget
- @budget ||= context.fetch(:budget, nil)
- end
-
- def project
- @project ||= context.fetch(:project, nil)
- end
- end
- end
- end
-end
diff --git a/app/services/decidim/budgets/order_reminder_generator.rb b/app/services/decidim/budgets/order_reminder_generator.rb
deleted file mode 100644
index 98a19601da..0000000000
--- a/app/services/decidim/budgets/order_reminder_generator.rb
+++ /dev/null
@@ -1,98 +0,0 @@
-# frozen_string_literal: true
-
-module Decidim
- module Budgets
- # This class is the generator class which creates and updates order related reminders,
- # after reminder is generated it is send to user who have not checked out his/her/their vote.
- class OrderReminderGenerator
- attr_reader :reminder_jobs_queued
-
- def initialize
- @reminder_manifest = Decidim.reminders_registry.for(:orders)
- @reminder_jobs_queued = 0
- end
-
- # Creates reminders and updates them if they already exists.
- def generate
- Decidim::Component.where(manifest_name: "budgets").each do |component|
- next if component.current_settings.votes != "enabled"
-
- send_reminders(component)
- end
- end
-
- def generate_for(component, &block)
- @alternative_refresh_state = block
- send_reminders(component)
- end
-
- private
-
- attr_reader :reminder_manifest
-
- def send_reminders(component)
- budgets = Decidim::Budgets::Budget.where(component: component)
- pending_orders = Decidim::Budgets::Order.where(budget: budgets, checked_out_at: nil, created_at: Time.zone.at(0)..minimum_time_before_first_reminder.ago)
- users = Decidim::User.where(id: pending_orders.pluck(:decidim_user_id).uniq)
- users.each do |user|
- reminder = Decidim::Reminder.find_or_create_by(user: user, component: component)
- users_pending_orders = pending_orders.where(user: user)
- update_reminder_records(reminder, users_pending_orders)
- if reminder.records.where(string: "active").any? && (reminder.deliveries.blank? || reminder.deliveries.last.created_at < minimum_interval_between_reminders.ago)
- Decidim::Budgets::SendVoteReminderJob.perform_later(reminder)
- @reminder_jobs_queued += 1
- end
- end
- end
-
- def minimum_interval_between_reminders
- 24.hours
- end
-
- def minimum_time_before_first_reminder
- @minimum_time_before_first_reminder ||= begin
- reminder_manifest = Decidim.reminders_registry.for(:orders)
- return minimum_interval_between_reminders if reminder_manifest.blank?
-
- Array(reminder_manifest.settings.attributes[:reminder_times].default).first
- end
- end
-
- def update_reminder_records(reminder, users_pending_orders)
- clean_checked_out_and_deleted_orders(reminder)
- add_pending_orders(reminder, users_pending_orders)
- end
-
- def clean_checked_out_and_deleted_orders(reminder)
- reminder.records.each do |record|
- if record.remindable.nil?
- record.update(state: "deleted")
- elsif record.remindable.checked_out_at.present?
- record.update(state: "completed")
- end
- end
- end
-
- def add_pending_orders(reminder, users_pending_orders)
- reminder.records << users_pending_orders.map { |order| Decidim::ReminderRecord.find_or_create_by(reminder: reminder, remindable: order) }
- return @alternative_refresh_state.call(reminder) if @alternative_refresh_state.present?
-
- reminder.records.each do |record|
- refresh_state(record, reminder.deliveries.length) if %w(active pending).include? record.state
- end
- end
-
- def refresh_state(record, delivered_count)
- intervals = Array(reminder_manifest.settings.attributes[:reminder_times].default)
- return record.update(state: "pending") if delivered_count >= intervals.length
-
- record.state = intervals[delivered_count].ago > record.remindable.created_at ? "active" : "pending"
- record.save if record.changed?
- end
-
- def voting_enabled?(component)
- component.current_settings.votes == "enabled"
- end
- end
- end
-end
diff --git a/app/services/decidim/repair_nickname_service.rb b/app/services/decidim/repair_nickname_service.rb
index 3d447fd806..23a4049069 100644
--- a/app/services/decidim/repair_nickname_service.rb
+++ b/app/services/decidim/repair_nickname_service.rb
@@ -52,10 +52,10 @@ def valid_nickname_for(user)
# Returns the corresponding char
# Else returns nil
def ascii_to_valid_char(id)
- letters = ("A".."Z").to_a.join("").codepoints
- letters += ("a".."z").to_a.join("").codepoints
- digits = ("0".."9").to_a.join("").codepoints
- special_chars = %w(- _).join("").codepoints
+ letters = ("A".."Z").to_a.join.codepoints
+ letters += ("a".."z").to_a.join.codepoints
+ digits = ("0".."9").to_a.join.codepoints
+ special_chars = %w(- _).join.codepoints
valid_ascii_code = letters + digits + special_chars
diff --git a/app/services/decidim/repair_url_in_content_service.rb b/app/services/decidim/repair_url_in_content_service.rb
index f13161ac9d..4a9d609733 100644
--- a/app/services/decidim/repair_url_in_content_service.rb
+++ b/app/services/decidim/repair_url_in_content_service.rb
@@ -100,7 +100,7 @@ def update_each_column(record, columns)
end
def write_attribute(record, column, value)
- if column.try(:name)
+ if column.try(:name) && record.respond_to?(:"#{column.name}=")
record.write_attribute(:"#{column.name}", value)
else
record.instance_variable_set(column, value)
@@ -108,7 +108,7 @@ def write_attribute(record, column, value)
end
def current_content_for(record, column)
- if column.try(:name)
+ if column.try(:name) && record.respond_to?(column.name)
record.send(column.name)
else
record.instance_variable_get(column)
diff --git a/app/views/decidim/admin/reminders/new.html.erb b/app/views/decidim/admin/reminders/new.html.erb
deleted file mode 100644
index f85d88d57c..0000000000
--- a/app/views/decidim/admin/reminders/new.html.erb
+++ /dev/null
@@ -1,21 +0,0 @@
-<%= decidim_form_for(@form, url: component_reminders_path(name: reminder_manifest.name), class: "form grid-container") do |form| %>
-
+About the GraphQL API
+
Decidim comes with an API that follows the GraphQL specification. It has a comprehensive coverage of all the public content that can be found on the website.
+
Currently, it is read-only (except for posting comments) but intends to cover anything that is published on the regular website.
+
Typically (although some particular installations may change that) you will find 3 relevant folders:
+
+-
+
URL/api
The route where to make requests. Request are usually in the POST format.
+-
+
URL/api/docs
This documentation, every Decidim site should provide one.
+-
+
URL/api/graphiql
GraphiQL is a in-browser IDE for exploring GraphQL APIs. Some Decidim installations may choose to remove access to this tool. In that case you can use a standalone version and use any URL/api
as the endpoint
+
+
+Using the GraphQL APi
+
The GraphQL format is a JSON formatted text that is specified in a query. Response is a JSON object as well. For details about specification check the official GraphQL site.
+
For instance, you can check the version of a Decidim installation by using curl
in the terminal:
+
curl -sSH "Content-Type: application/json" \
+-d '{"query": "{ decidim { version } }"}' \
+https://www.decidim.barcelona/api/
+
+
Note that Content-Type
needs to be specified.
+
The query can also be used in GraphiQL, in that case you can skip the "query"
text:
+
{
+ decidim {
+ version
+ }
+}
+
+
Response (formatted) should look something like this:
+
{
+ "data": {
+ "decidim": {
+ "version": "0.18.1"
+ }
+ }
+}
+
+
The most practical way to experiment with GraphQL, however, is just to use the in-browser IDE GraphiQL. It provides access to the documentation and auto-complete (use CTRL-Space) for writing queries.
+
From now on, we will skip the "query" keyword for the purpose of readability. You can skip it too if you are using GraphiQL, if you are querying directly (by using CURL for instance) you will need to include it.
+
+Usage limits
+
Decidim is just a Rails application, meaning that any particular installation may implement custom limits in order to access the API (and the application in general).
+
By default (particular installations may change that), API uses the same limitations as the whole Decidim website, provided by the Gem Rack::Attack. These are 100 maximum requests per minute per IP to prevent DoS attacks
+
+Decidim structure, Types, collections and Polymorphism
+
There are no endpoints in the GraphQL specification, instead objects are organized according to their "Type".
+
These objects can be grouped in a single, complex query. Also, objects may accept parameters, which are "Types" as well.
+
Each "Type" is just a pre-defined structure with fields, or just an Scalar (Strings, Integers, Booleans, ...).
+
For instance, to obtain all the participatory processes in a Decidim installation published since January 2018 and order them by published date, we could execute the next query:
+
{
+ participatoryProcesses(filter: {publishedSince: "2018-01-01"}, order: {publishedAt: "asc"}) {
+ slug
+ title {
+ translation(locale: "en")
+ }
+ }
+}
+
+
Response should look like:
+
{
+ "data": {
+ "participatoryProcesses": [
+ {
+ "slug": "consectetur-at",
+ "title": {
+ "translation": "Soluta consectetur quos fugit aut."
+ }
+ },
+ {
+ "slug": "nostrum-earum",
+ "title": {
+ "translation": "Porro hic ipsam cupiditate reiciendis."
+ }
+ }
+ ]
+ }
+}
+
+
+What happened?
+
In the former query, each keyword represents a type, the words publishedSince
, publishedAt
, slug
, locale
are scalars, all of them Strings.
+
The other keywords however, are objects representing certain entities:
+
+-
+
participatoryProcesses
is a type that represents a collection of participatory spaces. It accepts arguments (filter
and order
), which are other object types as well. slug
and title
are the fields of the participatory process we are interested in, there are "Types" too.
+-
+
filter
is a ParticipatoryProcessFilter* input type, it has several properties that allows us to refine our search. One of them is the publishedSince
property with the initial date from which to list entries.
+-
+
order
is a ParticipatoryProcessSort type, works the same way as the filter but with the goal of ordering the results.
+-
+
title
is a TranslatedField type, which allows us to deal with multi-language fields.
+
+
Finally, note that the returned object is an array, each item of which is a representation of the object we requested.
+
+*About how filters and sorting are organized
+There are two types of objects to filter and ordering collections in Decidim, they all work in a similar fashion. The type involved in filtering always have the suffix "Filter", for ordering it has the suffix "Sort".
+The types used to filter participatory spaces are: ParticipatoryProcessFilter, AssemblyFilter, ConsultationFilter and so on.
+Other collections (or connections) may have their own filters (i.e. ComponentFilter).
+Each filter has its own properties, you should check any object in particular for details. The way they work with multi-languages fields, however, is the same:
+Let's say we have some searchable object with a multi-language field called title, and we have a filter that allows us to search through this field. How should it work? Should we look up content for every language in the field? or should we stick to a specific language?
+In our case, we've decided to search only one particular language of a multi-language field but we let you choose which language to search.
+If no language is specified, the configured as default in the organization will be used. The keyword to specify the language is locale
, and it should be provided in the 2 letters ISO 639-1 format (en = English, es = Spanish, ...).
+Example (this is not a real Decidim query):
+ some_collection(filter: { locale: "en", title: "ideas"}) {
+ id
+ }
+
+The same applies to sorting (ParticipatoryProcessSort, AssemblySort, etc.)
+In this case, the content of the field (title) only allows 2 values: ASC and DESC.
+Example of ordering alphabetically by the title content in French language:
+some_collection(order: { locale: "en", title: "asc"}) {
+ id
+}
+
+Of course, you can combine both filter and order. Also remember to check availability of this type of behaviour for any particular filter/sort.
+
+
+Decidim main types
+
Decidim has 2 main types of objects through which content is provided. These are Participatory Spaces and Components.
+
A participatory space is the first level, currently there are 5 officially supported: Participatory Processes, Assemblies, Consultations, Conferences and Initiatives. For each participatory process there will correspond a collection type and a "single item" type.
+
The previous example uses the collection type for participatory processes. You can try assemblies
, conferences
, consultations
or initiatives
for the others. Note that each collection can implement their own filter and order types with different properties.
+
As an example for a single item query, you can run:
+
{
+ participatoryProcess(slug: "consectetur-at") {
+ slug
+ title {
+ translation(locale: "en")
+ }
+ }
+}
+
+
And the response will be:
+
{
+ "data": {
+ "participatoryProcess": {
+ "slug": "consectetur-at",
+ "title": {
+ "translation": "Soluta consectetur quos fugit aut."
+ }
+ }
+ }
+}
+
+
+What's different?
+
First, note that we are querying, in singular, the type participatoryProcess
, with a different parameter, slug
*, (a String). We can use the id
instead if we know it.
+
Second, the response is not an Array, it is just the object we requested. We can expect to return null
if the object is not found.
+
+* The slug
is a convenient way to find a participatory space as is (usually) in the URL.
+For instance, consider this real case from Barcelona:
+https://www.decidim.barcelona/processes/patrimonigracia
+The word patrimonigracia
indicates the "slug".
+
+
+Components
+
Every participatory space may (and should) have some components. There are 9 official components, these are Proposals
, Page
, Meetings
, Budgets
, Surveys
, Accountability
, Debates
, Sortitions
and Blog
. Plugins may add their own components.
+
If you know the id
* of a specific component you can obtain it by querying it directly:
+
{
+ component(id:2) {
+ id
+ name {
+ translation(locale:"en")
+ }
+ __typename
+ participatorySpace {
+ id
+ type
+ }
+ }
+}
+
+
Response:
+
{
+ "data": {
+ "component": {
+ "id": "2",
+ "name": {
+ "translation": "Meetings"
+ },
+ "__typename": "Meetings",
+ "participatorySpace": {
+ "id": "1",
+ "type": "Decidim::ParticipatoryProcess"
+ }
+ }
+ }
+}
+
+
The process is analogue as what has been explained in the case of searching for one specific participatory process.
+
+*Note that the id
of a component is present also in the URL after the letter "f":
+https://www.decidim.barcelona/processes/patrimonigracia/f/3257/
+In this case, 3257.
+
+
+What about component's collections?
+
Glad you asked, component's collections cannot be retrieved directly, the are available in the context of a participatory space.
+
For instance, we can query all the components in an particular Assembly as follows:
+
{
+ assembly(id: 3) {
+ components {
+ id
+ name {
+ translation(locale: "en")
+ }
+ __typename
+ }
+ }
+}
+
+
The response will be similar to:
+
{
+ "data": {
+ "assembly": {
+ "components": [
+ {
+ "id": "42",
+ "name": {
+ "translation": "Accountability"
+ },
+ "__typename": "Component"
+ },
+ {
+ "id": "38",
+ "name": {
+ "translation": "Meetings"
+ },
+ "__typename": "Meetings"
+ },
+ {
+ "id": "37",
+ "name": {
+ "translation": "Page"
+ },
+ "__typename": "Pages"
+ },
+ {
+ "id": "39",
+ "name": {
+ "translation": "Proposals"
+ },
+ "__typename": "Proposals"
+ }
+ ]
+ }
+ }
+}
+
+
We can also apply some filters by using the ComponentFilter type. In the next query we would like to find all the components with geolocation enabled in the assembly with id=2:
+
{
+ assembly(id: 2) {
+ components(filter: {withGeolocationEnabled: true}) {
+ id
+ name {
+ translation(locale: "en")
+ }
+ __typename
+ }
+ }
+}
+
+
The response:
+
{
+ "data": {
+ "assembly": {
+ "components": [
+ {
+ "id": "39",
+ "name": {
+ "translation": "Meetings"
+ },
+ "__typename": "Meetings"
+ }
+ ]
+ }
+ }
+}
+
+
Note that, in this case, there is only one component returned, "Meetings". In some cases Proposals can be geolocated too therefore would be returned in this query.
+
+Polymorphism and connections
+
Many relationships between tables in Decidim are polymorphic, this means that the related object can belong to different classes and share just a few properties in common.
+
For instance, components in a participatory space are polymorphic, while the concept of component is generic and all of them share properties like published date, name or weight, they differ in the rest. Proposals have the status field while Meetings have an agenda.
+
Another example are the case of linked resources, these are properties that may link objects of different nature between components or participatory spaces.
+
In a very simplified way (to know more please refer to the official guide), GraphQL polymorphism is handled through the operator ... on
. You'll know when a field is polymorphic because the property __typename
, which tells you the type of that particular object, will change accordingly.
+
In the previous examples we've queried for this property:
+
Response fragment:
+
...
+ "components": [
+ {
+ "id": "38",
+ "name": {
+ "translation": "Meetings"
+ },
+ "__typename": "Meetings"
+ }
+...
+
+
So, if we want to access the rest of the properties in a polymorphic object, we should do it through the ... on
operator as follows:
+
{
+ assembly(id: 2) {
+ components {
+ id
+ ... on Proposals {
+
+ }
+ }
+ }
+}
+
+
Consider this query:
+
{
+ assembly(id: 3) {
+ components(filter: {type: "Proposals"}) {
+ id
+ name {
+ translation(locale: "en")
+ }
+ ... on Proposals {
+ proposals(order: {endorsementCount: "desc"}, first: 2) {
+ edges {
+ node {
+ id
+ endorsements {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
The response:
+
{
+ "data": {
+ "assembly": {
+ "components": [
+ {
+ "id": "39",
+ "name": {
+ "translation": "Proposals"
+ },
+ "proposals": {
+ "edges": [
+ {
+ "node": {
+ "id": "35",
+ "endorsements": [
+ {
+ "name": "Ms. Johnathon Schaefer"
+ },
+ {
+ "name": "Linwood Lakin PhD 3 4 endr1"
+ },
+ {
+ "name": "Gracie Emmerich"
+ },
+ {
+ "name": "Randall Rath 3 4 endr3"
+ },
+ {
+ "name": "Jolene Schmitt MD"
+ },
+ {
+ "name": "Clarence Hammes IV 3 4 endr5"
+ },
+ {
+ "name": "Omar Mayer"
+ },
+ {
+ "name": "Raymundo Jaskolski 3 4 endr7"
+ }
+ ]
+ }
+ },
+ {
+ "node": {
+ "id": "33",
+ "endorsements": [
+ {
+ "name": "Spring Brakus"
+ },
+ {
+ "name": "Reiko Simonis IV 3 2 endr1"
+ },
+ {
+ "name": "Dr. Jim Denesik"
+ },
+ {
+ "name": "Dr. Mack Schoen 3 2 endr3"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+}
+
+
+What's going on?
+
Until the ... on Proposals
line, there's nothing new. We are requesting the Assembly participatory space identified by the id=3
, then listing all its components with the type "Proposals". All the components share the id and name properties, so we can just add them at the query.
+
After that, we want content specific from the Proposals type. In order to do that we must tell the server that the content we will request shall only be executed if the types matches Proposals. We do that by wrapping the rest of the query in the ... on Proposals
clause.
+
The next line is just a property of the type Proposals which is a type of collection called a "connection". A connection works similar as normal collection (such as components) but it can handle more complex cases.
+
Typically, a connection is used to paginate long results, for this purpose the results are not directly available but encapsulated inside the list edges in several node results. Also there are more arguments available in order to navigate between pages. This are the arguments:
+
+-
+
first
: Returns the first n elements from the list
+-
+
after
: Returns the elements in the list that come after the specified cursor
+
+-
+
last
: Returns the last n elements from the list
+-
+
before
: Returns the elements in the list that come before the specified cursor
+
+
+
Example:
+
{
+ assembly(id: 3) {
+ components(filter: {type: "Proposals"}) {
+ id
+ name {
+ translation(locale: "en")
+ }
+ ... on Proposals {
+ proposals(first:2,after:"Mg") {
+ pageInfo {
+ endCursor
+ startCursor
+ hasPreviousPage
+ hasNextPage
+ }
+ edges {
+ node {
+ id
+ endorsements {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
Being the response:
+
{
+ "data": {
+ "assembly": {
+ "components": [
+ {
+ "id": "39",
+ "name": {
+ "translation": "Proposals"
+ },
+ "proposals": {
+ "pageInfo": {
+ "endCursor": "NA",
+ "startCursor": "Mw",
+ "hasPreviousPage": false,
+ "hasNextPage": true
+ },
+ "edges": [
+ {
+ "node": {
+ "id": "32",
+ "endorsements": []
+ }
+ },
+ {
+ "node": {
+ "id": "31",
+ "endorsements": [
+ {
+ "name": "Mr. Nicolas Raynor"
+ },
+ {
+ "name": "Gerry Fritsch PhD 3 1 endr1"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+}
+
+
As you can see, a part from the edges list, you can access to the object pageInfo which gives you the information needed to navigate through the different pages.
+
For more info on how connections work, you can check the official guide:
+
https://graphql.org/learn/pagination/
+
+
+
+
diff --git a/app/views/static/api/docs/input_object/categoryfilter/index.html b/app/views/static/api/docs/input_object/categoryfilter/index.html
new file mode 100644
index 0000000000..98184e7912
--- /dev/null
+++ b/app/views/static/api/docs/input_object/categoryfilter/index.html
@@ -0,0 +1,1056 @@
+