add voting portal (#7)
de huidige opzet hier houdt in dat de tokens van leden voor een vergadering, voorheen alleen gebruikt voor hun persoonlijke links tot de digitale vergadering (die hun naam instelt in BigBlueButton en ze als aanwezig markeerde voor stemmingen), nu ook gebruikt worden voor de link naar de stem portaal. implicatie hiervan is dat Ingang admins zo ook toegang krijgen tot de stem credentials, in plaats van enkel de systeembeheerders met toegang tot de Helios database. waar die kennis iemand wel in staat stelt om namens een ander te stemmen, wordt dit risico in check gehouden doordat dit niet ongemerkt gaat: na het uitbrengen van een stem wordt er naar het bijbehorende email adres een bevestiging gestuurd. het risico bij anderszins kwaadwillenden die de info verkrijgen is hetzelfde; gebruik ervan gaat niet ongemerkt. waar het mogelijk zou zijn om van de bestaande token af te wijken voor dit doeleinde naar een hash niet zichtbaar voor Ingang admins, zou dit ook nadelen hebben. af en toe hebben we leden voor wie de mailtjes uit Ingang niet makkelijk aankomen, in welk geval we nu handmatig een inlog link kunnen doorgeven. mochten de nodige info hiervoor niet meer beschikbaar zijn voor degene die een ALV technisch runt, dan worden juist dit soort situaties weer lastiger. Co-authored-by: Kiara Grouwstra <kiara@bij1.org> Reviewed-on: #7
This commit is contained in:
parent
2082cace93
commit
9d67656f44
|
@ -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
|
||||
|
|
|
@ -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.' }
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
class VotesController < ApplicationController
|
||||
http_basic_authenticate_with name: Rails.application.config.admin_name,
|
||||
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
|
||||
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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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 %>
|
||||
|
|
|
@ -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 %>
|
||||
|
|
|
@ -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]) %>
|
||||
|
|
|
@ -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 %>
|
|
@ -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) %>
|
|
@ -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: ""
|
||||
|
|
|
@ -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") { } %>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
info@bij1.org,mijn-stemming,1,abcdABCD1234
|
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
|||
require 'test_helper'
|
||||
|
||||
class VoteTest < ActiveSupport::TestCase
|
||||
# test "the truth" do
|
||||
# assert true
|
||||
# end
|
||||
end
|
Loading…
Reference in New Issue