diff --git a/app/controllers/consultations_controller.rb b/app/controllers/consultations_controller.rb
index 0c40010..1f4241f 100644
--- a/app/controllers/consultations_controller.rb
+++ b/app/controllers/consultations_controller.rb
@@ -73,7 +73,7 @@ def create_params
end
def update_params
- params.require(:consultation).permit(:title, :description, :status, config: %i[mode ballot])
+ params.require(:consultation).permit(:title, :description, :status, config: %i[mode ballot distribution])
end
def event
diff --git a/app/interactors/cast_votes.rb b/app/interactors/cast_votes.rb
index b22396c..aee616c 100644
--- a/app/interactors/cast_votes.rb
+++ b/app/interactors/cast_votes.rb
@@ -64,13 +64,9 @@ def cast_vote(vote_params)
question = vote_params[:question]
envelope = Envelope.new(question, current_user, **vote_params.to_h.symbolize_keys)
- envelope.save(async: async?)
+ envelope.save_later
envelope.receipt.save!
self.receipts << envelope.receipt
end
-
- def async?
- Rails.configuration.x.asembleo.async_vote
- end
end
diff --git a/app/interactors/create_magic_link.rb b/app/interactors/create_magic_link.rb
index aaed1fd..adc365f 100644
--- a/app/interactors/create_magic_link.rb
+++ b/app/interactors/create_magic_link.rb
@@ -45,6 +45,6 @@ def user
end
def deliver
- SessionsMailer.magic_link_email(email, token).deliver_later
+ SessionsMailer.magic_link_email(email, token.class.name, token.id, token.to_hash, scope: :global).deliver_later
end
end
diff --git a/app/interactors/create_token.rb b/app/interactors/create_token.rb
index 9de0b74..e3a79ab 100644
--- a/app/interactors/create_token.rb
+++ b/app/interactors/create_token.rb
@@ -87,6 +87,7 @@ def issue_receipt
end
def deliver
- SessionsMailer.magic_link_email(identifier, token.reload).deliver_later
+ token.reload
+ SessionsMailer.magic_link_email(identifier, token.class.name, token.id, token.to_hash, scope:).deliver_later
end
end
diff --git a/app/interactors/redirect_by_session.rb b/app/interactors/redirect_by_session.rb
index f33e1dc..6551926 100644
--- a/app/interactors/redirect_by_session.rb
+++ b/app/interactors/redirect_by_session.rb
@@ -32,7 +32,8 @@ def active_question
join_condition = [
receipts[:question_id].eq(questions[:question_id]),
- receipts[:token_id].eq(identity.id)
+ receipts[:voter_type].eq(identity.class.name),
+ receipts[:voter_id].eq(identity.id)
]
where = {
status: :opened,
diff --git a/app/jobs/vote_cast_job.rb b/app/jobs/vote_cast_job.rb
index 27f89b3..c9760af 100644
--- a/app/jobs/vote_cast_job.rb
+++ b/app/jobs/vote_cast_job.rb
@@ -4,11 +4,13 @@ class VoteCastJob < ApplicationJob
queue_as :default
# TODO: Vote hash chaining
- def perform(question_id, token_id, value, created_at)
+ def perform(question_id, token_class, token_id, value, created_at)
question = Question.find(question_id)
- token = Token.find(token_id)
+
+ token_class = token_class.constantize
+ token = token_class.find(token_id)
envelope = Envelope.new(question, token, value:, created_at:)
- envelope.save
+ envelope.save!
end
end
diff --git a/app/mailers/sessions_mailer.rb b/app/mailers/sessions_mailer.rb
index 42b7dfe..95b6d4d 100644
--- a/app/mailers/sessions_mailer.rb
+++ b/app/mailers/sessions_mailer.rb
@@ -1,8 +1,9 @@
# frozen_string_literal: true
class SessionsMailer < ApplicationMailer
- def magic_link_email(to, token)
- @token = token
+ def magic_link_email(to, voter_type, voter_id, voter_hash, scope: :consultation)
+ @scope = scope.to_sym
+ @voter_hash = voter_hash
raise Errors::InvalidEmail unless EmailValidator.valid?(to, mode: :strict)
tag = TokenTag.sent
@@ -14,20 +15,22 @@ def magic_link_email(to, token)
raise e
ensure
- token.tags << tag
- token.save!
+ return unless voter_type == 'Token'
+
+ tag.token_id = voter_id
+ tag.save!
end
private
def template_name
- return 'user_magic_link_email' if @token.scope == 'global'
+ return 'user_magic_link_email' if @scope == :global
'magic_link_email'
end
def subject
- return I18n.t('sessions_mailer.user_magic_link_email.subject') if @token.scope == 'global'
+ return I18n.t('sessions_mailer.user_magic_link_email.subject') if @scope == :global
I18n.t('sessions_mailer.magic_link_email.subject')
end
diff --git a/app/models/envelope.rb b/app/models/envelope.rb
index 727f19a..2a04f89 100644
--- a/app/models/envelope.rb
+++ b/app/models/envelope.rb
@@ -34,24 +34,25 @@ def receipt
return @receipt if defined?(@receipt)
@receipt ||= Receipt.new.tap do |r|
- r.token_id = token.id
+ r.voter = token
r.question = question
r.created_at = created_at
r.fingerprint = FingerprintService.generate(r, token.to_hash, values)
end
end
- def save(async: false)
- if async
- VoteCastJob.perform_later(question.id, token.id, values, receipt.created_at)
- else
- votes.map(&:save!)
- end
+ def save_later
+ VoteCastJob.perform_later(question.id, token.class.name, token.id, values, receipt.created_at)
+ end
+
+ def save!
+ votes.each(&:save!)
end
private
def vote_alias
+ return unless token.is_a?(Token)
return unless consultation.ballot == 'open'
token.on_behalf_of || token.alias || token.to_hash
diff --git a/app/models/question.rb b/app/models/question.rb
index 70d43ce..5f5ac2a 100644
--- a/app/models/question.rb
+++ b/app/models/question.rb
@@ -19,8 +19,8 @@ def valid_option?(value)
options.exists?(value:)
end
- def voted?(token)
- receipts.exists?(token:)
+ def voted?(voter)
+ receipts.exists?(voter:)
end
def short_description
diff --git a/app/models/receipt.rb b/app/models/receipt.rb
index 0294ffd..6c66b7b 100644
--- a/app/models/receipt.rb
+++ b/app/models/receipt.rb
@@ -3,6 +3,6 @@
class Receipt < ApplicationRecord
has_paper_trail
- belongs_to :token
+ belongs_to :voter, polymorphic: true
belongs_to :question
end
diff --git a/app/models/token.rb b/app/models/token.rb
index 3222f6f..eac1b98 100644
--- a/app/models/token.rb
+++ b/app/models/token.rb
@@ -8,7 +8,7 @@ class Token < ApplicationRecord
belongs_to :consultation, optional: true
belongs_to :event, optional: true
- has_many :receipts, dependent: :destroy
+ has_many :receipts, dependent: :destroy, as: :voter
has_many :tags, foreign_key: 'token_id', class_name: 'TokenTag', dependent: :destroy, inverse_of: :token
enum role: { voter: 0, manager: 1, admin: 2 }
diff --git a/app/models/user.rb b/app/models/user.rb
index 55d32b5..a2624ad 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -5,7 +5,7 @@
class User < ApplicationRecord
has_paper_trail
- has_many :receipts, foreign_key: 'token_id', dependent: :destroy
+ has_many :receipts, dependent: :destroy, as: :voter
enum :role, { voter: 0, manager: 1, admin: 2 }, default: :voter
enum :status, { enabled: 1, disabled: 0 }, default: :disabled
diff --git a/app/services/markdown_service.rb b/app/services/markdown_service.rb
index 03c32c8..b3621f9 100644
--- a/app/services/markdown_service.rb
+++ b/app/services/markdown_service.rb
@@ -20,7 +20,10 @@ def self.render(text, plain_text: false)
ActionController::Base.helpers.sanitize(content)
end
- def header(text, _header_level)
- %(#{text})
+ def header(text, header_level)
+ new_header_level = header_level + 2
+ new_header_level = 6 if new_header_level > 6
+
+ %(#{text})
end
end
diff --git a/app/views/consultations/show.html.erb b/app/views/consultations/show.html.erb
index 55a40d5..e3ece4d 100644
--- a/app/views/consultations/show.html.erb
+++ b/app/views/consultations/show.html.erb
@@ -1,5 +1,5 @@
<%= @consultation.title %>
+<%= render @consultation.status %>
<%= markdown @consultation.description %>
-
-<%= render @consultation.status %>
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/views/sessions_mailer/magic_link_email.text.erb b/app/views/sessions_mailer/magic_link_email.text.erb
index 56310f6..428c8fe 100644
--- a/app/views/sessions_mailer/magic_link_email.text.erb
+++ b/app/views/sessions_mailer/magic_link_email.text.erb
@@ -1,8 +1,8 @@
<%= t('application_mailer.greeting') %>,
<%= t('sessions_mailer.magic_link_email.body',
- magic_link_url: magic_link_url(@token.to_hash),
+ magic_link_url: magic_link_url(@voter_hash),
root_url: root_url,
- token: @token).strip %>
+ token: @voter_hash).strip %>
<%= t('application_mailer.complimentary_close', recipient: Rails.configuration.x.asembleo.title).strip %>
\ No newline at end of file
diff --git a/config/application.rb b/config/application.rb
index 193eb85..b235c84 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -33,7 +33,6 @@ class Application < Rails::Application
config.x.asembleo.private_instance = ENV['ASEMBLEO_PRIVATE_INSTANCE'].present?
config.x.asembleo.open_registration = ENV['ASEMBLEO_OPEN_REGISTRATION'].present?
config.x.asembleo.token_alias = ENV['ASEMBLEO_TOKEN_ALIAS'].presence
- config.x.asembleo.async_vote = ActiveModel::Type::Boolean.new.cast(ENV['ASEMBLEO_ASYNC_VOTE'].presence) || false
config.x.asembleo.hide_receipt = ActiveModel::Type::Boolean.new.cast(ENV['ASEMBLEO_HIDE_RECEIPT'].presence) || false
# Branding
diff --git a/config/locales/en.yml b/config/locales/en.yml
index e111a2a..6503f61 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -81,7 +81,7 @@ en:
confirm: Are you sure?
create: Create
edit: Edit
- email: Email
+ email: &email Email
delete: Delete
footer: Asembleo is a free software project developed by Coopanio
log_in: Log in
@@ -105,8 +105,7 @@ en:
consultation_created: Consultation created.
consultation_draft: The consultation is still in draft mode.
consultation_finished: You have answered all the questions.
- consultation_open: The consultation is open, but there are no active questions.
- Try it again later.
+ consultation_open: The consultation is open. Please proceed to vote by clicking next.
consultation_archived: The consultation has been archived.
consultation_closed: The consultation is closed.
consultation_deleted: Consultation deleted.
@@ -316,6 +315,9 @@ en:
session:
identifier: Identifier
password: Password
+ user:
+ email: *email
+ nid: Identity document
events_helper:
open: Open
close: Close
diff --git a/db/migrate/20230421070610_add_voter_to_receipts.rb b/db/migrate/20230421070610_add_voter_to_receipts.rb
new file mode 100644
index 0000000..18bcfda
--- /dev/null
+++ b/db/migrate/20230421070610_add_voter_to_receipts.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddVoterToReceipts < ActiveRecord::Migration[7.0]
+ def change
+ add_reference :receipts, :voter, polymorphic: true, null: true
+ end
+end
diff --git a/db/migrate/20230421070925_backfill_existing_receipts.rb b/db/migrate/20230421070925_backfill_existing_receipts.rb
new file mode 100644
index 0000000..73c9a57
--- /dev/null
+++ b/db/migrate/20230421070925_backfill_existing_receipts.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class BackfillExistingReceipts < ActiveRecord::Migration[7.0]
+ disable_ddl_transaction!
+
+ def change
+ Receipt.unscoped.in_batches do |batch|
+ batch.update_all(voter_type: 'Token', voter_id: Arel.sql('token_id'))
+ end
+ end
+end
diff --git a/db/migrate/20230421071603_remove_token_from_receipts.rb b/db/migrate/20230421071603_remove_token_from_receipts.rb
new file mode 100644
index 0000000..66d10ec
--- /dev/null
+++ b/db/migrate/20230421071603_remove_token_from_receipts.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class RemoveTokenFromReceipts < ActiveRecord::Migration[7.0]
+ def change
+ remove_reference :receipts, :token, null: false, foreign_key: true
+ end
+end
diff --git a/db/migrate/20230421151548_remove_token_and_question_index_from_receipts.rb b/db/migrate/20230421151548_remove_token_and_question_index_from_receipts.rb
new file mode 100644
index 0000000..490d79d
--- /dev/null
+++ b/db/migrate/20230421151548_remove_token_and_question_index_from_receipts.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class RemoveTokenAndQuestionIndexFromReceipts < ActiveRecord::Migration[7.0]
+ def change
+ remove_index :receipts, name: 'index_receipts_on_token_id_and_question_id'
+ end
+end
diff --git a/db/migrate/20230421151747_add_voter_and_question_index_to_receipts.rb b/db/migrate/20230421151747_add_voter_and_question_index_to_receipts.rb
new file mode 100644
index 0000000..dc97c46
--- /dev/null
+++ b/db/migrate/20230421151747_add_voter_and_question_index_to_receipts.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddVoterAndQuestionIndexToReceipts < ActiveRecord::Migration[7.0]
+ def change
+ add_index :receipts, %i[voter_type voter_id question_id], unique: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 906780c..ded5a6b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2023_03_14_232746) do
+ActiveRecord::Schema[7.0].define(version: 2023_04_21_151747) do
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
@@ -116,13 +116,14 @@
create_table "receipts", force: :cascade do |t|
t.text "fingerprint"
- t.integer "token_id", null: false
t.integer "question_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.string "voter_type"
+ t.integer "voter_id"
t.index ["question_id"], name: "index_receipts_on_question_id"
- t.index ["token_id", "question_id"], name: "index_receipts_on_token_id_and_question_id", unique: true
- t.index ["token_id"], name: "index_receipts_on_token_id"
+ t.index ["voter_type", "voter_id", "question_id"], name: "index_receipts_on_voter_type_and_voter_id_and_question_id", unique: true
+ t.index ["voter_type", "voter_id"], name: "index_receipts_on_voter"
end
create_table "token_receipts", id: false, force: :cascade do |t|
@@ -204,7 +205,6 @@
add_foreign_key "question_links", "questions"
add_foreign_key "questions", "consultations"
add_foreign_key "receipts", "questions"
- add_foreign_key "receipts", "tokens"
add_foreign_key "token_receipts", "consultations"
add_foreign_key "token_tags", "tokens"
add_foreign_key "tokens", "consultations"
diff --git a/test/controllers/questions_controller_test.rb b/test/controllers/questions_controller_test.rb
index b57b6b8..a93cdcf 100644
--- a/test/controllers/questions_controller_test.rb
+++ b/test/controllers/questions_controller_test.rb
@@ -18,6 +18,7 @@ class QuestionsControllerTest < ActionDispatch::IntegrationTest
post sessions_url, params: { session: { identifier: token.to_hash } }
post votes_url, params: { vote: { question.id => { value: [value] } }, question_id: question.id }.stringify_keys
+ perform_enqueued_jobs
end
end
@@ -50,6 +51,7 @@ class QuestionsControllerTest < ActionDispatch::IntegrationTest
post sessions_url, params: { session: { identifier: token.to_hash } }
post votes_url, params: { vote: { question.id => { value: [value] } }, question_id: question.id }.stringify_keys
+ perform_enqueued_jobs
end
results = subject
diff --git a/test/controllers/votes_controller_test.rb b/test/controllers/votes_controller_test.rb
index 5df711e..2f30b08 100644
--- a/test/controllers/votes_controller_test.rb
+++ b/test/controllers/votes_controller_test.rb
@@ -20,11 +20,15 @@ class VotesControllerTest < ActionDispatch::IntegrationTest
post sessions_url, params: { session: { identifier: token.to_hash } }
end
- subject { post votes_url, params: @params }
+ subject do
+ post votes_url, params: @params
+ perform_enqueued_jobs
+ end
test 'should create vote' do
subject
+ assert_performed_jobs 1
assert_response :success
Vote.all.tap do |votes|
@@ -33,7 +37,7 @@ class VotesControllerTest < ActionDispatch::IntegrationTest
assert_equal @token.event, votes.first.event
end
- Receipt.find_by(token:, question:).tap do |receipt|
+ Receipt.find_by(voter: token, question:).tap do |receipt|
assert_predicate receipt, :present?
assert receipt.fingerprint.present? && receipt.fingerprint.length == 64
end
@@ -51,7 +55,7 @@ class VotesControllerTest < ActionDispatch::IntegrationTest
assert(votes.map(&:event).all?(@token.event))
end
- Receipt.find_by(token:, question:).tap do |receipt|
+ Receipt.find_by(voter: token, question:).tap do |receipt|
assert_predicate receipt, :present?
assert receipt.fingerprint.present? && receipt.fingerprint.length == 64
end
@@ -100,23 +104,6 @@ class VotesControllerTest < ActionDispatch::IntegrationTest
assert_response :success
end
- test 'should asynchronously create vote' do
- CastVotes.stub_any_instance(:async?, true) do
- subject
- end
-
- perform_enqueued_jobs
-
- assert_performed_jobs 1
-
- Vote.all.tap do |votes|
- assert_equal 1, votes.length
- assert_equal 'yes', votes.first.value
- end
-
- assert_includes response.body, Receipt.first.fingerprint
- end
-
test 'should fail if question_id is unknown' do
@params['vote'] = { '999' => { value: [@value] } }
@params['question_id'] = 999
@@ -125,7 +112,7 @@ class VotesControllerTest < ActionDispatch::IntegrationTest
# Not sure about this, the redirection is caused because not enough questions where found...
assert_response :redirect
assert_empty Vote.all
- assert_not Receipt.exists?(token:, question:)
+ assert_not Receipt.exists?(voter: token, question:)
end
test 'should fail if value is not valid' do
@@ -134,7 +121,7 @@ class VotesControllerTest < ActionDispatch::IntegrationTest
assert_response :redirect
assert_empty Vote.all
- assert_not Receipt.exists?(token:, question:)
+ assert_not Receipt.exists?(voter: token, question:)
end
test 'should fail if a second vote is attempted' do
diff --git a/test/factories.rb b/test/factories.rb
index ae82deb..c34cf80 100644
--- a/test/factories.rb
+++ b/test/factories.rb
@@ -57,7 +57,7 @@
end
factory :receipt do
- token
+ voter { create(:token) }
question
end
end
diff --git a/test/interactors/redirect_by_session_test.rb b/test/interactors/redirect_by_session_test.rb
index 3a7b8d8..4cf092b 100644
--- a/test/interactors/redirect_by_session_test.rb
+++ b/test/interactors/redirect_by_session_test.rb
@@ -51,8 +51,8 @@ class RedirectBySessionTest < ActiveSupport::TestCase
init_questions(status, event)
end
- create(:receipt, token: identity, question: questions.first)
- create(:receipt, token: identity, question: questions.second)
+ create(:receipt, voter: identity, question: questions.first)
+ create(:receipt, voter: identity, question: questions.second)
assert_predicate subject, :present?
assert_equal subject, "/consultations/#{consultation.id}/questions/#{questions.third.id}"
diff --git a/test/mailers/sessions_mailer_test.rb b/test/mailers/sessions_mailer_test.rb
index d4c3e60..7bfda13 100644
--- a/test/mailers/sessions_mailer_test.rb
+++ b/test/mailers/sessions_mailer_test.rb
@@ -6,7 +6,7 @@ class SessionsMailerTest < ActionMailer::TestCase
test 'generate magic link email' do
to = 'user@coopanio.com'
token = create(:token)
- email = SessionsMailer.magic_link_email(to, token)
+ email = SessionsMailer.magic_link_email(to, token.class.name, token.id, token.to_hash)
assert_emails 1 do
email.deliver_now
diff --git a/test/services/fingerprint_service_test.rb b/test/services/fingerprint_service_test.rb
index 0e1a991..3c3b490 100644
--- a/test/services/fingerprint_service_test.rb
+++ b/test/services/fingerprint_service_test.rb
@@ -9,7 +9,7 @@ class FingerprintServiceTest < ActiveSupport::TestCase
Timecop.freeze(Time.utc(2020, 9, 11, 0, 0)) do
@consultation = create(:consultation, id: 1)
@token = create(:token, id: 1, consultation: @consultation, salt: 9999)
- @receipt = create(:receipt, id: nil, token:)
+ @receipt = create(:receipt, id: nil, voter: @token)
end
end
@@ -18,12 +18,12 @@ class FingerprintServiceTest < ActiveSupport::TestCase
test 'generate' do
@short = false
- assert_equal '050893424e6e39c6cecf0ae010185be7af5e971b5ccc2235c68d96218a5a38cd', subject
+ assert_equal 'f513eba3cdcb251c01f5cfd8ccea0e5f4c388dea74f37645cc3e77491c21c2d5', subject
end
test 'generate short' do
@short = true
- assert_equal '050893424e6e39c6cecf', subject
+ assert_equal 'f513eba3cdcb251c01f5', subject
end
end
diff --git a/test/services/markdown_service_test.rb b/test/services/markdown_service_test.rb
index 04589b9..6116c6b 100644
--- a/test/services/markdown_service_test.rb
+++ b/test/services/markdown_service_test.rb
@@ -12,7 +12,7 @@ class MarkdownServiceTest < ActiveSupport::TestCase
test 'render' do
rendered = MarkdownService.render(text)
- assert_equal(%(This is a test), rendered)
+ assert_equal(%(This is a test
), rendered)
end
test 'render plain text' do