Browse Source

initial commit

 Author:    hmt <dev@hmt.im>
 Date:      Tue Sep 13 18:39:28 2016 +0200
hmt 8 years ago
commit
a4e4cd531d

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+config/*.y*
+.dropbox
+.sass-cache
+log
+test.db

+ 24 - 0
Gemfile

@@ -0,0 +1,24 @@
+source 'https://rubygems.org'
+gem 'hashids'
+gem 'sequel'
+gem 'pg'
+gem 'sinatra'
+gem 'slim'
+gem 'sass'
+gem 'puma'
+gem 'envyable'
+gem 'pry'
+gem 'newrelic_rpm'
+
+group :test do
+  gem 'minitest'
+  gem 'minitest-rg'
+  gem 'rack-test'
+  gem 'rake'
+  gem 'sqlite3'
+end
+
+group :development do
+  gem 'pry'
+end
+

+ 21 - 0
LICENSE.md

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 HMT
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 17 - 0
Rakefile

@@ -0,0 +1,17 @@
+require 'rake/testtask'
+require "sqlite3"
+
+task :default => [:test]
+
+Rake::TestTask.new do |t|
+  require "sequel"
+  Sequel.extension :migration
+  db = Sequel.connect("sqlite://test.db")
+  Sequel::Migrator.run(db, "migrations")
+  t.test_files = FileList['specs/*_spec.rb']
+  t.verbose = false
+  if t.respond_to?(:warning=)
+    t.warning = false
+  end
+end
+

+ 3 - 0
config.ru

@@ -0,0 +1,3 @@
+require './sv'
+app_stack = [SV.new]
+run Rack::Cascade.new (app_stack)

+ 13 - 0
migrations/01_init.rb

@@ -0,0 +1,13 @@
+Sequel.migration do
+  change do
+    create_table(:schueler) do
+      String :hashid, :unique => true, :primary_key => true
+      String :vorname
+      String :klasse
+    end
+    create_table(:lehrer) do
+      primary_key :id
+      String :name
+    end
+  end
+end

+ 6 - 0
migrations/02_geschlecht.rb

@@ -0,0 +1,6 @@
+Sequel.migration do
+  change do
+    add_column :schueler, :geschlecht, Integer
+  end
+end
+

+ 7 - 0
migrations/03_info.rb

@@ -0,0 +1,7 @@
+Sequel.migration do
+  change do
+    add_column :schueler, :info, Integer
+  end
+end
+
+

+ 11 - 0
migrations/04_infos.rb

@@ -0,0 +1,11 @@
+Sequel.migration do
+  change do
+    create_table(:infos) do
+      primary_key :id
+      Integer :projekt_id
+      String :text
+      Integer :timestamp
+    end
+  end
+end
+

+ 7 - 0
migrations/05_email.rb

@@ -0,0 +1,7 @@
+Sequel.migration do
+  change do
+    add_column :schueler, :email, String
+  end
+end
+
+

+ 9 - 0
migrations/06_wahlfelder.rb

@@ -0,0 +1,9 @@
+Sequel.migration do
+  change do
+    add_column :schueler, :aktiv_wahl, Integer
+    # 0 ist nicht aktiv, 1 schülersprecher, 2 schuko
+    set_column_default :schueler, :aktiv_wahl, 0
+  end
+end
+
+

+ 8 - 0
migrations/07_info_type_change.rb

@@ -0,0 +1,8 @@
+Sequel.migration do
+  change do
+    set_column_type :schueler, :info, String
+  end
+end
+
+
+

+ 8 - 0
migrations/08_checkbox.rb

@@ -0,0 +1,8 @@
+Sequel.migration do
+  change do
+    drop_column :schueler, :aktiv_wahl
+    add_column :schueler, :sprecher, TrueClass
+    add_column :schueler, :schuko, TrueClass
+  end
+end
+

+ 5 - 0
migrations/09_nachname.rb

@@ -0,0 +1,5 @@
+Sequel.migration do
+  change do
+    add_column :schueler, :nachname, String
+  end
+end

+ 18 - 0
migrations/10_schueler_wahl.rb

@@ -0,0 +1,18 @@
+Sequel.migration do
+  change do
+    add_column :schueler, :gewaehlt, TrueClass
+    create_table(:sprecher) do
+      primary_key :id
+      Integer :sprecher_id
+    end
+    create_table(:schuko) do
+      primary_key :id
+      Integer :schuko_id
+    end
+    create_table(:verbindungslehrer) do
+      primary_key :id
+      Integer :lehrer_id
+    end
+  end
+end
+

+ 7 - 0
migrations/11_schueler_id_type.rb

@@ -0,0 +1,7 @@
+Sequel.migration do
+  change do
+    set_column_type :sprecher, :sprecher_id, String
+    set_column_type :schuko, :schuko_id, String
+  end
+end
+

File diff suppressed because it is too large
+ 4 - 0
public/assets/bootstrap/3.3.5/bootstrap.min.css


+ 250 - 0
specs/route_spec.rb

@@ -0,0 +1,250 @@
+require "#{File.dirname(__FILE__)}/spec_helper"
+include SpecHelper
+
+describe "routes" do
+  describe 'basic auth' do
+    it "ohne auth" do
+      get '/info'
+      last_response.status.must_equal 401
+    end
+    it "falsche auth" do
+      authorize 'oh', 'ah'
+      get '/info'
+      last_response.status.must_equal 401
+    end
+    it "korrekte auth" do
+      authorize 'test', 'test'
+      get '/info'
+      last_response.status.must_equal 200
+    end
+  end
+
+  describe "Views ok" do
+    # before do
+    #   authorize 'test', 'test'
+    # end
+
+    it "gibt home-view 200 zurück" do
+      get '/'
+      last_response.status.must_equal 200
+    end
+  end
+end
+
+describe "verlauf schüler" do
+  before do
+    Lehrer.create(:id => 1, :name => "Müller")
+    Lehrer.create(:id => 2, :name => "Maier")
+    Lehrer.create(:id => 3, :name => "Walter")
+  end
+
+  describe "schüler besucht seite zur aktiven Wahl" do
+    before do
+      Schueler.create(:hashid => "a", :vorname => "A", :klasse => "A1")
+      Schueler.create(:hashid => "b", :vorname => "B", :klasse => "B1")
+    end
+
+    it "schüler gibt code auf startseite ein und wird umgeleitet" do
+      Time.stub :now, Time.parse("Sep 21, 2016, 17:00") do
+        post "/hashid", params={:hashid => "a"}
+        follow_redirect!
+        last_response.body.must_include "Hallo A,"
+      end
+    end
+
+    it "schüler stellt sich auf als schuko" do
+      Time.stub :now, Time.parse("Sep 21, 2016, 17:00") do
+        post "/wahl", params={:hashid => "a",:schuko => "on"}
+        a = Schueler["a"]
+        a.schuko.must_equal true
+        a.sprecher.must_be_nil
+      end
+    end
+
+    it "schüler stellt sich auf als sprecher" do
+      Time.stub :now, Time.parse("Sep 21, 2016, 17:00") do
+        post "/wahl", params={:hashid => "a",:sprecher => "on"}
+        a = Schueler["a"]
+        a.sprecher.must_equal true
+        a.schuko.must_be_nil
+      end
+    end
+
+    it "schüler stellt sich auf als sprecher und ändert Daten" do
+      Time.stub :now, Time.parse("Sep 21, 2016, 17:00") do
+        post "/wahl", params={:hashid => "a",:sprecher => "on"}
+        post "/wahl", params={:hashid => "a",:sprecher => "", :schuko => "on", :email => "xyz"}
+        a = Schueler["a"]
+        a.sprecher.must_be_nil
+        a.schuko.must_equal true
+        a.email.must_equal "xyz"
+      end
+    end
+
+    it "schüler stellt sich auf als sprecher und schuko" do
+      Time.stub :now, Time.parse("Sep 21, 2016, 17:00") do
+        post "/wahl", params={:hashid => "a", :sprecher => "on", :schuko => "on"}
+        a = Schueler["a"]
+        a.schuko.must_equal true
+        a.sprecher.must_equal true
+      end
+    end
+
+    it "schüler stellt sich auf als schuko und gibt Infos" do
+      Time.stub :now, Time.parse("Sep 21, 2016, 17:00") do
+        post "/wahl", params={:hashid => "a",:schuko => "on", :nachname => "AA7", :info => "Bin hier"}
+        get "/a"
+        last_response.body.must_include "AA7"
+        last_response.body.must_include "Bin hier"
+      end
+    end
+
+    after do
+      Schueler.dataset.destroy
+    end
+  end
+
+  describe "schüler besucht seite zur passiven Wahl" do
+    before do
+      Schueler.create(:hashid => "a", :vorname => "A", :klasse => "A1", :schuko => true, :sprecher => true)
+      Schueler.create(:hashid => "b", :vorname => "B", :klasse => "B1", :schuko => true, :sprecher => true)
+      Schueler.create(:hashid => "c", :schuko => true)
+      %w(d e f g h i j k l).each do |s|
+        Schueler.create(:hashid => s, :sprecher => true)
+      end
+    end
+
+    it "schüler gibt code auf startseite ein und wird umgeleitet" do
+      Time.stub :now, Time.parse("Oct 1, 2016, 17:00") do
+      post "/hashid", params={:hashid => "a"}
+      follow_redirect!
+      last_response.body.must_include "Hallo A,"
+      end
+    end
+
+    it "schüler will sich als schuko aufstellen nach wahlbeginn" do
+      Time.stub :now, Time.parse("Oct 1, 2016, 17:00") do
+        post "/wahl", params={:hashid => "d",:schuko => "on"}
+        a = Schueler["d"]
+        a.sprecher.must_equal true
+        a.schuko.must_be_nil
+        follow_redirect!
+        last_response.body.must_include "Wahl hat bereits begonnen"
+      end
+    end
+
+    it "schüler wählt jeweils zwei" do
+      Time.stub :now, Time.parse("Oct 1, 2016, 17:00") do
+        post "/a", params={:sprecher => ["a", "b"], :schuko => ["a", "b"], :lehrer => [1,2]}
+        a = Schueler["a"]
+        a.gewaehlt.must_equal true
+        Sprecher.count.must_equal 2
+        Schuko.count.must_equal 2
+        Verbindungslehrer.count.must_equal 2
+      end
+    end
+
+    it "schüler wählt jeweils zwei und versucht es nochmal" do
+      Time.stub :now, Time.parse("Oct 1, 2016, 17:00") do
+        post "/a", params={:sprecher => ["a", "b"], :schuko => ["a", "b"], :lehrer => [1,2]}
+        post "/a", params={:sprecher => ["d"]}
+        a = Schueler["a"]
+        a.gewaehlt.must_equal true
+        Sprecher.count.must_equal 2
+        Schuko.count.must_equal 2
+        Verbindungslehrer.count.must_equal 2
+      end
+    end
+
+    it "schüler wählt drei sprecher statt zwei" do
+      Time.stub :now, Time.parse("Oct 1, 2016, 17:00") do
+        post "/a", params={:sprecher => ["a", "b", "d"]}
+        a = Schueler["a"]
+        a.gewaehlt.must_be_nil
+        Sprecher.count.must_equal 0
+      end
+    end
+
+    it "schüler wählt neun schuko statt acht" do
+      Time.stub :now, Time.parse("Oct 1, 2016, 17:00") do
+        post "/a", params={:schuko => %w(d e f g h i j k l)}
+        a = Schueler["a"]
+        a.gewaehlt.must_be_nil
+        Schuko.count.must_equal 0
+      end
+    end
+
+    it "schüler wählt drei lehrer statt zwei" do
+      Time.stub :now, Time.parse("Oct 1, 2016, 17:00") do
+        post "/a", params={:lehrer => [1,2,3]}
+        a = Schueler["a"]
+        a.gewaehlt.must_be_nil
+        Verbindungslehrer.count.must_equal 0
+      end
+    end
+
+    it "schüler wählt nichtaufgestellten schuko" do
+      Time.stub :now, Time.parse("Oct 1, 2016, 17:00") do
+        post "/a", params={:schuko => %w(d)}
+        a = Schueler["a"]
+        a.gewaehlt.must_be_nil
+        Schuko.count.must_equal 0
+      end
+    end
+
+    after do
+      Schueler.dataset.destroy
+      Sprecher.dataset.destroy
+      Schuko.dataset.destroy
+      Verbindungslehrer.dataset.destroy
+    end
+  end
+
+  describe "schüler besucht seite nach der wahl" do
+    before do
+      Schueler.create(:hashid => "a", :vorname => "A", :klasse => "A1", :schuko => true, :sprecher => true)
+      Schueler.create(:hashid => "b", :vorname => "B", :klasse => "B1", :schuko => true, :sprecher => true)
+      Schueler.create(:hashid => "c", :schuko => true)
+      Schueler.create(:hashid => "d")
+    end
+
+    it "schüler gibt code auf startseite ein und wird umgeleitet" do
+      Time.stub :now, Time.parse("Oct 3, 2016, 19:00") do
+      post "/hashid", params={:hashid => "a"}
+      follow_redirect!
+      last_response.body.must_include "Hallo A,"
+      end
+    end
+
+    it "schüler will sich als schuko aufstellen nach wahlbeginn" do
+      Time.stub :now, Time.parse("Oct 3, 2016, 19:00") do
+        post "/wahl", params={:hashid => "d",:schuko => "on"}
+        a = Schueler["d"]
+        a.sprecher.must_be_nil
+        a.schuko.must_be_nil
+      end
+    end
+
+    it "schüler wählt jeweils zwei und kann es nicht mehr" do
+      Time.stub :now, Time.parse("Oct 3, 2016, 19:00") do
+        post "/a", params={:sprecher => ["a", "b"], :schuko => ["a", "b"], :lehrer => [1,2]}
+        a = Schueler["a"]
+        a.gewaehlt.must_be_nil
+        Sprecher.count.must_equal 0
+        Schuko.count.must_equal 0
+        Verbindungslehrer.count.must_equal 0
+      end
+    end
+
+    after do
+      Schueler.dataset.destroy
+      Sprecher.dataset.destroy
+      Schuko.dataset.destroy
+      Verbindungslehrer.dataset.destroy
+    end
+  end
+
+  after do
+    Lehrer.dataset.destroy
+  end
+end

+ 30 - 0
specs/spec_helper.rb

@@ -0,0 +1,30 @@
+ENV['RACK_ENV'] = "test"
+ENV['PW_BASIC_AUTH_USER'], ENV['PW_BASIC_AUTH_PASSWORD'] = "test", "test"
+
+require 'minitest/autorun'
+require 'minitest/rg'
+require 'rack/test'
+require "sequel"
+
+class Minitest::Spec
+  def run(*args, &block)
+    Sequel::Model.db.transaction(:rollback=>:always, :auto_savepoint=>true){super}
+  end
+end
+
+DB = Sequel.connect("sqlite://test.db")
+
+SV_APP = Rack::Builder.parse_file("#{File.dirname(__FILE__)}/../config.ru").first
+Schueler.unrestrict_primary_key
+Lehrer.unrestrict_primary_key
+
+module SpecHelper
+  include Rack::Test::Methods
+  def app
+    SV_APP
+  end
+
+  def session
+    last_request.env['rack.session']
+  end
+end

+ 191 - 0
sv.rb

@@ -0,0 +1,191 @@
+require "sequel"
+require "sinatra"
+require 'newrelic_rpm'
+require "slim"
+require "pry"
+require "envyable"
+require "hashids"
+require "set"
+
+Envyable.load("config/env.yaml", ENV['RACK_ENV'])
+
+if ENV['RACK_ENV'] != "test"
+  DB = Sequel.connect(:adapter=>'postgres', :host=>ENV['PW_HOST'], :database=>ENV['PW_DB'], :user=>ENV['PW_USER'], :password=>ENV['PW_PASSWORD'], :sslmode => "require")
+end
+
+class Schueler < Sequel::Model(:schueler);end
+class Lehrer < Sequel::Model(:lehrer);end
+class Info < Sequel::Model(:infos);end
+class Sprecher < Sequel::Model(:sprecher);end
+class Schuko < Sequel::Model(:schuko);end
+class Verbindungslehrer < Sequel::Model(:verbindungslehrer);end
+
+class SV < Sinatra::Application
+  configure do
+    newrelic_ignore '/ping'
+    use Rack::Session::Cookie, :key => 'rack.session',
+      :path => '/',
+      :secret => (ENV['PW_SESSION_SECRET'] || 'your_secret'),
+      :expire_after => 2592000
+    Slim::Engine.set_options pretty: true
+    set(:wahlbeginn) do |zeit|
+      condition do
+        zeit >= Time.new(2016,9,11,12).to_i
+      end
+    end
+    enable :static
+    set :public_folder, File.dirname(__FILE__) + '/public'
+  end
+
+  helpers do
+    def protected!
+      return if authorized?
+      headers['WWW-Authenticate'] = 'Basic realm="Bitte anmelden"'
+      halt 401, "Not authorized\n"
+    end
+
+    def authorized?
+      @auth ||=  Rack::Auth::Basic::Request.new(request.env)
+      @auth.provided? and @auth.basic? and @auth.credentials and @auth.credentials == [ENV['PW_BASIC_AUTH_USER'], ENV['PW_BASIC_AUTH_PASSWORD']]
+    end
+
+    def hashids
+      hashids = Hashids.new("zujkthdtgsjfadwuiez8734fubcek", 5, "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789")
+    end
+  end
+
+  error do
+    'Es ist ein Fehler aufgetreten. Sollte das nochmal passieren, schreiben Sie mir doch bitte eine eMail: bk@hmt.im'
+  end
+
+  before do
+    # params.delete_if { |k, v| v.empty? }
+  end
+
+  get '/' do
+    infos = Info.all
+    slim :home, :locals => {:infos => infos}
+  end
+
+  get '/ping' do
+    halt 200
+  end
+
+  post "/wahl" do
+    if Time.now > time_for('Sep 30, 2016, 18:00')
+      session[:flash] = [0, "Sie können sich nicht mehr aufstellen lassen, die Wahl hat bereits begonnen"]
+      redirect back
+    end
+    schueler = Schueler.where(:hashid => params[:hashid]).first
+    if schueler.update_fields params, [:info, :email, :schuko, :sprecher, :nachname]
+      session[:flash] = [1, "Ihre Eingabe wurde gespeichert"]
+    else
+      session[:flash] = [0, "Es gab einen Fehler beim Speichern"]
+    end
+    redirect back
+  end
+
+  post '/info' do
+    protected!
+    if DB_SV[:infos].insert(params)
+      session[:flash] = [1, "Die Info wurde gespeichert"]
+    else
+      session[:flash] = [0, "Es gab einen Fehler beim Speichern der Info"]
+    end
+    redirect to("/")
+  end
+
+  get '/info_loeschen/:id' do |id|
+    protected!
+    Info.where(:id => id).first.delete
+    redirect back
+  end
+
+  get '/info' do
+    protected!
+    infos = Info.all
+    slim :info, :locals => {:infos => infos}
+  end
+
+  post "/hashid" do
+    redirect to("/#{params[:hashid]}")
+  end
+
+  get "/:hashid" do
+    schueler = Schueler.where(:hashid => params[:hashid]).first
+    if schueler.nil?
+      halt 404, "Adresse richtig abgetippt? Eventuell geht es mit dem QR-Code am Telefon leichter?"
+    end
+    lehrer = Lehrer.all
+    slim :hashid, :locals => {:schueler => schueler, :lehrer => lehrer}
+  end
+
+  post "/:hashid" do
+    if Time.now > time_for('Oct 3, 2016, 18:00')
+      session[:flash] = [0, "Der Wahlzeitraum ist seit 3. Oktober 18:00 Uhr abgelaufen."]
+      redirect back
+    elsif Time.now < time_for('Sep 30, 2016, 18:00')
+      session[:flash] = [0, "Der Wahlzeitraum beginnt erst am 30. September um 18:00 Uhr."]
+      redirect back
+    end
+
+    schueler = Schueler.where(:hashid => params[:hashid]).first
+    if schueler.nil?
+      halt 404, "Hier stimmt etwas nicht mit Ihrem persönlichen Code"
+    end
+    if schueler.gewaehlt
+      session[:flash] = [0, "Sie haben schon gewählt"]
+      redirect back
+    end
+    sprecher, schuko, lehrer = params[:sprecher], params[:schuko], params[:lehrer]
+    if sprecher && sprecher.count > 2
+      session[:flash] = [0, "Zu viele Sprecher gewählt (max. 2)"]
+      redirect back
+    elsif schuko && schuko.count > 8
+      session[:flash] = [0, "Zu viele Vertreter für die Schulkonferenz gewählt (max. 8)"]
+      redirect back
+    elsif lehrer && lehrer.count > 2
+      session[:flash] = [0, "Zu viele Vertrauenslehrer gewählt (max. 2)"]
+      redirect back
+    end
+    stimm_ids = []
+    sprecher_ids, schuko_ids, lehrer_ids =[], [], []
+    stimmabgabe = DB.transaction do
+      sprecher && sprecher.to_set.each do |s|
+        if Schueler[s].sprecher
+          sprecher_ids << DB[:sprecher].insert(:sprecher_id => s)
+        else
+          raise Sequel::Rollback
+          session[:flash] = [0, "Es wurde ein nichtaufgestellter Sprecher gewählt"]
+          redirect back
+        end
+      end
+      schuko && schuko.to_set.each do |s|
+        if Schueler[s].schuko
+          schuko_ids << DB[:schuko].insert(:schuko_id => s)
+        else
+          raise Sequel::Rollback
+          session[:flash] = [0, "Es wurde ein nichtaufgestellter Vertreter zur Schulkonferenz gewählt"]
+          redirect back
+        end
+      end
+      lehrer && lehrer.to_set.each do |l|
+        lehrer_ids << DB[:verbindungslehrer].insert(:lehrer_id => l)
+      end
+      schueler.update(:gewaehlt => true)
+    end
+    stimm_ids << sprecher_ids << schuko_ids << lehrer_ids
+    stimm_hash = stimm_ids.map do |s_id|
+      return "" if s_id.nil?
+      hashids.encode s_id
+    end
+    if stimmabgabe
+      session[:flash] = [1, "Ihre Wahl wurde gespeichert"]
+      session[:stimm_hash] = stimm_hash.join("l")
+    else
+      session[:flash] = [0, "Es gab einen Fehler bei der Speicherung der Wahlergebnisse"]
+    end
+    redirect back
+  end
+end
+

+ 155 - 0
views/hashid.slim

@@ -0,0 +1,155 @@
+-if session[:stimm_hash]
+  .panel.panel-success
+    .panel-body
+      |Ihre Wahl wurde anonymisiert. Sie können nicht noch einmal wählen.
+       Wenn Sie jedoch im Anschluss an die Wahl Einblick in die Wahldatenbank nehmen möchten, dann notieren
+       Sie sich folgenden Code:
+      b<> =session[:stimm_hash]
+      br
+      |Wenn Sie ihn verlieren,
+       können weder Sie noch wir die Stimmabgabe nachvollziehen.
+      -session.delete(:stimm_hash)
+h1 SV-Wahlen 2016
+.panel.panel-danger
+  .panel-body
+    h2
+      'Hallo
+      =schueler.vorname
+      |,
+    h3 Die Wahl findet von Freitag 30. September 18 Uhr bis Montag 3. Oktober 18 Uhr statt.
+    -if Time.now < time_for('Sep 30, 2016, 18:00')
+      |Bis dahin sollten alle Schüler und Studierenden des BK-Bethel Gelegenheit
+       haben, sich für die Wahl aufstellen zu lassen.
+      p
+      |Möchten Sie sich aufstellen lassen, dann geben Sie bitte unten an, für
+       welche Ämter Sie sich zur Verfügung stellen.
+      br
+      |Ebenso sollten Sie dann auch Ihren Nachnamen angeben,
+       der sonst nicht auf dieser Plattform hinterlegt ist.
+      p
+    |Es werden insgesamt 5 Vertreter und 5 weitere Stellvertreter für die
+     Schulkonferenz gewählt.
+    p
+    |Die Schulkonferenz tagt in diesem Schuljahr bereits am 4. Oktober um 18 Uhr.
+    p
+    |Prüfen Sie diese oder die
+    a<> href=url("/") Startseite
+    |für eventuelle weitere Nachrichten.
+
+-if Time.now < time_for('Sep 30, 2016, 18:00')
+  .panel.panel-info
+    .panel-body
+      h3 Für welche Ämter stellen Sie sich zur Verfügung?
+      h4 Alle Schüler und Studierenden des BK-Bethel haben die Möglichkeit gewählt
+       zu werden und zu wählen.
+      form.form-horizontal method="post" action="/wahl"
+        .form-group
+          .col-sm-offset-1.col-sm-9
+            .checkbox
+              label
+                input type="checkbox" checked=schueler.sprecher name="sprecher" value="true" Schülersprecher#{schueler.geschlecht == 3 ? "" : "in"}
+            .checkbox
+              label
+                input type="checkbox" checked=schueler.schuko name="schuko" value="true" Vertreter#{schueler.geschlecht == 3 ? "" : "in"} Schulkonferenz
+        .form-group class=("has-success" if (schueler.schuko || schueler.sprecher) && !schueler.info)
+          .col-sm-offset-1.col-sm-9
+            textarea.form-control placeholder="Schreiben Sie etwas über sich, z.B. warum Sie sich zur Wahl stellen." rows="5" name="info"
+              =schueler.info
+        .form-group class=("has-success" if (schueler.schuko || schueler.sprecher) && !schueler.nachname)
+          .col-sm-offset-1.col-sm-9
+            input.form-control placeholder="Nachname" type="text" name="nachname" value=schueler.nachname
+        .form-group class=("has-success" if (schueler.schuko || schueler.sprecher) && !schueler.email)
+          .col-sm-offset-1.col-sm-9
+            input.form-control placeholder="eMail" type="email" name="email" value=schueler.email
+        .form-group
+          .col-sm-offset-1.col-sm-9
+            input type="hidden" name="hashid" value=schueler.hashid
+            button type="submit" class="btn btn-success" Speichern
+  .panel.panel-info
+    .panel-body
+      h3 Folgende Schüler und Studierende stellen sich schon zur Wahl
+      h4 Schülersprecher/in
+      -sprecher=Schueler.where(:sprecher).all
+      -if sprecher.count > 0
+        ul
+          -sprecher.each do |s|
+            li
+              |#{s.vorname}, #{s.klasse}
+              -if s.info
+                blockquote
+                  p =s.info
+      -else
+        |–bisher niemand–
+
+      h4 Vertreter/in der Schulkonferenz
+      -schuko=Schueler.where(:schuko).all
+      -if schuko.count > 0
+        ul
+          -schuko.each do |s|
+            li
+              |#{s.vorname}, #{s.klasse}
+              -if s.info
+                blockquote
+                  p =s.info
+      -else
+        |–bisher niemand–
+
+      h3 Verbindungslehrer, die zur Wahl stehen
+      ul
+        -Lehrer.each do |l|
+          li =l.name
+
+-if (Time.now < time_for('Oct 3, 2016, 18:00')) && (Time.now > time_for('Sep 30, 2016, 17:59'))
+  -unless schueler.gewaehlt
+    .panel.panel-info
+      .panel-body
+        form.form-horizontal method="post" action="/#{schueler.hashid}"
+          h3 Folgende Schüler und Studierende stehen Wahl
+          h4 Schülersprecher/in
+          |Sie können
+          b<>zwei
+          |Personen wählen:
+          -sprecher=Schueler.where(:sprecher).all
+          -if sprecher.count > 0
+            .form-group
+              .col-sm-offset-1.col-sm-9
+                -sprecher.each do |s|
+                  .checkbox
+                    label
+                      input type="checkbox" name="sprecher[]" value=s.hashid
+                        |#{s.vorname}, #{s.klasse}
+                        -if s.info
+                          blockquote
+                            p =s.info
+
+          h4 Vertreter in der Schulkonferenz
+          |Sie können
+          b<>acht
+          |Personen wählen:
+          -schuko=Schueler.where(:schuko).all
+          -if schuko.count > 0
+            .form-group
+              .col-sm-offset-1.col-sm-9
+                -schuko.each do |s|
+                  .checkbox
+                    label
+                      input type="checkbox" name="schuko[]" value=s.hashid
+                        |#{s.vorname}, #{s.klasse}
+                        -if s.info
+                          blockquote
+                            p =s.info
+          h4 Vertrauenslehrer
+          |Sie können
+          b<>zwei
+          |Personen wählen:
+          .form-group
+            .col-sm-offset-1.col-sm-9
+              -Lehrer.each do |l|
+                .checkbox
+                  label
+                    input type="checkbox" name="lehrer[]" value=l.id
+                      =l.name
+          .form-group
+            .col-sm-offset-1.col-sm-9
+              input type="hidden" name="hashid" value=schueler.hashid
+              button type="submit" class="btn btn-success" Speichern

+ 18 - 0
views/home.slim

@@ -0,0 +1,18 @@
+h1 SV-Wahlen 2016
+form method="post" action="/hashid"
+  .row
+    .col-xs-6.col-sm-4.col-lg-4
+      .form-group.form-group-lg
+        input.form-control type="text" placeholder="Code eingeben" name="hashid" autofocus="true"
+    .col-xs-6.col-sm-3.col-lg-2
+      button type="submit" class="btn btn-primary btn-lg" Anmelden
+
+h2 Aktuelle Infos zu den SV-Wahlen
+-if infos.empty?
+  h3 –keine Neuigkeiten–
+-infos.reverse.each do |i|
+  .panel.panel-info
+    .panel-body
+      =i.text
+    .panel-footer
+      =Time.at(i.timestamp).strftime("%d.%m.%Y – %k:%M Uhr")

+ 25 - 0
views/info.slim

@@ -0,0 +1,25 @@
+h2 Information eingeben
+|Jede eingegebene Information erscheint mit einem Datum versehen auf der
+a<> href=url("/") Startseite
+p
+form.form-horizontal method="post" action=url("/info")
+  .form-group
+    label.col-sm-1 for="text" Text
+    .col-sm-11
+      textarea class="form-control" rows="3" name="text"
+  .form-group
+    .col-sm-offset-1.col-sm-11
+      input.form-control type="hidden" name="timestamp" value=Time.now.to_i
+      button type="submit" class="btn btn-primary" Info speichern
+
+-unless infos.empty?
+  h2 Einträge bearbeiten
+  table.table.table-condensed
+    -infos.each do |i|
+      tr
+        td =(Time.at(i.timestamp)).strftime("%d.%m.%Y – %k:%M Uhr")
+        td =i.text
+        td
+          a.btn.btn-danger href="/info_loeschen/#{i.id}" Löschen
+
+

+ 22 - 0
views/layout.slim

@@ -0,0 +1,22 @@
+doctype 5
+html lang="en"
+  head
+    meta charset="utf-8"
+    meta name="viewport" content="width=device-width, initial-scale=1.0"
+    title SV-Wahlen 2016
+    link rel="stylesheet" href="/assets/bootstrap/3.3.5/bootstrap.min.css"
+    .container
+      .row
+        .col-xs-12
+          p
+          -if session[:flash]
+            .alert class=(session[:flash][0]== 1 ? "alert-success" : "alert-danger") role="alert" = session[:flash][1]
+            .alert ==session[:flash][2]
+            -session.delete :flash
+          ==yield
+          .well
+            |Bei technischen Problemen wenden Sie sich bitte an
+            a<> href="mailto:bk@hmt.im" bk@hmt.im
+            p
+            |In die Kategorie »technisches Problem« fällt praktisch alles, was
+             nicht so funktioniert, wie man es erwartet.

Some files were not shown because too many files changed in this diff