add voting portal #7

Merged
kiara merged 17 commits from vote into main 2023-01-27 14:47:10 +00:00
19 changed files with 285 additions and 29 deletions

View File

@ -1,5 +1,5 @@
class MainController < ApplicationController
before_action :set_user_room, only: [:join, :users, :stream]
before_action :set_user_room, only: [:join, :users, :stream, :stemmen]
def index
end
@ -32,10 +32,19 @@ class MainController < ApplicationController
end
end
def stemmen
@votes = Vote.where(room_id: @room.id, user_id: @user.id).order(created_at: :desc)
render :votes
end
private
def set_user_room
@user = User.find_by token: params[:token]
@room = Room.find(@user.room_id)
if @user.nil?
redirect_to 'https://bij1.org/', status: :unauthorized
else
@room = Room.find(@user.room_id)
end
end
end

View File

@ -130,6 +130,9 @@ class UsersController < ApplicationController
# DELETE /users/1
# DELETE /users/1.json
def destroy
Vote.where(user_id: @user.id).each do |vote|
vote.destroy
end
@user.destroy
respond_to do |format|
format.html { redirect_to room_users_url(@user.room_id), notice: 'User was successfully destroyed.' }
@ -141,6 +144,9 @@ class UsersController < ApplicationController
# DELETE /rooms/1/users.json
def destroy_all
@users.each do |user|
Vote.where(user_id: user.id).each do |vote|
vote.destroy
end
user.destroy
end
respond_to do |format|
@ -162,11 +168,12 @@ class UsersController < ApplicationController
require 'csv'
CSV.parse(users_csv, :headers => true) do |row|
users = CSV.parse(users_csv, :headers => true).map { |row|
fields = row.to_hash
fields[:room_id] = room_id
User.create!(fields)
end
fields
}
User.upsert_all(users, unique_by: [:room_id, :email])
respond_to do |format|
format.html { redirect_to room_users_url(room_id), notice: 'Users were successfully created.' }

View File

@ -0,0 +1,81 @@
class VotesController < ApplicationController
http_basic_authenticate_with name: Rails.application.config.admin_name,
Review

opmerking voor later: niet heel handig dat alle admins met hetzelfde account inloggen. hierdoor kan je niet bijhouden wie welke handeling heeft verricht..

opmerking voor later: niet heel handig dat alle admins met hetzelfde account inloggen. hierdoor kan je niet bijhouden wie welke handeling heeft verricht..
Review

eens, is wat het resultaat geweest van meer projecten dan mensen.
de data in dit project is van zeer tijdelijke aard tho, en verwachting tot nu toe is geweest dat deze ivm AVG na de dag van de vergadering weer verwijderd wordt.

eens, is wat het resultaat geweest van meer projecten dan mensen. de data in dit project is van zeer tijdelijke aard tho, en verwachting tot nu toe is geweest dat deze ivm AVG na de dag van de vergadering weer verwijderd wordt.
password: Rails.application.config.admin_password
before_action :set_room, only: [:index, :bulk, :destroy_for_room]
before_action :set_votes, only: [:index, :bulk, :destroy_for_room]
# GET /rooms/:room_id/votes
# GET /rooms/:room_id/votes.json
def index
respond_to do |format|
format.html { render :index }
# let's protect voter credentials
format.json { render json: [], status: :unauthorized }
end
end
# GET /rooms/:room_id/votes/bulk
def bulk
@sample = "info@bij1.org,mijn-stemming,1,abcdABCD1234"
end
# POST /rooms/:room_id/votes/bulk
# POST /rooms/:room_id/votes/bulk.json
def create_bulk
room_id = params[:room_id]
votes_csv = params[:votes_csv]
require 'csv'
headers = %i[
voter_email
short_name
voter_login_id
voter_password
]
votes = CSV.parse(votes_csv, headers: headers).map { |row|
csv_fields = row.to_hash
kiara marked this conversation as resolved
Review

this code probably fails if :user_id and :election_slug combination already exists. catch duplicate exception.

this code probably fails if `:user_id` and `:election_slug` combination already exists. catch duplicate exception.
Review

thanks! looks like your ruby already got better than mine ;), i indeed had yet to test upserts, and it does indeed turn out my db uniqueness constraint won't do here.

