diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 009d07eb..4cb156e5 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,7 +1,19 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2022-10-08 19:18:22 UTC using RuboCop version 1.36.0. +# on 2023-04-19 19:51:02 UTC using RuboCop version 1.50.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. + +# Offense count: 5 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: Include. +# Include: **/test/**/*.rb +Rails/ActionControllerTestCase: + Exclude: + - 'test/controllers/event_controller_test.rb' + - 'test/controllers/partners_controller_test.rb' + - 'test/controllers/registrations_controller_test.rb' + - 'test/controllers/users_controller_test.rb' + - 'test/controllers/welcome_controller_test.rb' diff --git a/.ruby-version b/.ruby-version index b0f2dcb3..818bd47a 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.0.4 +3.0.6 diff --git a/.tool-versions b/.tool-versions index c133be0c..d6608a7e 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -ruby 3.0.4 +ruby 3.0.6 diff --git a/Dockerfile b/Dockerfile index f2eb68c5..d051340f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:3.0.4 +FROM ruby:3.0.6 ENV RAILS_ENV=production diff --git a/Gemfile.lock b/Gemfile.lock index d6b30440..2177630e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,7 +7,7 @@ GIT GIT remote: https://github.com/ZeusWPI/rails_style.git - revision: 7101522ddc0f7e4cf13235a6e7a7318f04253443 + revision: 33ea5008e49be142ad490c5b572ef4b3f6436f23 specs: rails_style (0.0.1) bundler (>= 2.1.4) @@ -161,7 +161,7 @@ GEM rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.6.2) + json (2.6.3) jwt (2.3.0) launchy (2.5.0) addressable (~> 2.7) @@ -232,8 +232,8 @@ GEM paper_trail (14.0.0) activerecord (>= 6.0) request_store (~> 1.4) - parallel (1.22.1) - parser (3.1.2.1) + parallel (1.23.0) + parser (3.2.2.0) ast (~> 2.4.1) poltergeist (1.18.1) capybara (>= 2.1, < 4) @@ -300,37 +300,40 @@ GEM actionview (>= 5) redis-client (0.14.1) connection_pool - regexp_parser (2.6.0) + regexp_parser (2.8.0) request_store (1.5.1) rack (>= 1.4) responders (3.1.0) actionpack (>= 5.2) railties (>= 5.2) rexml (3.2.5) - rubocop (1.36.0) + rubocop (1.50.2) json (~> 2.3) parallel (~> 1.10) - parser (>= 3.1.2.1) + parser (>= 3.2.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.20.1, < 2.0) + rubocop-ast (>= 1.28.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.21.0) - parser (>= 3.1.1.0) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.28.0) + parser (>= 3.2.1.0) + rubocop-capybara (2.17.1) + rubocop (~> 1.41) rubocop-minitest (0.22.2) rubocop (>= 0.90, < 2.0) - rubocop-rails (2.16.1) + rubocop-rails (2.19.1) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) - rubocop-rspec (2.13.2) + rubocop-rspec (2.20.0) rubocop (~> 1.33) + rubocop-capybara (~> 2.17) ruby-graphviz (1.2.5) rexml ruby-ole (1.2.12.2) - ruby-progressbar (1.11.0) + ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) @@ -394,7 +397,7 @@ GEM concurrent-ruby (~> 1.0) uglifier (4.2.0) execjs (>= 0.3.0, < 3) - unicode-display_width (2.3.0) + unicode-display_width (2.4.2) validates_timeliness (6.0.0) activemodel (>= 6.0.0, < 7) timeliness (>= 0.3.10, < 1) diff --git a/README.md b/README.md index e5106921..1f841cfd 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,10 @@ In short, Gandalf is a project that does everything that makes organising and managing an event a lot easier for FK-clubs of the University of Ghent. The application is written specifically for the UGent FakulteitenKonvent. It allows students to register for events and it also interacts with the FK-Enrolment database and allows members of student unions to subscribe to member-only events from their clubs. # Getting started -0. Install the prerequisites: ruby 3.0.4, preferably using [asdf](https://asdf-vm.com/), and some system libraries depending on your OS (e.g. imagemagick) + +If you have NixOS or Nix installed, you can use the `flake.nix` to avoid the hassle of installing the right dependencies. Scroll down for more info. + +0. Install the prerequisites: ruby 3.0.6, preferably using [asdf](https://asdf-vm.com/), and some system libraries depending on your OS (e.g. imagemagick) 1. Install the ruby dependencies: `bin/bundle` 2. Start up the database, sidekiq and rails server by running `bin/dev` 3. Set up some database data using `rails db:setup` @@ -14,6 +17,14 @@ In short, Gandalf is a project that does everything that makes organising and ma In case you want to start the webserver in your IDE, just run `docker-compose up -d` and start Sidekiq manually (`bundle exec sidekiq`) +## Development using Nix + +- Make sure you have [Nix](https://nixos.org/download.html#download-nix) installed. +- Run `nix develop` +- Done! You have everything installed. You can now: + 1. Install Ruby dependencies with `gems:refresh` + 2. Start EVERYTHING with `server:start` + # Manually adding users to clubs / making users admin ``` diff --git a/app/actions/generate_html_barcodes.rb b/app/actions/generate_html_barcodes.rb new file mode 100644 index 00000000..4c0e5f13 --- /dev/null +++ b/app/actions/generate_html_barcodes.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'barby/barcode/ean_13' +require 'barby/outputter/html_outputter' + +class GenerateHtmlBarcodes + def initialize(barcode_data) + @barcode_data = barcode_data + end + + def call + barcode = Barby::EAN13.new(@barcode_data) + + html_outputter = Barby::HtmlOutputter.new(barcode) + html_outputter.to_html.html_safe # rubocop:disable Rails/OutputSafety + end +end diff --git a/app/actions/generate_ical.rb b/app/actions/generate_ical.rb new file mode 100644 index 00000000..aeabb03e --- /dev/null +++ b/app/actions/generate_ical.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class GenerateIcal + def initialize(event) + @event = event + end + + def call + cal = Icalendar::Calendar.new + cal.event do |e| + e.dtstart = @event.start_date + e.dtend = @event.end_date + e.location = @event.location + e.summary = @event.name + e.description = @event.website + e.organizer = "mailto:#{@event.contact_email}" + e.organizer = Icalendar::Values::CalAddress.new("mailto:#{@event.contact_email}", cn: @event.club.name) + end + cal.publish + cal.to_ical + end +end diff --git a/app/actions/user/fetch_club.rb b/app/actions/user/fetch_club.rb index 2880acd6..cb8e8713 100644 --- a/app/actions/user/fetch_club.rb +++ b/app/actions/user/fetch_club.rb @@ -21,8 +21,8 @@ def call private def fk_fetch_club - fk_authorized_clubs = Set['chemica', 'dentalia', 'filologica', 'fk', 'gbk', 'geografica', 'geologica', 'gfk', 'hermes', 'hilok', 'khk', - 'kmf', 'lila', 'lombrosiana', 'moeder-lies', 'oak', 'politeia', 'slavia', 'vbk', 'vdk', 'vek', 'veto', + fk_authorized_clubs = Set['chemica', 'dentalia', 'dsa', 'filologica', 'fk', 'gbk', 'geografica', 'geologica', 'gfk', 'hermes', 'hilok', + 'khk', 'kmf', 'lila', 'lombrosiana', 'moeder-lies', 'oak', 'politeia', 'slavia', 'vbk', 'vdk', 'vek', 'veto', 'vgk-fgen', 'vgk-flwi', 'vlak', 'vlk', 'vppk', 'vrg', 'vtk', 'wina'] resp = HTTParty.get( "#{Rails.application.secrets.fk_auth_url}/#{user.username}/Gandalf", @@ -35,7 +35,7 @@ def fk_fetch_club return Set.new unless resp.success? hash = JSON[resp.body] - clubs = hash['clubs'].map { |club| club['internal_name'] }.to_set + clubs = hash['clubs'].to_set { |club| club['internal_name'].downcase } # Only return clubs FK can manage clubs & fk_authorized_clubs end @@ -77,6 +77,8 @@ def dsa_fetch_club next return_map end + return Set.new if cas_to_dsa_associations.nil? + cas_to_dsa_associations.fetch(user.username, Set[]) end end diff --git a/app/assets/javascripts/registrations.js b/app/assets/javascripts/registrations.js index abfe9913..66b33b47 100644 --- a/app/assets/javascripts/registrations.js +++ b/app/assets/javascripts/registrations.js @@ -39,10 +39,10 @@ $(document).on('turbolinks:load', function() { } }; - $("#registration_access_levels").on('change', function() { + $("#registration_access_level").on('change', function() { return hideCommentFieldIfNeeded($(this).val()); }); - return hideCommentFieldIfNeeded($("#registration_access_levels").val()); + return hideCommentFieldIfNeeded($("#registration_access_level").val()); -}); \ No newline at end of file +}); diff --git a/app/assets/stylesheets/barcodes.css.scss b/app/assets/stylesheets/barcodes.css.scss new file mode 100644 index 00000000..1988ddbb --- /dev/null +++ b/app/assets/stylesheets/barcodes.css.scss @@ -0,0 +1,4 @@ +table.barby-barcode { border-spacing: 0; } +tr.barby-row {} +td.barby-cell { width: 3px; height: 70px; } +td.barby-cell.on { background: #000; } diff --git a/app/controllers/access_levels_controller.rb b/app/controllers/access_levels_controller.rb index 7a520133..c2e1d93a 100644 --- a/app/controllers/access_levels_controller.rb +++ b/app/controllers/access_levels_controller.rb @@ -5,13 +5,20 @@ class AccessLevelsController < ApplicationController respond_to :html, :js + def index + @event = Event.find params.require(:event_id) + authorize! :read, @event + end + def show @access_level = AccessLevel.find params.require(:id) end - def index + def edit @event = Event.find params.require(:event_id) - authorize! :read, @event + authorize! :update, @event + @access_level = @event.access_levels.find(params.require(:id)) + respond_with @access_level end def create @@ -24,13 +31,6 @@ def create respond_with @access_level end - def edit - @event = Event.find params.require(:event_id) - authorize! :update, @event - @access_level = @event.access_levels.find(params.require(:id)) - respond_with @access_level - end - def update @event = Event.find params.require(:event_id) authorize! :update, @event diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index 45b99528..be404756 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -27,19 +27,7 @@ def show respond_to do |format| format.html { render :show } - format.ics do - cal = Icalendar::Calendar.new - cal.event do |e| - e.dtstart = @event.start_date - e.dtend = @event.end_date - e.location = @event.location - e.summary = @event.name - e.organizer = "mailto:#{@event.contact_email}" - e.organizer = Icalendar::Values::CalAddress.new("mailto:#{@event.contact_email}", cn: @event.club.name) - end - cal.publish - render plain: cal.to_ical - end + format.ics { render plain: GenerateIcal.new(@event).call } end end @@ -47,9 +35,13 @@ def new; end def edit; end - def destroy - @event.destroy! - redirect_to action: :index + def create + authorize! :create, Event + + @event = Event.new(event_create_params) + flash.now[:success] = "Successfully created event." if @event.save + + respond_with @event end def update @@ -60,6 +52,11 @@ def update render action: :edit end + def destroy + @event.destroy! + redirect_to action: :index + end + def toggle_registration_open @event = Event.find params.require(:id) authorize! :update, @event @@ -69,15 +66,6 @@ def toggle_registration_open redirect_to action: :edit end - def create - authorize! :create, Event - - @event = Event.new(event_create_params) - flash.now[:success] = "Successfully created event." if @event.save - - respond_with @event - end - def statistics @event = Event.find params.require(:id) authorize! :view_stats, @event diff --git a/app/controllers/partners_controller.rb b/app/controllers/partners_controller.rb index 2b88f162..b84b932c 100644 --- a/app/controllers/partners_controller.rb +++ b/app/controllers/partners_controller.rb @@ -23,6 +23,14 @@ def new @partner = Partner.new end + def edit + @event = Event.find params.require(:event_id) + authorize! :update, @event + + @partner = @event.partners.find params.require(:id) + respond_with @partner + end + def create @event = Event.find params.require(:event_id) authorize! :update, @event @@ -39,14 +47,6 @@ def create respond_with @partner end - def edit - @event = Event.find params.require(:event_id) - authorize! :update, @event - - @partner = @event.partners.find params.require(:id) - respond_with @partner - end - def update @event = Event.find params.require(:event_id) authorize! :update, @event diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 9d9d0a8a..c8e7e756 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class RegistrationsController < ApplicationController - before_action :authenticate_user!, only: [:index, :destroy, :resend, :update, :email, :upload] + before_action :authenticate_user!, except: [:new, :create, :show] require 'csv' @@ -20,28 +20,17 @@ def index @registrations = @registrations.paginate(page: params[:page], per_page: 25) end - def new - @event = Event.find params.require(:event_id) - @registration = Registration.new - end + def show + @registration = Registration.find_by(token: params[:token]) + return head(:not_found) unless @registration - def destroy - @event = Event.find params.require(:event_id) - authorize! :destroy, @event - registration = Registration.find params.require(:id) - @id = registration.id - registration.destroy! + @event = @registration.event + @barcode = GenerateHtmlBarcodes.new(@registration.barcode_data).call end - def info - @registration = Registration.find params.require(:id) - authorize! :read, @registration.event - end - - def resend - @registration = Registration.find params.require(:id) - authorize! :update, @registration.event - @registration.deliver + def new + @event = Event.find params.require(:event_id) + @registration = Registration.new end def create @@ -66,7 +55,8 @@ def create @registration.deliver flash[:success] = "Registration successful. Please check your mailbox for your ticket or further payment information." - respond_with @event + + redirect_to event_registration_path(@registration.event, @registration.token) else render "events/show" end @@ -83,6 +73,25 @@ def update respond_with @registration end + def destroy + @event = Event.find params.require(:event_id) + authorize! :destroy, @event + registration = Registration.find params.require(:id) + @id = registration.id + registration.destroy! + end + + def info + @registration = Registration.find params.require(:id) + authorize! :read, @registration.event + end + + def resend + @registration = Registration.find params.require(:id) + authorize! :update, @registration.event + @registration.deliver + end + def email @event = Event.find params.require(:event_id) authorize! :read, @event diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index b9dd9933..61cd505c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -10,8 +10,12 @@ def datepicker_time(field) field.try { |d| d.strftime("%Y-%m-%d %H:%M") } end + def nice_amount(float) + number_with_precision float, precision: 2 + end + def euro(float) - "€#{number_with_precision float, precision: 2}" + number_to_currency(float, unit: '€') end # Form helpers diff --git a/app/mailers/registration_mailer.rb b/app/mailers/registration_mailer.rb index a1b5f728..be0113f1 100644 --- a/app/mailers/registration_mailer.rb +++ b/app/mailers/registration_mailer.rb @@ -13,6 +13,8 @@ def ticket(registration) barcodes = GenerateEmailBarcodes.new(@registration.barcode_data).call + attachments["event.ics"] = GenerateIcal.new(@registration.event).call + attachments.inline['barcode.png'] = barcodes.first attachments.inline['barcode-tilted.png'] = barcodes.second diff --git a/app/models/registration.rb b/app/models/registration.rb index d9400908..3bb650be 100644 --- a/app/models/registration.rb +++ b/app/models/registration.rb @@ -26,6 +26,8 @@ class Registration < ApplicationRecord record.payment_code = Registration.create_payment_code if record.payment_code.nil? end + before_save :ensure_token + after_save do |record| if !record.access_level.capacity.nil? && (record.access_level.registrations.count > record.access_level.capacity) record.errors.add :access_level, "type is sold out." @@ -95,6 +97,10 @@ def deliver end end + def ensure_token + self.token ||= SecureRandom.uuid + end + private def from_cents(value) @@ -122,6 +128,7 @@ def to_cents(value) # payment_code :string(255) # price :integer # student_number :string(255) +# token :string(255) not null # created_at :datetime # updated_at :datetime # access_level_id :integer not null @@ -132,6 +139,7 @@ def to_cents(value) # index_registrations_on_access_level_id (access_level_id) # index_registrations_on_event_id (event_id) # index_registrations_on_payment_code (payment_code) UNIQUE +# index_registrations_on_token (token) # # Foreign Keys # diff --git a/app/views/access_levels/_access_level.html.erb b/app/views/access_levels/_access_level.html.erb index f5dd7a15..b153a3a0 100644 --- a/app/views/access_levels/_access_level.html.erb +++ b/app/views/access_levels/_access_level.html.erb @@ -4,7 +4,7 @@ <%= access_level.registrations.size %> (<%= access_level.registrations.paid.size %>) / <%= access_level.capacity.presence || "∞".html_safe %> <%= "#{pluralize(access_level.tickets_left, "ticket")} left" if access_level.capacity.presence %> -
Organisation | +<%= event.club.name %> | +
---|---|
Location | +<%= event.location %> | +
Start date | +<%= nice_time event.start_date %> | +
End date | +<%= nice_time event.end_date %> | +
+ <%= event.description.html_safe %> + | +|
Website | +<%= link_to event.website, event.website %> | +
Contact e-mail | +<%= mail_to event.contact_email, event.contact_email %> | +
Organisation | -<%= @event.club.name %> | -
---|---|
Location | -<%= @event.location %> | -
Start date | -<%= nice_time @event.start_date %> | -
End date | -<%= nice_time @event.end_date %> | -
- <%= @event.description.html_safe %> - | -|
Website | -<%= link_to @event.website, @event.website %> | -
Contact e-mail | -<%= mail_to @event.contact_email, @event.contact_email %> | -
Bedankt voor het bestellen van een ticket voor <%= @registration.event.name %> van <%= @registration.event.club.name %>. Als u zich niet opgegeven hebt voor dit ticket, dan heeft iemand anders uw e-mailadres gebruikt om dit te registreren. Mocht dit het geval zijn, mag u deze e-mail negeren.
-Om uw ticket te ontvangen via e-mail, schrijft u <%= number_with_precision @registration.to_pay, precision: 2 %> euro over op het rekeningnummer <%= @registration.event.bank_number || "our bank account" %>. Plaats de code "<%= @registration.payment_code %>" in de mededeling van de overschrijving (zonder aanhalingstekens). Als u de code vergeet mee te geven of niet correct vermeldt in de beschrijving, dan kunnen wij uw betaling niet verwerken en zal u uw ticket niet ontvangen. U mag maximaal één code ingeven per overschrijving.
+Om uw ticket te ontvangen via e-mail, schrijft u <%= nice_amount @registration.to_pay %> euro over op het rekeningnummer <%= @registration.event.bank_number || "our bank account" %>. Plaats de code "<%= @registration.payment_code %>" in de mededeling van de overschrijving (zonder aanhalingstekens). Als u de code vergeet mee te geven of niet correct vermeldt in de beschrijving, dan kunnen wij uw betaling niet verwerken en zal u uw ticket niet ontvangen. U mag maximaal één code ingeven per overschrijving.
Gelieve ten laatste 3 dagen voor het evenement te betalen. Indien dit niet meer lukt, gelieve ons te contacteren via <%= mail_to @registration.event.contact_email, @registration.event.contact_email %> om een andere betalingswijze af te spreken. Eens we uw betaling verwerkt hebben, zal u uw ticket via e-mail ontvangen.
+U kan deze informatie ook altijd herbekijken op deze pagina. +
Mocht er zich eender welk probleem voordoen, kan u ons altijd via e-mail contacteren: <%= mail_to @registration.event.contact_email, @registration.event.contact_email %>
Met vriendelijke groet,
<%= @registration.event.club.name %>
Thank you for buying a ticket for <%= @registration.event.name %> of <%= @registration.event.club.name %>. If you did not register for this ticket, someone else used your email address to register. If this is the case, please ignore this email.
-To receive your ticket by mail, please transfer <%= number_with_precision @registration.to_pay, precision: 2 %> euro to <%= @registration.event.bank_number || "our bank account" %>. Place "<%= @registration.payment_code %>" in the description of your transfer (without the quotation marks). If you forget this code or you do not copy it correctly, we cannot process your payment and you will not receive your ticket. You are only allowed to enter one code per transfer.
+To receive your ticket by mail, please transfer <%= nice_amount @registration.to_pay %> euro to <%= @registration.event.bank_number || "our bank account" %>. Place "<%= @registration.payment_code %>" in the description of your transfer (without the quotation marks). If you forget this code or you do not copy it correctly, we cannot process your payment and you will not receive your ticket. You are only allowed to enter one code per transfer.
Please pay at least 3 days before the event. If this is no longer possible, please contact us via <%= mail_to @registration.event.contact_email, @registration.event.contact_email %> to agree upon a different payment method. You will receive your ticket by mail once we processed your payment.
+You can always view this information on this page. +
If you have any problems, you can contact us via mail: <%= mail_to @registration.event.contact_email, @registration.event.contact_email %>
Kind regards,
<%= @registration.event.club.name %>
+ <%= link_to "Register another ticket", event_path(@registration.event) %> +
+Name | +<%= @registration.name %> | +
---|---|
<%= @registration.email %> | +|
Ticket | +<%= @registration.access_level.name %> | +
Price | +<%= euro @registration.access_level.price %> | +
---|---|
Already paid | +- <%= euro @registration.paid %> | +
Remaining | +<%= euro @registration.to_pay %> | +
+ Om uw ticket te ontvangen via e-mail, schrijft u <%= nice_amount @registration.to_pay %> euro
+ over op het rekeningnummer <%= @registration.event.bank_number || "our bank account" %>. Plaats de code
+ "<%= @registration.payment_code %>" in de mededeling van de overschrijving (zonder aanhalingstekens).
+ Als u de
+ code vergeet mee te geven of niet correct vermeldt in de beschrijving, dan kunnen wij uw betaling niet verwerken
+ en zal u uw ticket niet ontvangen. U mag maximaal één code ingeven per overschrijving.
+