diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index 916eb55..8409009 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -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 diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 47a43fe..b937c1f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -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.' } diff --git a/app/controllers/votes_controller.rb b/app/controllers/votes_controller.rb new file mode 100644 index 0000000..ef34536 --- /dev/null +++ b/app/controllers/votes_controller.rb @@ -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 diff --git a/app/models/user.rb b/app/models/user.rb index 47e474e..a95f14f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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 diff --git a/app/models/vote.rb b/app/models/vote.rb new file mode 100644 index 0000000..bafd553 --- /dev/null +++ b/app/models/vote.rb @@ -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 diff --git a/app/views/main/votes.html.erb b/app/views/main/votes.html.erb new file mode 100644 index 0000000..fdd44c3 --- /dev/null +++ b/app/views/main/votes.html.erb @@ -0,0 +1,18 @@ +

<%= notice %>

+ +

Stemmingen

+ + + +
+ +<%= link_to 'Stream', user_stream_path %> | +<%= link_to 'Inbellen', join_room_path %> via BigBlueButton (voor toelichtingen) diff --git a/app/views/rooms/show.html.erb b/app/views/rooms/show.html.erb index 3b1cbc1..f155440 100644 --- a/app/views/rooms/show.html.erb +++ b/app/views/rooms/show.html.erb @@ -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 %> diff --git a/app/views/users/bulk.html.erb b/app/views/users/bulk.html.erb index 18efb11..85c5e17 100644 --- a/app/views/users/bulk.html.erb +++ b/app/views/users/bulk.html.erb @@ -1,7 +1,7 @@

Bulk Users: <%= @room.name %>

-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: email,name,moderator,vote,proxy,invited,presence. kolom volgorde maakt niet uit. komma-separated met header. <% if false %> download een sample <%= link_to "hier", "/bbb.csv" %>. <% end %> diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 280828e..44a8eab 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -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]) %> diff --git a/app/views/votes/bulk.html.erb b/app/views/votes/bulk.html.erb new file mode 100644 index 0000000..17f7718 --- /dev/null +++ b/app/views/votes/bulk.html.erb @@ -0,0 +1,46 @@ +

Bulk Votes: <%= @room.name %>

+ +

+csv formaat voor bulk import, in die volgorde, komma-separated en zonder header: voter_email,short_name,voter_login_id,voter_password +<% if false %> +download een sample <%= link_to "hier", "/votes.csv" %>. +<% end %> +

+ + + +

+De gegevens hiervoor zijn te verkrijgen via commando, gegeven SSH toegang tot de database server: +

+ +

+... waar 'UUID1', 'UUID2', ... vervangen dient te worden door de UUIDs van de stemmingen van deze kamer, in de URLs te vinden in Helios. +

+

+Gebruikers vinden vervolgens hun stem links op de volgende URL, waar TOKEN dient te worden vervangen door hun Ingang token: +

+ + +<%= form_with(local: true) do |form| %> +
+ <%= form.text_area :votes_csv, value: @sample %> +
+ +
+ <%= form.submit %> +
+ <%= link_to 'Back', room_users_path %> +<% end %> diff --git a/app/views/votes/index.html.erb b/app/views/votes/index.html.erb new file mode 100644 index 0000000..332155c --- /dev/null +++ b/app/views/votes/index.html.erb @@ -0,0 +1,29 @@ +

<%= notice %>

+ +

Votes: <%= @room.name %>

+ + + + + + + + + + + + + <% @votes.each do |vote| %> + + + + + + + <% end %> + +
IDUser IDElection SlugLogin ID
<%= vote.id %><%= vote.user_id %><%= vote.election_slug %><%= vote.voter_login_id %>
+ +
+ +<%= link_to 'Back', room_path(@room) %> diff --git a/config/application.yml.bck b/config/application.yml.bck index 0454273..c6cb762 100644 --- a/config/application.yml.bck +++ b/config/application.yml.bck @@ -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: "" diff --git a/config/database.yml b/config/database.yml index 316f61e..857c61c 100644 --- a/config/database.yml +++ b/config/database.yml @@ -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") { } %> diff --git a/config/routes.rb b/config/routes.rb index ab0be58..483ce86 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 diff --git a/db/migrate/20230123193409_create_votes.rb b/db/migrate/20230123193409_create_votes.rb new file mode 100644 index 0000000..ca195d1 --- /dev/null +++ b/db/migrate/20230123193409_create_votes.rb @@ -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 diff --git a/db/migrate/20230127133700_upsertable_users.rb b/db/migrate/20230127133700_upsertable_users.rb new file mode 100644 index 0000000..01286f7 --- /dev/null +++ b/db/migrate/20230127133700_upsertable_users.rb @@ -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 + \ No newline at end of file diff --git a/public/votes.csv b/public/votes.csv new file mode 100644 index 0000000..9038b0f --- /dev/null +++ b/public/votes.csv @@ -0,0 +1 @@ +info@bij1.org,mijn-stemming,1,abcdABCD1234 \ No newline at end of file diff --git a/test/fixtures/votes.yml b/test/fixtures/votes.yml new file mode 100644 index 0000000..7062be5 --- /dev/null +++ b/test/fixtures/votes.yml @@ -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 diff --git a/test/models/vote_test.rb b/test/models/vote_test.rb new file mode 100644 index 0000000..f31f992 --- /dev/null +++ b/test/models/vote_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class VoteTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end