i'll try and push an update to address this. got the code now, but seems to use upsert i should still migrate it from sqlite to postgres as well.

thanks! looks like your ruby already got better than mine ;), i indeed had yet to test upserts, and it does indeed turn out my db uniqueness constraint won't do here. i'll try and push an update to address this. got the code now, but seems to use upsert i should still migrate it from sqlite to postgres as well.
Review

i pushed some commits to address this, manually tested to verify this works now.

i pushed some commits to address this, manually tested to verify this works now.
email = csv_fields[:voter_email]
user = User.find_by(room_id: room_id, email: email)
{
:room_id => room_id,
:user_id => user.id,
:election_slug => csv_fields[:short_name],
:voter_login_id => csv_fields[:voter_login_id],
:voter_password => csv_fields[:voter_password],
}
}
Vote.upsert_all(votes, unique_by: [:user_id, :election_slug])
respond_to do |format|
format.html { redirect_to room_users_url(room_id), notice: 'Votes were successfully created.' }
format.json { render :show, status: :created, location: @room }
end
end
# DELETE /rooms/:room_id/votes
# DELETE /rooms/:room_id/votes.json
def destroy_for_room
@votes.each do |vote|
vote.destroy
end
respond_to do |format|
format.html { redirect_to room_users_url(@room.id), notice: 'Votes were successfully destroyed.' }
format.json { render :show, status: :created, location: @room }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_room
@room = Room.find(params[:room_id])
end
def set_votes
@votes = Vote.where(room_id: @room.id)
end
end

View File

@ -6,4 +6,7 @@ class User < ApplicationRecord
attribute :invited, :boolean, default: false
attribute :vote, :boolean, default: true
attribute :proxy, :boolean, default: false
validates :email, uniqueness: { scope: :room_id }
validates :room_id, uniqueness: { scope: :email }
end

6
app/models/vote.rb Normal file
View File

@ -0,0 +1,6 @@
class Vote < ApplicationRecord
belongs_to :user
validates :election_slug, uniqueness: { scope: :user_id }
validates :user_id, uniqueness: { scope: :election_slug }
end

View File

@ -0,0 +1,18 @@
<p id="notice"><%= notice %></p>
<h1>Stemmingen</h1>
<ul>
<% @votes.each do |vote| %>
<li>
<a href="https://stemmen.bij1.org/helios/e/<%= vote.election_slug %>/vote#id_voter_id=<%= vote.voter_login_id %>&id_password=<%= vote.voter_password %>">
<%= vote.election_slug %>
</a>
</li>
<% end %>
</ul>
<br>
<%= link_to 'Stream', user_stream_path %> |
<%= link_to 'Inbellen', join_room_path %> via BigBlueButton (voor toelichtingen)

View File

@ -32,5 +32,7 @@
<%= link_to 'Show Users', room_users_path(@room) %> |
<%= link_to 'Edit', edit_room_path(@room) %> |
<%= link_to 'Import Users', bulk_new_room_users_path(@room), method: :get %> |
<%= link_to 'Import Votes', bulk_new_room_votes_path(@room), method: :get %> |
<%= link_to 'Present Users', room_present_users_path(@room) %> |
<%= link_to 'Back', rooms_path %>

View File

@ -1,7 +1,7 @@
<h1>Bulk Users: <%= @room.name %></h1>
<p>
csv formaat voor bulk import: email,name,moderator,vote,proxy,invited,presence. kolom volgorde maakt niet uit. komma-separated met header.
csv formaat voor bulk import: <code>email,name,moderator,vote,proxy,invited,presence</code>. kolom volgorde maakt niet uit. komma-separated met header.
<% if false %>
download een sample <%= link_to "hier", "/bbb.csv" %>.
<% end %>

View File

