Welcome to Snap!Con <%= @email %>!
+ +You can confirm your account email through the link below:
+ +<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
+diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index 87aceb78..83f1aca4 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -9,6 +9,7 @@ on: jobs: linters: + continue-on-error: true runs-on: ubuntu-latest env: OSEM_RUBY_VERSION: 2.6.6 @@ -16,11 +17,12 @@ jobs: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: - ruby-version: 2.6 + ruby-version: 2.6.6 bundler-cache: true - run: bundle exec rubocop - run: bundle exec haml-lint app/views spec: + continue-on-error: true runs-on: ubuntu-latest name: spec env: @@ -34,7 +36,7 @@ jobs: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: - ruby-version: 2.6 + ruby-version: 2.6.6 bundler-cache: true - name: Prepare spec run: | diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 7bcb33d4..02f1ac56 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -248,6 +248,11 @@ linters: - "app/views/users/edit.html.haml" - "app/views/users/show.html.haml" + ConsecutiveComments: + exclude: + # This one seems like a bug in haml-lint. + - "app/views/admin/conferences/show.html.haml" + # Offense count: 24 ConsecutiveSilentScripts: exclude: @@ -605,11 +610,6 @@ linters: - "app/views/admin/registrations/index.csv.haml" - "app/views/layouts/_admin.html.haml" - # Offense count: 1 - ImplicitDiv: - exclude: - - "app/views/admin/registrations/index.html.haml" - # Offense count: 9 MultilinePipe: exclude: @@ -637,7 +637,6 @@ linters: - "app/views/devise/passwords/new.html.haml" - "app/views/payments/new.html.haml" - # Offense count: 13 ViewLength: exclude: diff --git a/Gemfile b/Gemfile index 87a01972..8627c685 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' -ruby ENV['TRAVIS_RUBY_VERSION'] || '2.6.6' +ruby ENV['TRAVIS_RUBY_VERSION'] || '~>2.6.6' # rails-assets requires >= 1.8.4 if Gem::Version.new(Bundler::VERSION) < Gem::Version.new('1.8.4') @@ -27,6 +27,7 @@ gem 'paper_trail' # for upload management gem 'carrierwave' gem 'carrierwave-bombshelter' +gem 'mimemagic', '~> 0.3.6' gem 'mini_magick' # for internationalizing @@ -138,8 +139,8 @@ gem 'prawn-rails' gem 'rqrcode' # to render XLS spreadsheets -gem 'axlsx', git: 'https://github.com/randym/axlsx.git' -gem 'axlsx_rails' +gem 'caxlsx' +gem 'caxlsx_rails' gem 'sentry-delayed_job' gem 'sentry-rails' @@ -220,6 +221,12 @@ gem 'dalli' gem 'icalendar' +# for making external requests easier +gem 'httparty' + +# pagination +gem 'pagy', '<4.0' + # Use guard and spring for testing in development group :development do # to launch specs when files are modified diff --git a/Gemfile.lock b/Gemfile.lock index c59eb26c..52965d29 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,37 +1,27 @@ -GIT - remote: https://github.com/randym/axlsx.git - revision: c593a08b2a929dac7aa8dc418b55e26b4c49dc34 - specs: - axlsx (3.0.0.pre) - htmlentities (~> 4.3, >= 4.3.4) - mimemagic (~> 0.3) - nokogiri (~> 1.8, >= 1.8.2) - rubyzip (~> 1.2, >= 1.2.1) - GEM remote: https://rubygems.org/ remote: https://rails-assets.org/ specs: Ascii85 (1.0.3) - actioncable (5.2.4.5) - actionpack (= 5.2.4.5) + actioncable (5.2.5) + actionpack (= 5.2.5) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.4.5) - actionpack (= 5.2.4.5) - actionview (= 5.2.4.5) - activejob (= 5.2.4.5) + actionmailer (5.2.5) + actionpack (= 5.2.5) + actionview (= 5.2.5) + activejob (= 5.2.5) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.4.5) - actionview (= 5.2.4.5) - activesupport (= 5.2.4.5) + actionpack (5.2.5) + actionview (= 5.2.5) + activesupport (= 5.2.5) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.4.5) - activesupport (= 5.2.4.5) + actionview (5.2.5) + activesupport (= 5.2.5) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -41,20 +31,20 @@ GEM activemodel (>= 4.1, < 6.2) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (5.2.4.5) - activesupport (= 5.2.4.5) + activejob (5.2.5) + activesupport (= 5.2.5) globalid (>= 0.3.6) - activemodel (5.2.4.5) - activesupport (= 5.2.4.5) - activerecord (5.2.4.5) - activemodel (= 5.2.4.5) - activesupport (= 5.2.4.5) + activemodel (5.2.5) + activesupport (= 5.2.5) + activerecord (5.2.5) + activemodel (= 5.2.5) + activesupport (= 5.2.5) arel (>= 9.0) - activestorage (5.2.4.5) - actionpack (= 5.2.4.5) - activerecord (= 5.2.4.5) - marcel (~> 0.3.1) - activesupport (5.2.4.5) + activestorage (5.2.5) + actionpack (= 5.2.5) + activerecord (= 5.2.5) + marcel (~> 1.0.0) + activesupport (5.2.5) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -65,8 +55,8 @@ GEM awesome_nested_set (>= 3.0) acts_as_list (0.9.19) activerecord (>= 3.0) - addressable (2.6.0) - public_suffix (>= 2.0.2, < 4.0) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) afm (0.2.2) ajax-datatables-rails (0.4.3) railties (>= 4.0) @@ -82,9 +72,6 @@ GEM awesome_nested_set (3.4.0) activerecord (>= 4.0.0, < 7.0) aws_cf_signer (0.1.3) - axlsx_rails (0.5.2) - actionpack (>= 3.1) - axlsx (>= 2.0.1) bcrypt (3.1.16) bindex (0.6.0) bootstrap-sass (3.4.1) @@ -94,15 +81,15 @@ GEM bootstrap3-datetimepicker-rails (4.17.47) momentjs-rails (>= 2.8.1) builder (3.2.4) - byebug (11.0.1) + byebug (11.1.3) cancancan (2.3.0) - capybara (3.26.0) + capybara (3.35.3) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) rack-test (>= 0.6.3) - regexp_parser (~> 1.5) + regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) carrierwave (2.1.1) activemodel (>= 5.0.0) @@ -118,6 +105,14 @@ GEM fastimage case_transform (0.2) activesupport + caxlsx (3.1.0) + htmlentities (~> 4.3, >= 4.3.4) + marcel (~> 1.0) + nokogiri (~> 1.10, >= 1.10.4) + rubyzip (>= 1.3.0, < 3) + caxlsx_rails (0.6.2) + actionpack (>= 3.1) + caxlsx (>= 3.0) chartkick (3.4.2) childprocess (3.0.0) chronic (0.10.2) @@ -249,10 +244,10 @@ GEM httparty (0.18.1) mime-types (~> 3.0) multi_xml (>= 0.5.2) - i18n (1.8.9) + i18n (1.8.10) concurrent-ruby (~> 1.0) i18n_data (0.8.0) - icalendar (2.7.0) + icalendar (2.7.1) ice_cube (~> 0.16) ice_cube (0.16.3) image_processing (1.12.1) @@ -293,18 +288,19 @@ GEM lumberjack (1.0.12) mail (2.7.1) mini_mime (>= 0.1.1) - marcel (0.3.3) - mimemagic (~> 0.3.2) + marcel (1.0.0) method_source (0.8.2) mime-types (3.3.1) mime-types-data (~> 3.2015) mime-types-data (3.2021.0225) - mimemagic (0.3.5) + mimemagic (0.4.3) + nokogiri (~> 1) + rake mina (1.2.3) open4 (~> 1.3.4) rake mini_magick (4.9.5) - mini_mime (1.0.2) + mini_mime (1.0.3) mini_portile2 (2.5.0) minitest (5.14.4) momentjs-rails (2.20.1) @@ -324,7 +320,7 @@ GEM nenv (0.3.0) netrc (0.11.0) nio4r (2.5.7) - nokogiri (1.11.1) + nokogiri (1.11.2) mini_portile2 (~> 2.5.0) racc (~> 1.4) notiffany (0.1.1) @@ -362,6 +358,7 @@ GEM rack-openid (~> 1.3.1) open4 (1.3.4) orm_adapter (0.5.0) + pagy (3.12.0) paper_trail (10.3.1) activerecord (>= 4.2) request_store (~> 1.1) @@ -420,7 +417,7 @@ GEM pry-byebug (3.8.0) byebug (~> 11.0) pry (~> 0.10) - public_suffix (3.1.1) + public_suffix (4.0.6) puma (5.2.2) nio4r (~> 2.0) racc (1.5.2) @@ -430,18 +427,18 @@ GEM ruby-openid (>= 2.1.8) rack-test (1.1.0) rack (>= 1.0, < 3) - rails (5.2.4.5) - actioncable (= 5.2.4.5) - actionmailer (= 5.2.4.5) - actionpack (= 5.2.4.5) - actionview (= 5.2.4.5) - activejob (= 5.2.4.5) - activemodel (= 5.2.4.5) - activerecord (= 5.2.4.5) - activestorage (= 5.2.4.5) - activesupport (= 5.2.4.5) + rails (5.2.5) + actioncable (= 5.2.5) + actionmailer (= 5.2.5) + actionpack (= 5.2.5) + actionview (= 5.2.5) + activejob (= 5.2.5) + activemodel (= 5.2.5) + activerecord (= 5.2.5) + activestorage (= 5.2.5) + activesupport (= 5.2.5) bundler (>= 1.3.0) - railties (= 5.2.4.5) + railties (= 5.2.5) sprockets-rails (>= 2.0.0) rails-assets-bootstrap (3.3.6) rails-assets-jquery (>= 1.9.1, < 3) @@ -475,9 +472,9 @@ GEM rails-i18n (5.1.3) i18n (>= 0.7, < 2) railties (>= 5.0, < 6) - railties (5.2.4.5) - actionpack (= 5.2.4.5) - activesupport (= 5.2.4.5) + railties (5.2.5) + actionpack (= 5.2.5) + activesupport (= 5.2.5) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) @@ -489,7 +486,7 @@ GEM recaptcha (4.14.0) json redcarpet (3.5.1) - regexp_parser (1.6.0) + regexp_parser (2.1.1) request_store (1.4.1) rack (>= 1.4) responders (2.4.1) @@ -678,8 +675,6 @@ DEPENDENCIES annotate autoprefixer-rails awesome_nested_set - axlsx! - axlsx_rails bootstrap-sass (~> 3.4.0) bootstrap-switch-rails (~> 3.0.0) bootstrap3-datetimepicker-rails (~> 4.17.47) @@ -688,6 +683,8 @@ DEPENDENCIES capybara carrierwave carrierwave-bombshelter + caxlsx + caxlsx_rails chartkick climate_control cloudinary @@ -712,6 +709,7 @@ DEPENDENCIES guard-rspec haml-lint haml-rails + httparty icalendar iso-639 jquery-datatables @@ -721,6 +719,7 @@ DEPENDENCIES leaflet-rails letter_opener letter_opener_web (~> 1.0) + mimemagic (~> 0.3.6) mina mini_magick money-rails @@ -731,6 +730,7 @@ DEPENDENCIES omniauth-github omniauth-google-oauth2 omniauth-openid + pagy (< 4.0) paper_trail pdf-inspector pg diff --git a/Procfile b/Procfile index 8b9e764c..7a5416e2 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,3 @@ web: bundle exec rails server -b 0.0.0.0 worker: bundle exec rails jobs:work +release: rake db:migrate diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index ca8cf3da..6548a3fe 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -54,6 +54,7 @@ //= require selectize //= require bootstrap-select //= require osem-survey +//= require pagy $(document).ready(function() { $('a[disabled=disabled]').click(function(event){ @@ -63,4 +64,6 @@ $(document).ready(function() { $('body').smoothScroll({ delegateSelector: 'a.smoothscroll' }); + + window.addEventListener("load", Pagy.init); }); diff --git a/app/assets/javascripts/osem.js b/app/assets/javascripts/osem.js index c4c8356a..ec6fef18 100644 --- a/app/assets/javascripts/osem.js +++ b/app/assets/javascripts/osem.js @@ -149,13 +149,23 @@ function word_count(text, divId, maxcount) { }); }; -function fill_if_empty(text_area, filler) { - let area = $('#' + text_area); +function replace_defaut_submission_text(input_selector, new_text, valid_defaults) { + let $area = $(input_selector); + let current_text = $area.val(); - if (!area.val()) { - area.val(filler); - area.trigger('change'); + if (!current_text) { + $area.val(new_text); + $area.trigger('change'); + return; } + + valid_defaults.some(default_text => { + if (current_text == default_text) { + $area.val(new_text); + $area.trigger('change'); + return true; + } + }); } /* Wait for the DOM to be ready before attaching events to the elements */ @@ -166,8 +176,13 @@ $( document ).ready(function() { var max = $selected.data("max-words"); var min = $selected.data("min-words"); - // Set the filler text for the submission text - fill_if_empty('event_submission_text', $selected.data("instructions")); + // We replace the default text only if the current field is empty, + // or is set to the default text of another event type. + replace_defaut_submission_text( + '#event_submission_text', + $selected.data("instructions"), + $("#event_event_type_id option").toArray().map(e => $(e).data('instructions')) + ); $("#abstract-maximum-word-count").text(max); $("#submission-maximum-word-count").text(max); diff --git a/app/controllers/admin/splashpages_controller.rb b/app/controllers/admin/splashpages_controller.rb index fd432cde..fc7e3299 100644 --- a/app/controllers/admin/splashpages_controller.rb +++ b/app/controllers/admin/splashpages_controller.rb @@ -50,7 +50,7 @@ module Admin :include_venue, :include_registrations, :include_tickets, :include_lodgings, :include_sponsors, :include_social_media, - :include_booths) + :include_booths, :include_happening_now) end end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c9f93a30..b2e925e7 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,6 +3,7 @@ class ApplicationController < ActionController::Base before_action :set_paper_trail_whodunnit include ApplicationHelper + include Pagy::Backend add_flash_types :error protect_from_forgery with: :exception, prepend: true before_action :store_location diff --git a/app/controllers/conference_registrations_controller.rb b/app/controllers/conference_registrations_controller.rb index 8d4ae8a1..6a64ff8a 100644 --- a/app/controllers/conference_registrations_controller.rb +++ b/app/controllers/conference_registrations_controller.rb @@ -57,6 +57,8 @@ class ConferenceRegistrationsController < ApplicationController sign_in(@registration.user) end + MailblusterEditLeadJob.perform_later(@user, add_tags: ["snapcon-#{@conference.short_title}"]) + if @conference.tickets.visible.any? && !current_user.supports?(@conference) redirect_to conference_tickets_path(@conference.short_title), notice: 'You are now registered and will be receiving E-Mail notifications.' @@ -87,6 +89,7 @@ class ConferenceRegistrationsController < ApplicationController def destroy if @registration.destroy + MailblusterEditLeadJob.perform_later(@user, remove_tags: ["snapcon-#{@conference.short_title}"]) redirect_to root_path, notice: "You are not registered for #{@conference.title} anymore!" else diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb index 02d869f4..35e6b709 100644 --- a/app/controllers/conferences_controller.rb +++ b/app/controllers/conferences_controller.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true +EVENTS_PER_PAGE = Rails.configuration.conference[:events_per_page] + class ConferencesController < ApplicationController + include ConferenceHelper + protect_from_forgery with: :null_session before_action :respond_to_options load_and_authorize_resource find_by: :short_title, except: :show @@ -56,6 +60,13 @@ class ConferencesController < ApplicationController if splashpage.include_booths @booths = @conference.confirmed_booths.order('title') end + if splashpage.include_happening_now + events_schedules_list = get_happening_now_events_schedules(@conference) + @events_schedules_limit = EVENTS_PER_PAGE + @events_schedules_length = events_schedules_list.length + @pagy, @events_schedules = pagy_array(events_schedules_list, items: @events_schedules_limit, link_extra: 'data-remote="true"') + @happening_now_url = happening_now_conference_schedule_path(conference_id: @conference.short_title, format: :json) + end end if splashpage.include_registrations || splashpage.include_tickets @tickets = @conference.tickets.visible.order('price_cents') diff --git a/app/controllers/schedules_controller.rb b/app/controllers/schedules_controller.rb index 230cda2f..bdefb939 100644 --- a/app/controllers/schedules_controller.rb +++ b/app/controllers/schedules_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class SchedulesController < ApplicationController + include ConferenceHelper + load_and_authorize_resource before_action :respond_to_options load_resource :conference, find_by: :short_title @@ -67,11 +69,13 @@ class SchedulesController < ApplicationController end def happening_now - @events_schedules = @program.selected_event_schedules( - includes: [:room, { event: %i[track event_type speakers submitter] }] - ).select(&:happening_now?) - @events_schedules = [] unless @events_schedules + @events_schedules = get_happening_now_events_schedules(@conference) @current_time = Time.now.in_time_zone(@conference.timezone) + + respond_to do |format| + format.html + format.json { render json: @events_schedules.to_json(root: false, include: :event) } + end end def app diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 1c7f5da6..7578e99c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true module ApplicationHelper + include Pagy::Frontend # Returns a string build from the start and end date of the given conference. # # If the conference is only one day long diff --git a/app/helpers/conference_helper.rb b/app/helpers/conference_helper.rb index 417a8d55..cf2c06ce 100644 --- a/app/helpers/conference_helper.rb +++ b/app/helpers/conference_helper.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -DEFAULT_LOGO = 'snapcon_logo.png' -DEFAULT_COLOR = '#0B3559' +DEFAULT_LOGO = Rails.configuration.conference[:default_logo_filename] +DEFAULT_COLOR = Rails.configuration.conference[:default_color] module ConferenceHelper # Return true if only call_for_papers or call_for_tracks or call_for_booths is open @@ -77,4 +77,12 @@ module ConferenceHelper end calendar end + + def get_happening_now_events_schedules(conference) + events_schedules = conference.program.selected_event_schedules( + includes: [:room, { event: %i[track event_type speakers submitter] }] + ).select(&:happening_now?) + events_schedules ||= [] + events_schedules + end end diff --git a/app/jobs/mailbluster_create_lead_job.rb b/app/jobs/mailbluster_create_lead_job.rb new file mode 100644 index 00000000..701719d7 --- /dev/null +++ b/app/jobs/mailbluster_create_lead_job.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class MailblusterCreateLeadJob < ApplicationJob + queue_as :default + + def perform(user) + MailblusterManager.create_lead(user) + end +end diff --git a/app/jobs/mailbluster_delete_lead_job.rb b/app/jobs/mailbluster_delete_lead_job.rb new file mode 100644 index 00000000..387d1cc0 --- /dev/null +++ b/app/jobs/mailbluster_delete_lead_job.rb @@ -0,0 +1,7 @@ +class MailblusterDeleteLeadJob < ApplicationJob + queue_as :default + + def perform(user) + MailblusterManager.delete_lead(user) + end +end diff --git a/app/jobs/mailbluster_edit_lead_job.rb b/app/jobs/mailbluster_edit_lead_job.rb new file mode 100644 index 00000000..ff5d07ce --- /dev/null +++ b/app/jobs/mailbluster_edit_lead_job.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class MailblusterEditLeadJob < ApplicationJob + queue_as :default + + def perform(user, add_tags: [], remove_tags: [], old_email: nil) + MailblusterManager.edit_lead(user, add_tags: add_tags, remove_tags: remove_tags, old_email: old_email) + end +end diff --git a/app/mailers/mailbot.rb b/app/mailers/mailbot.rb index 2f9d1f77..33f3c010 100644 --- a/app/mailers/mailbot.rb +++ b/app/mailers/mailbot.rb @@ -1,8 +1,9 @@ # frozen_string_literal: true -SNAPCON_BCC_ADDRESS = 'messages@snap.berkeley.edu' EMAIL_TEMPLATE = 'email_template' -YTLF_TICKET_ID = 50 + +SNAPCON_BCC_ADDRESS = Rails.configuration.mailbot[:bcc_address] +YTLF_TICKET_ID = Rails.configuration.mailbot[:ytlf_ticket_id] class Mailbot < ActionMailer::Base helper ConferenceHelper diff --git a/app/models/concerns/track_saved_changes.rb b/app/models/concerns/track_saved_changes.rb new file mode 100644 index 00000000..db75e625 --- /dev/null +++ b/app/models/concerns/track_saved_changes.rb @@ -0,0 +1,48 @@ +# https://github.com/ccmcbeck/after-commit +module TrackSavedChanges + extend ActiveSupport::Concern + + included do + # expose the details if consumer wants to do more + # attr_reader :ts_saved_changes_history, :ts_saved_changes_unfiltered + after_initialize :ts_reset_saved_changes + after_save :ts_track_saved_changes + end + + # on initalize, but useful for fine grain control + def ts_reset_saved_changes + @ts_saved_changes_unfiltered = {} + @ts_saved_changes_history = [] + end + + # filter out any changes that result in the original value + def ts_saved_changes + @ts_saved_changes_unfiltered.reject { |_k, v| v[0] == v[1] } + end + + private + + # on save + def ts_track_saved_changes + # maintain an array of ActiveModel::Dirty.changes + @ts_saved_changes_history << previous_changes.dup + # accumulate the most recent changes + @ts_saved_changes_history.last.each_pair { |k, v| ts_track_saved_change k, v } + end + + # v is an an array of [prev, current] + def ts_track_saved_change(key, value) + if @ts_saved_changes_unfiltered.key? key + @ts_saved_changes_unfiltered[key][1] = ts_track_saved_value value[1] + else + @ts_saved_changes_unfiltered[key] = value.dup + end + end + + # type safe dup inspred by http://stackoverflow.com/a/20955038 + def ts_track_saved_value(value) + value.dup + rescue TypeError + value + end +end diff --git a/app/models/event.rb b/app/models/event.rb index b172bc3b..c780afb9 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -34,6 +34,8 @@ class Event < ApplicationRecord include ActiveRecord::Transitions include RevisionCount + include FormatHelper + has_paper_trail on: [:create, :update], ignore: [:updated_at, :guid, :week], meta: { conference_id: :conference_id } acts_as_commentable @@ -338,6 +340,10 @@ class Event < ApplicationRecord time <=> other.time end + def serializable_hash(options = {}) + super(options).merge('rendered_abstract' => markdown(abstract)) + end + private ## diff --git a/app/models/event_schedule.rb b/app/models/event_schedule.rb index 07b31a30..036a89f2 100644 --- a/app/models/event_schedule.rb +++ b/app/models/event_schedule.rb @@ -55,16 +55,19 @@ class EventSchedule < ApplicationRecord # True within `threshold` before and after the event. # def happening_now?(threshold = 30.minutes) + # TODO: Save start_time with local timezone info when making an event schedule in_tz_start = start_time.in_time_zone(timezone) in_tz_end = end_time.in_time_zone(timezone) in_tz_start -= in_tz_start.utc_offset in_tz_end -= in_tz_end.utc_offset + + return false if in_tz_end < Time.now + begin_range = Time.now - threshold end_range = Time.now + threshold event_time_range = in_tz_start..in_tz_end now_range = begin_range..end_range - # TODO: There's probably better logic. - event_time_range.overlaps?(now_range) && (in_tz_end > Time.now) + event_time_range.overlaps?(now_range) end def self.withdrawn_or_canceled_event_schedules(schedule_ids) diff --git a/app/models/program.rb b/app/models/program.rb index 19000746..e0196099 100644 --- a/app/models/program.rb +++ b/app/models/program.rb @@ -195,6 +195,7 @@ class Program < ApplicationRecord # * +True+ -> If there is any event for the given date # * +False+ -> If there is not any event for the given date def any_event_for_this_date?(date) + return false if date.nil? || date == '' return false unless selected_schedule.present? parsed_date = DateTime.parse("#{date} 00:00").utc diff --git a/app/models/splashpage.rb b/app/models/splashpage.rb index cf4576db..f9a6cbc2 100644 --- a/app/models/splashpage.rb +++ b/app/models/splashpage.rb @@ -11,6 +11,7 @@ # banner_photo_updated_at :datetime # include_booths :boolean # include_cfp :boolean default(FALSE) +# include_happening_now :boolean # include_lodgings :boolean # include_program :boolean # include_registrations :boolean diff --git a/app/models/user.rb b/app/models/user.rb index 955c76d1..ddcf63bb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -53,6 +53,7 @@ class UserDisabled < StandardError end class User < ApplicationRecord + include TrackSavedChanges rolify # prevent N+1 queries with has_cached_role? by preloading roles *always* default_scope { preload(:roles) } @@ -80,6 +81,12 @@ class User < ApplicationRecord after_save :touch_events + # Note that using after_create_commit and after_update_commit does not work. + # See https://github.com/CactusPuppy/snapcon/pull/43#discussion_r609458034 + after_commit :mailbluster_create_lead, on: :create + after_commit :mailbluster_delete_lead, on: :destroy + after_commit :mailbluster_update_lead, on: :update, if: ->(user){ ['name', 'email'].any? { |key| user.ts_saved_changes.key? key } } + # add scope scope :comment_notifiable, ->(conference) {joins(:roles).where('roles.name IN (?)', [:organizer, :cfp]).where('roles.resource_type = ? AND roles.resource_id = ?', 'Conference', conference.id)} @@ -361,12 +368,32 @@ class User < ApplicationRecord User.count == 1 && User.first.email == 'deleted@localhost.osem' end + # TODO: email_hash function for mailbluster + # def email_hash + # Digest::MD5.hexdigest user.email + # end + private def setup_role self.is_admin = true if User.empty? end + def mailbluster_create_lead + MailblusterCreateLeadJob.perform_later self + ts_reset_saved_changes + end + + def mailbluster_delete_lead + MailblusterDeleteLeadJob.perform_later email + ts_reset_saved_changes + end + + def mailbluster_update_lead + MailblusterEditLeadJob.perform_later(self, old_email: ts_saved_changes.fetch('email', [nil])[0]) + ts_reset_saved_changes + end + def touch_events event_users.each(&:touch) end diff --git a/app/services/mailbluster_manager.rb b/app/services/mailbluster_manager.rb new file mode 100644 index 00000000..d74ea76a --- /dev/null +++ b/app/services/mailbluster_manager.rb @@ -0,0 +1,40 @@ +class MailblusterManager + include HTTParty + base_uri 'https://api.mailbluster.com/api/leads/' + @auth_headers = { + headers: { + 'Content-Type' => 'application/json', + 'Authorization' => ENV['MAILBLUSTER_API_KEY'] + } + } + + def self.query_api(method, path, body: {}) + options = @auth_headers.merge(body: body.to_json) + send(method, path, options).parsed_response + end + + def self.create_lead(user) + query_api(:post, '/', body: { + 'email' => user.email, + 'firstName' => user.name, + 'overrideExisting' => true, + 'subscribed' => true, + 'tags' => [ENV['OSEM_NAME'] || 'snapcon'] + }) + end + + def self.edit_lead(user, add_tags: [], remove_tags: [], old_email: nil) + email_hash = Digest::MD5.hexdigest(old_email.presence || user.email) + query_api(:put, "/#{email_hash}", body: { + 'email' => user.email, + 'firstName' => user.name, + 'addTags' => add_tags, + 'removeTags' => remove_tags + }) + end + + def self.delete_lead(email) + email_hash = Digest::MD5.hexdigest email + query_api(:delete, "/#{email_hash}") + end +end diff --git a/app/views/admin/registrations/index.html.haml b/app/views/admin/registrations/index.html.haml index 09f674e9..70a50b46 100644 --- a/app/views/admin/registrations/index.html.haml +++ b/app/views/admin/registrations/index.html.haml @@ -20,7 +20,7 @@ combined_data: @registration_distribution .row .col-md-12 - %div.margin-event-table + .margin-event-table %table.datatable#registrations{ data: { source: admin_conference_registrations_path(conference_id: @conference, format: :json) } } %thead %tr diff --git a/app/views/admin/splashpages/_form.html.haml b/app/views/admin/splashpages/_form.html.haml index 3db1e9ea..c40e9294 100644 --- a/app/views/admin/splashpages/_form.html.haml +++ b/app/views/admin/splashpages/_form.html.haml @@ -25,6 +25,8 @@ = f.input :include_tracks, label: 'Include confirmed tracks', input_html: { checked: params[:action] == 'new' || @splashpage.try(:include_tracks) } %li = f.input :include_booths, label: "Include confirmed #{(t'booth').pluralize}", input_html: { checked: params[:action] == 'new' || @splashpage.try(:include_booths) } + %li + = f.input :include_happening_now, label: 'Include events happening now', input_html: { checked: params[:action] == 'new' || @splashpage.try(:include_happening_now) } %li = f.input :include_registrations, label: 'Display the registration period', input_html: { checked: params[:action] == 'new' || @splashpage.try(:include_registrations) } diff --git a/app/views/admin/splashpages/show.html.haml b/app/views/admin/splashpages/show.html.haml index a9046fdc..08edb140 100644 --- a/app/views/admin/splashpages/show.html.haml +++ b/app/views/admin/splashpages/show.html.haml @@ -27,6 +27,9 @@ %li %i{ class: "fa-li #{icon_for_todo @splashpage.include_booths?}" } Include confirmed #{(t'booth').pluralize} + %li + %i{ class: "fa-li #{icon_for_todo @splashpage.include_happening_now?}" } + Include events happening now %li %i{ class: "fa-li #{icon_for_todo @splashpage.include_registrations?}" } Display the registration period diff --git a/app/views/conferences/_about_and_happening_now.haml b/app/views/conferences/_about_and_happening_now.haml new file mode 100644 index 00000000..c048b28d --- /dev/null +++ b/app/views/conferences/_about_and_happening_now.haml @@ -0,0 +1,32 @@ += content_for :happening_now do + #happening-now + = render 'happening_now', conference: conference, + events_schedules: events_schedules, pagy: pagy, + events_schedules_length: events_schedules_length, + events_schedules_limit: events_schedules_limit + += content_for :about do + #about + .row + %h2.text-left{ style: 'margin-bottom:30px' } About the Conference + = markdown(conference.description, false) + +%section#about-and-happening-now + .container + .row + -# happening now events are displayed second in md or lg view + - if conference.splashpage.include_happening_now && conference.splashpage.include_program + - if conference.description.present? + .col-md-6.col-md-push-6.col-lg-4.col-lg-push-8 + = yield :happening_now + - else + .col-md-12 + = yield :happening_now + - if conference.description.present? + - if conference.splashpage.include_happening_now && conference.splashpage.include_program + .col-md-6.col-md-pull-6.col-lg-8.col-lg-pull-4 + = yield :about + - else + .col-md-12 + = yield :about + .trapezoid diff --git a/app/views/conferences/_happening_now.haml b/app/views/conferences/_happening_now.haml new file mode 100644 index 00000000..66355a82 --- /dev/null +++ b/app/views/conferences/_happening_now.haml @@ -0,0 +1,12 @@ +- if conference.splashpage.include_program && conference.splashpage.include_happening_now + - if events_schedules.any? + .row + %h2.text-center{ style: 'margin-bottom:30px' } Happening Now + - events_schedules.each do |event_schedule| + = render 'schedules/event', conference: conference, event_schedule: event_schedule, event: event_schedule.event, is_brief: true + - if events_schedules_length > events_schedules_limit + .container{ style: 'width:100%; text-align:center' } + != pagy_bootstrap_nav_js(pagy) + - else + .row + %h3.text-center There are no events happening now. diff --git a/app/views/conferences/_header.haml b/app/views/conferences/_header.haml index f40fe600..9a1316f6 100644 --- a/app/views/conferences/_header.haml +++ b/app/views/conferences/_header.haml @@ -26,11 +26,3 @@ - if venue.country != 'US' • = venue.country_name - - - unless conference.description.blank? - %section#about - .container - .row - .col-md-8.col-md-offset-2 - = markdown(conference.description, escape_html=false) - .trapezoid diff --git a/app/views/conferences/show.html.haml b/app/views/conferences/show.html.haml index c34aa3c1..3a73c0b0 100644 --- a/app/views/conferences/show.html.haml +++ b/app/views/conferences/show.html.haml @@ -41,9 +41,16 @@ - if @conference.code_of_conduct.present? = render 'code_of_conduct', organization: @conference.organization - -# header/description + -# header = render 'header', conference: @conference, venue: @conference.venue + -# description / happening now + - if @conference.splashpage.include_happening_now? || @conference.description.present? + = render 'about_and_happening_now', conference: @conference, + events_schedules: @events_schedules, pagy: @pagy, + events_schedules_length: @events_schedules_length, + events_schedules_limit: @events_schedules_limit + -# calls for content, or program - if @conference.splashpage.include_cfp = render 'call_for_content', conference: @conference, diff --git a/app/views/conferences/show.js.erb b/app/views/conferences/show.js.erb new file mode 100644 index 00000000..fb35b420 --- /dev/null +++ b/app/views/conferences/show.js.erb @@ -0,0 +1,5 @@ +$('#happening-now').html("<%= j(render 'happening_now', conference: @conference, + events_schedules: @events_schedules, pagy: @pagy, + events_schedules_length: @events_schedules_length, + events_schedules_limit: @events_schedules_limit)%>"); +Pagy.init(document.getElementById('happening-now')); diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml index 8df4efc1..c628275b 100644 --- a/app/views/devise/confirmations/new.html.haml +++ b/app/views/devise/confirmations/new.html.haml @@ -4,7 +4,7 @@ .panel.panel-default .panel-heading %h3.panel-title - Resend confirmation instructions + Resend account confirmation instructions .panel-body = semantic_form_for(resource, as: resource_name, url: confirmation_path(resource_name), method: :post) do |f| = f.input :email, input_html: { autofocus: true, required: true } diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb new file mode 100644 index 00000000..55318a91 --- /dev/null +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -0,0 +1,9 @@ +<%= render partial: "layouts/mailbot_header" %> +
Welcome to Snap!Con <%= @email %>!
+ +You can confirm your account email through the link below:
+ +<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
+Hello <%= @email %>!
+ +<% if @resource.try(:unconfirmed_email?) %> +We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.
+<% else %> +We're contacting you to notify you that your email has been changed to <%= @resource.email %>.
+<% end %> diff --git a/app/views/devise/mailer/password_change.html.erb b/app/views/devise/mailer/password_change.html.erb new file mode 100644 index 00000000..b41daf47 --- /dev/null +++ b/app/views/devise/mailer/password_change.html.erb @@ -0,0 +1,3 @@ +Hello <%= @resource.email %>!
+ +We're contacting you to notify you that your password has been changed.
diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb new file mode 100644 index 00000000..f667dc12 --- /dev/null +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +Hello <%= @resource.email %>!
+ +Someone has requested a link to change your password. You can do this through the link below.
+ +<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>
+ +If you didn't request this, please ignore this email.
+Your password won't change until you access the link above and create a new one.
diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb new file mode 100644 index 00000000..41e148bf --- /dev/null +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -0,0 +1,7 @@ +Hello <%= @resource.email %>!
+ +Your account has been locked due to an excessive number of unsuccessful sign in attempts.
+ +Click the link below to unlock your account:
+ +<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>
diff --git a/app/views/layouts/_mailbot_footer.html.erb b/app/views/layouts/_mailbot_footer.html.erb index 3cc8b3c3..4cc06c7f 100644 --- a/app/views/layouts/_mailbot_footer.html.erb +++ b/app/views/layouts/_mailbot_footer.html.erb @@ -1,12 +1,7 @@ - - - - - - - <%= stylesheet_link_tag "mailbot" %> - - - + <% if @conference.present? %> +