diff --git a/Gemfile b/Gemfile index 87ef005..0143936 100644 --- a/Gemfile +++ b/Gemfile @@ -16,6 +16,7 @@ gem "decidim", "~> #{DECIDIM_VERSION}.0" # External Decidim gems gem "decidim-cache_cleaner" gem "decidim-decidim_awesome", git: "https://github.com/octree-gva/decidim-module-decidim_awesome.git", branch: "feat/awesome_decidim_private_fields" +gem "decidim-extra_user_fields", git: "https://github.com/OpenSourcePolitics/decidim-module-extra_user_fields.git", branch: DECIDIM_BRANCH gem "decidim-friendly_signup", git: "https://github.com/OpenSourcePolitics/decidim-module-friendly_signup.git" gem "decidim-spam_detection" gem "decidim-term_customizer", git: "https://github.com/opensourcepolitics/decidim-module-term_customizer.git", branch: "fix/multi-threading-compliant" diff --git a/Gemfile.lock b/Gemfile.lock index a7ef9b0..6193c24 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,13 @@ +GIT + remote: https://github.com/OpenSourcePolitics/decidim-module-extra_user_fields.git + revision: 25f5a7843a38a5f9360f9bd42eb7e246d997bc7b + branch: release/0.27-stable + specs: + decidim-extra_user_fields (0.27.2) + country_select (~> 9.0) + decidim-core (>= 0.27.0, < 0.28) + deface (~> 1.5) + GIT remote: https://github.com/OpenSourcePolitics/decidim-module-friendly_signup.git revision: 1c91e024ae4467c097b39673e5d78b06afd206d1 @@ -198,6 +208,10 @@ GEM commonmarker (0.23.10) concurrent-ruby (1.2.3) connection_pool (2.4.1) + countries (6.0.1) + unaccent (~> 0.3) + country_select (9.0.0) + countries (> 5.0, < 7.0) crack (1.0.0) bigdecimal rexml @@ -882,6 +896,7 @@ GEM uber (0.1.0) uglifier (4.2.0) execjs (>= 0.3.0, < 3) + unaccent (0.4.0) unicode-display_width (2.5.0) uri (0.13.0) valid_email2 (2.3.1) @@ -951,6 +966,7 @@ DEPENDENCIES decidim-cache_cleaner decidim-decidim_awesome! decidim-dev (~> 0.27.0) + decidim-extra_user_fields! decidim-friendly_signup! decidim-spam_detection decidim-term_customizer! diff --git a/app/commands/concerns/decidim/extra_user_fields/create_registrations_commands_overrides.rb b/app/commands/concerns/decidim/extra_user_fields/create_registrations_commands_overrides.rb new file mode 100644 index 0000000..68dfbe5 --- /dev/null +++ b/app/commands/concerns/decidim/extra_user_fields/create_registrations_commands_overrides.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require "active_support/concern" + +module Decidim + module ExtraUserFields + # Changes in methods to store extra fields in user profile + module CreateRegistrationsCommandsOverrides + extend ActiveSupport::Concern + + def call + return broadcast(:invalid) if same_email_representative? + + if form.invalid? + + user = User.has_pending_invitations?(form.current_organization.id, form.email) + user.invite!(user.invited_by) if user + return broadcast(:invalid) + end + + create_user + send_email_to_statutory_representative + + broadcast(:ok, @user) + rescue ActiveRecord::RecordInvalid + broadcast(:invalid) + end + + private + + def create_user + @user = User.create!( + email: form.email, + name: form.name, + nickname: form.nickname, + password: form.password, + password_confirmation: form.password_confirmation, + password_updated_at: Time.current, + organization: form.current_organization, + tos_agreement: form.tos_agreement, + newsletter_notifications_at: form.newsletter_at, + accepted_tos_version: form.current_organization.tos_version, + locale: form.current_locale, + extended_data: extended_data + ) + end + + def extended_data + @extended_data ||= (@user&.extended_data || {}).merge( + country: form.country, + postal_code: form.postal_code, + date_of_birth: form.date_of_birth, + gender: form.gender, + phone_number: form.phone_number, + location: form.location, + underage: form.underage, + statutory_representative_email: form.statutory_representative_email + ) + end + + def send_email_to_statutory_representative + return if form.statutory_representative_email.blank? || !form.underage + + Decidim::ExtraUserFields::StatutoryRepresentativeMailer.inform(@user).deliver_now + end + + def same_email_representative? + return false if form.statutory_representative_email.blank? + + form.statutory_representative_email == form.email + end + end + end +end diff --git a/db/migrate/20240717094815_add_extra_user_fields_to_decidim_organization.decidim_extra_user_fields.rb b/db/migrate/20240717094815_add_extra_user_fields_to_decidim_organization.decidim_extra_user_fields.rb new file mode 100644 index 0000000..6a19d4a --- /dev/null +++ b/db/migrate/20240717094815_add_extra_user_fields_to_decidim_organization.decidim_extra_user_fields.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# This migration comes from decidim_extra_user_fields (originally 20221024121407) + +class AddExtraUserFieldsToDecidimOrganization < ActiveRecord::Migration[6.0] + def up + add_column :decidim_organizations, :extra_user_fields, :jsonb, default: { "enabled" => false } + end + + def down + remove_column :decidim_organizations, :extra_user_fields, :jsonb + end +end diff --git a/db/schema.rb b/db/schema.rb index 88170bf..7365e04 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1091,6 +1091,7 @@ t.string "machine_translation_display_priority", default: "original", null: false t.string "external_domain_whitelist", default: [], array: true t.boolean "enable_participatory_space_filters", default: true + t.jsonb "extra_user_fields", default: {"enabled"=>false} t.index ["host"], name: "index_decidim_organizations_on_host", unique: true t.index ["name"], name: "index_decidim_organizations_on_name", unique: true t.index ["secondary_hosts"], name: "index_decidim_organizations_on_secondary_hosts" diff --git a/spec/commands/decidim/create_registration_spec.rb b/spec/commands/decidim/create_registration_spec.rb new file mode 100644 index 0000000..74224b8 --- /dev/null +++ b/spec/commands/decidim/create_registration_spec.rb @@ -0,0 +1,190 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Comments + describe CreateRegistration do + describe "call" do + let(:organization) { create(:organization) } + + let(:name) { "Username" } + let(:nickname) { "nickname" } + let(:email) { "user@example.org" } + let(:password) { "Y1fERVzL2F" } + let(:password_confirmation) { password } + let(:tos_agreement) { "1" } + let(:current_locale) { "fr" } + let(:country) { "Argentina" } + let(:date_of_birth) { "01/01/2000" } + let(:gender) { "Other" } + let(:location) { "Paris" } + let(:phone_number) { "0123456789" } + let(:postal_code) { "75001" } + let(:newsletter_notifications_at) { nil } + let(:notifications_sending_frequency) { "daily" } + let(:underage) { false } + let(:statutory_representative_email) { nil } + let(:extended_data) do + { + country: country, + date_of_birth: date_of_birth, + gender: gender, + location: location, + phone_number: phone_number, + postal_code: postal_code, + underage: underage, + statutory_representative_email: statutory_representative_email + } + end + + let(:form_params) do + { + "user" => { + "name" => name, + "nickname" => nickname, + "email" => email, + "password" => password, + "password_confirmation" => password_confirmation, + "tos_agreement" => tos_agreement, + "country" => country, + "postal_code" => postal_code, + "date_of_birth" => date_of_birth, + "gender" => gender, + "phone_number" => phone_number, + "location" => location, + "underage" => underage, + "statutory_representative_email" => statutory_representative_email + } + } + end + let(:form) do + RegistrationForm.from_params( + form_params, + current_locale: current_locale + ).with_context( + current_organization: organization + ) + end + let(:command) { described_class.new(form) } + + describe "when the form is not valid" do + before do + allow(form).to receive(:invalid?).and_return(true) + end + + it "broadcasts invalid" do + expect { command.call }.to broadcast(:invalid) + end + + it "doesn't create a user" do + expect do + command.call + end.not_to change(User, :count) + end + + context "when the user was already invited" do + let(:user) { build(:user, email: email, organization: organization) } + + before do + user.invite! + clear_enqueued_jobs + end + + RSpec::Matchers.define_negated_matcher :not_change, :change + + it "receives the invitation email again" do + expect { command.call }.not_to change(User, :count) + + expect do + command.call + user.reload + end.to not_change(User, :count) + .and broadcast(:invalid) + .and change(user.reload, :invitation_token) + + expect(ActionMailer::MailDeliveryJob).to have_been_enqueued.on_queue("mailers").twice + end + end + end + + describe "when the form is valid" do + it "broadcasts ok" do + expect { command.call }.to broadcast(:ok) + end + + it "creates a new user" do + expect(User).to receive(:create!).with( + name: form.name, + nickname: form.nickname, + email: form.email, + password: form.password, + password_confirmation: form.password_confirmation, + password_updated_at: an_instance_of(ActiveSupport::TimeWithZone), + tos_agreement: form.tos_agreement, + organization: organization, + accepted_tos_version: organization.tos_version, + newsletter_notifications_at: newsletter_notifications_at, + locale: form.current_locale, + extended_data: { + country: country, + date_of_birth: Date.parse(date_of_birth), + gender: gender, + location: location, + phone_number: phone_number, + postal_code: postal_code, + underage: underage, + statutory_representative_email: statutory_representative_email + } + ).and_call_original + + expect { command.call }.to change(User, :count).by(1) + end + + it "sets the password_updated_at to the current time" do + expect { command.call }.to broadcast(:ok) + expect(User.last.password_updated_at).to be_between(2.seconds.ago, Time.current) + end + + describe "when user keeps the newsletter unchecked" do + let(:newsletter) { "0" } + + it "creates a user with no newsletter notifications" do + expect do + command.call + expect(User.last.newsletter_notifications_at).to be_nil + end.to change(User, :count).by(1) + end + end + + describe "when the user is underage and sends a valid email" do + let(:underage) { true } + let(:statutory_representative_email) { "user@example.fr" } + + it "creates a user with the statutory representative email and sends email" do + expect do + expect(Decidim::ExtraUserFields::StatutoryRepresentativeMailer).to receive(:inform).with(instance_of(Decidim::User)).and_call_original + + command.call + + user = User.last + expect(user.extended_data["statutory_representative_email"]).to eq(statutory_representative_email) + end.to change(User, :count).by(1) + end + end + + describe "when the user is underage and tries to duplicate email" do + let(:underage) { true } + let(:statutory_representative_email) { email } + + it "broadcasts invalid" do + expect(Decidim::ExtraUserFields::StatutoryRepresentativeMailer).not_to receive(:inform).with(instance_of(Decidim::User)).and_call_original + + expect { command.call }.to broadcast(:invalid) + end + end + end + end + end + end +end