@ -50,4 +50,6 @@
<%= link_to 'Present Users', room_present_users_path(id: params[:room_id], v: 1) %> |
<%= link_to 'Absent Users', room_present_users_path(id: params[:room_id], v: 0) %> |
<%= link_to 'Destroy all', destroy_room_users_path(params[:room_id]), method: :delete %> |
<%= link_to 'Import Votes', bulk_new_room_votes_path(params[:room_id]), method: :get %> |
<%= link_to 'Destroy Votes', room_votes_path(params[:room_id]), method: :delete %> |
<%= link_to 'Back', room_path(params[:room_id]) %>

View File

@ -0,0 +1,46 @@
<h1>Bulk Votes: <%= @room.name %></h1>
<p>
csv formaat voor bulk import, in die volgorde, komma-separated en zonder header: <code>voter_email,short_name,voter_login_id,voter_password</code>
<% if false %>
download een sample <%= link_to "hier", "/votes.csv" %>.
<% end %>
</p>
<ul>
<li><code>voter_email</code> is het email adres van de stemmer, om de credentials terug te koppelen aan het juiste lid.</li>
<li><code>short_name</code> is de naam van de stemming</li>
<li><code>voter_login_id</code> is de login id van de stemmer in het stem systeem</li>
<li><code>voter_password</code> is het wachtwoord van de stemmer in het stem systeem</li>
</ul>
<p>
De gegevens hiervoor zijn te verkrijgen via commando, gegeven SSH toegang tot de database server:
</p>
<ul>
<li>
<code>ssh db.bij1.net "sudo psql -U postgres helios -c \"SELECT voter_email, short_name, voter_login_id, voter_password FROM helios_voter JOIN helios_election ON election_id = helios_election.id WHERE helios_election.uuid IN ('UUID1', 'UUID2', ...);\" -tAF,"</code>
</li>
</ul>
<p>
... waar <code>'UUID1', 'UUID2', ...</code> vervangen dient te worden door de UUIDs van de stemmingen van deze kamer, in de URLs te vinden in <a href="https://stemmen.bij1.org/helios/elections/administered">Helios</a>.
</p>
<p>
Gebruikers vinden vervolgens hun stem links op de volgende URL, waar <code>TOKEN</code> dient te worden vervangen door hun Ingang token:
</p>
<ul>
<li>
<code>https://vergadering.bij1.org/ingang/TOKEN/stemmen</code>
</li>
</ul>
<%= form_with(local: true) do |form| %>
<div class="field">
<%= form.text_area :votes_csv, value: @sample %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<%= link_to 'Back', room_users_path %>
<% end %>

View File

@ -0,0 +1,29 @@
<p id="notice"><%= notice %></p>
<h1>Votes: <%= @room.name %></h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>User ID</th>
<th>Election Slug</th>
<th>Login ID</th>
</tr>
</thead>
<tbody>
<% @votes.each do |vote| %>
<tr>
<td><%= vote.id %></td>
<td><%= vote.user_id %></td>
<td><%= vote.election_slug %></td>
<td><%= vote.voter_login_id %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'Back', room_path(@room) %>

View File

@ -1,4 +1,8 @@
RAILS_LOG_TO_STDOUT: 'any value will do, it just cares that this env var is present'
SMTP_USERNAME: ""
SMTP_PASSWORD: ""
INGANG_ADMIN_PASSWORD: ""
BIGBLUEBUTTON_SECRET: ""
DATABASE_PASSWORD_PRODUCTION: ""
DATABASE_PASSWORD_TEST: ""
DATABASE_PASSWORD_DEV: ""

View File

@ -1,35 +1,29 @@
# SQLite. Versions 3.8.0 and up are supported.
# gem install sqlite3
#
# Ensure the SQLite 3 gem is defined in your Gemfile
# gem 'sqlite3'
#
default: &default
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
# upsert does not work with sqlite
adapter: postgresql
encoding: unicode
host: db.internal.bij1.net
port: 5432
development:
<<: *default
adapter: sqlite3
database: db/development.sqlite3
database: ingang_dev
username: ingang_dev
password: <%= ENV.fetch("DATABASE_PASSWORD_DEV") { } %>
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
<<: *default
adapter: sqlite3
database: db/test.sqlite3
database: ingang_test
username: ingang_test
password: <%= ENV.fetch("DATABASE_PASSWORD_TEST") { } %>
production:
<<: *default
adapter: sqlite3
database: db/production.sqlite3
# adapter: postgresql
# encoding: unicode
# database: ingang
# username: ingang
# password: <%= ENV.fetch("DATABASE_PASSWORD") { } %>
# host: db.internal.bij1.net
# port: 5432
database: ingang
username: ingang
password: <%= ENV.fetch("DATABASE_PASSWORD_PRODUCTION") { } %>

View File

@ -1,18 +1,24 @@
Rails.application.routes.draw do
resources :rooms do
resources :users, shallow: true
resources :rooms, shallow: true do
resources :users do
# resources :votes
end
end
get 'rooms/:room_id/users/bulk', to: 'users#bulk', as: 'bulk_new_room_users'
post 'rooms/:room_id/users/bulk', to: 'users#create_bulk', as: 'bulk_create_room_users'
post 'rooms/:room_id/users/:id/test_invite', to: 'users#test_invite', as: 'test_invite_user'
post 'rooms/:room_id/users/invite', to: 'users#invite', as: 'invite_room_users'
delete 'rooms/:room_id/users', to: 'users#destroy_all', as: 'destroy_room_users'
delete 'rooms/:room_id/votes', to: 'votes#destroy_for_room', as: 'room_votes'
delete 'rooms/:room_id/users/invite', to: 'users#uninvite', as: 'uninvite_room_users'
post 'rooms/:room_id/users/mark_invited', to: 'users#mark_invited', as: 'mark_invited_room_users'
post 'rooms/:room_id/users/mark_presence', to: 'users#mark_presence', as: 'mark_presence_room_users'
get 'rooms/:room_id/votes/bulk', to: 'votes#bulk', as: 'bulk_new_room_votes'
post 'rooms/:room_id/votes/bulk', to: 'votes#create_bulk', as: 'bulk_create_room_votes'
get 'rooms/:id/voters.csv', to: 'rooms#voters', as: 'room_export_voters'
get 'rooms/:id/aanwezig.csv', to: 'rooms#present', as: 'room_present_users'
get ':token/stream', to: 'main#stream'
get ':token/stemmen', to: 'main#stemmen', as: 'user_elections'
get ':token/stream', to: 'main#stream', as: 'user_stream'
get ':token', to: 'main#join', as: 'join_room'
root 'main#index'
end

View File

@ -0,0 +1,16 @@
class CreateVotes < ActiveRecord::Migration[6.0]
def change
create_table :votes do |t|
t.references :room, null: false, foreign_key: true
t.references :user, null: false, foreign_key: true
t.string :election_slug
t.string :voter_login_id
t.string :voter_password
t.timestamps
end
change_column_default :votes, :created_at, from: nil, to: ->{ 'now()' }
change_column_default :votes, :updated_at, from: nil, to: ->{ 'now()' }
add_index :votes, [:user_id, :election_slug], unique: true
end
end

View File

@ -0,0 +1,8 @@
class UpsertableUsers < ActiveRecord::Migration[6.0]
def change
change_column_default :users, :created_at, from: nil, to: ->{ 'now()' }
change_column_default :users, :updated_at, from: nil, to: ->{ 'now()' }
add_index :users, [:room_id, :email], unique: true
end
end

1
public/votes.csv Normal file
View File

@ -0,0 +1 @@
info@bij1.org,mijn-stemming,1,abcdABCD1234
1 info@bij1.org mijn-stemming 1 abcdABCD1234

17
test/fixtures/votes.yml vendored Normal file
View File

@ -0,0 +1,17 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
id:
room_id: 1
user_id: 1
election_slug: MyString
voter_login_id: MyString
voter_password: MyString
two:
id:
room_id: 1
user_id: 1
election_slug: MyString
voter_login_id: MyString
voter_password: MyString

7
test/models/vote_test.rb Normal file
View File

@ -0,0 +1,7 @@
require 'test_helper'
class VoteTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end