Browse Source

Workshop auth fixes and better error reporting

development
Godwin 7 years ago
parent
commit
e8ca73a801
  1. 4
      Gemfile
  2. 6
      Rakefile
  3. 6
      app/assets/javascripts/main.js
  4. 28
      app/assets/stylesheets/_application.scss
  5. 15
      app/assets/stylesheets/_settings.scss
  6. 66
      app/assets/stylesheets/user-mailer.scss
  7. 222
      app/controllers/application_controller.rb
  8. 5
      app/controllers/conferences_controller.rb
  9. 74
      app/controllers/workshops_controller.rb
  10. 30
      app/helpers/admin_helper.rb
  11. 46
      app/helpers/form_helper.rb
  12. 36
      app/mailers/user_mailer.rb
  13. 92
      app/views/user_mailer/error_report.html.haml
  14. 3
      app/views/workshops/facilitate.html.haml
  15. 5
      app/views/workshops/facilitate_request_sent.html.haml
  16. 4
      app/views/workshops/show.html.haml
  17. 7
      config/locales/en.yml
  18. 1
      config/locales/fr.yml
  19. 24
      db/schema.rb
  20. 21
      features/workshops.feature

4
Gemfile

@ -30,8 +30,8 @@ gem 'launchy'
# bundle config local.lingua_franca ../lingua_franca # bundle config local.lingua_franca ../lingua_franca
# bundle config local.marmara ../marmara # bundle config local.marmara ../marmara
gem 'bikecollectives_core', git: 'https://github.com/bikebike/bikecollectives_core.git', branch: 'master' gem 'bikecollectives_core', git: 'https://github.com/bikebike/bikecollectives_core.git', branch: 'master'
gem 'bumbleberry', git: 'https://github.com/bumbleberry/bumbleberry.git', branch: '2017' gem 'bumbleberry', git: 'https://github.com/bumbleberry/bumbleberry.git', branch: 'master'
gem 'lingua_franca', git: 'https://github.com/lingua-franca/lingua_franca.git', branch: '2017' gem 'lingua_franca', git: 'https://github.com/lingua-franca/lingua_franca.git', branch: 'master'
gem 'marmara', git: 'https://github.com/lingua-franca/marmara.git', branch: 'master' gem 'marmara', git: 'https://github.com/lingua-franca/marmara.git', branch: 'master'
# Bike!Bike! specific stuff # Bike!Bike! specific stuff

6
Rakefile

@ -135,3 +135,9 @@ namespace :cucumber do
raise exception unless exception.nil? raise exception unless exception.nil?
end end
end end
namespace :clean do
task requests: :environment do
Request.delete_all(['created_at < ?', DateTime.now - 1.day])
end
end

6
app/assets/javascripts/main.js

@ -1,5 +1,5 @@
(function() { (function() {
window.onerror = function(message, url, lineNumber) { window.onerror = function(message, url, lineNumber, column, errorObj) {
//save error and send to server for example. //save error and send to server for example.
var request = new XMLHttpRequest(); var request = new XMLHttpRequest();
request.open('POST', '/js_error', true); request.open('POST', '/js_error', true);
@ -7,7 +7,9 @@
request.send( request.send(
'message=' + encodeURI(message) + 'message=' + encodeURI(message) +
'&url=' + encodeURI(url) + '&url=' + encodeURI(url) +
'&lineNumber=' + encodeURI(lineNumber) + '&line=' + encodeURI(lineNumber) +
'&col=' + encodeURI(column) +
(errorObj && errorObj.stack ? '&stack=' + encodeURI(errorObj.stack) : '') +
'&location=' + encodeURI(window.location.href) '&location=' + encodeURI(window.location.href)
); );
return false; return false;

28
app/assets/stylesheets/_application.scss

@ -803,26 +803,22 @@ ul.menu {
.message { .message {
display: inline-block; display: inline-block;
@include font-family(secondary);
font-size: 1.25em; font-size: 1.25em;
padding: 1em 2em; padding: 1em 2em;
border: 0.25rem solid $blue; background-color: $text-on-blue;
background-color: rgba($blue, 0.25); @include message;
} }
&.error .message { &.error .message {
border-color: $red; background-color: $text-on-red;
background-color: rgba($red, 0.25);
} }
&.complete .message { &.complete .message {
border-color: $green; background-color: $text-on-green;
background-color: rgba($green, 0.25);
} }
&.warning .message { &.warning .message {
border-color: $yellow; background-color: $text-on-yellow;
background-color: rgba($yellow, 0.25);
} }
} }
@ -1732,14 +1728,12 @@ ul.warnings {
.info-message { .info-message {
position: relative; position: relative;
min-height: 4em; min-height: 4em;
border: 0.2em solid rgba(0, 0, 0, 0.1); background-color: $text-on-yellow;
background-color: rgba($colour-3, 0.333);
padding: 1em 1em 1em 4em; padding: 1em 1em 1em 4em;
@include font-family(secondary);
text-align: left; text-align: left;
margin: 1em; margin: 1em;
width: auto; width: auto;
@include default-box-shadow(top, 2); @include message;
@include before { @include before {
content: '!'; content: '!';
@ -1754,7 +1748,7 @@ ul.warnings {
text-align: center; text-align: center;
line-height: 1.5em; line-height: 1.5em;
font-size: 1.5em; font-size: 1.5em;
background-color: $colour-3; background-color: $yellow;
@include _(border-radius, 50%); @include _(border-radius, 50%);
@include default-box-shadow(top, 2); @include default-box-shadow(top, 2);
} }
@ -1769,15 +1763,15 @@ ul.warnings {
} }
.success-info { .success-info {
background-color: rgba($colour-5, 0.333); background-color: $text-on-green;
@include before { @include before {
background-color: $colour-5; background-color: $green;
} }
} }
.error-info { .error-info {
background-color: rgba($red, 0.333); background-color: $text-on-red;
@include before { @include before {
background-color: $red; background-color: $red;

15
app/assets/stylesheets/_settings.scss

@ -28,6 +28,13 @@ $yellow: $colour-3;
$orange: $colour-4; $orange: $colour-4;
$green: $colour-5; $green: $colour-5;
$text-on-blue: rgba($blue, 0.333);
$text-on-pink: rgba($pink, 0.333);
$text-on-yellow: rgba($yellow, 0.333);
$text-on-orange: rgba($orange, 0.333);
$text-on-green: rgba($green, 0.333);
$text-on-red: rgba($red, 0.333);
$link-colour: darken($colour-1, 13%); $link-colour: darken($colour-1, 13%);
$selected-colour: rgba($blue, 0.5); $selected-colour: rgba($blue, 0.5);
@ -105,7 +112,7 @@ $selected-colour: rgba($blue, 0.5);
} }
@mixin text-stroke { @mixin text-stroke {
@include _(text-stroke, 1px rgba(0, 0, 0, 0.25)); @include _(text-stroke, 1px rgba($black, 0.25));
} }
@mixin button { @mixin button {
@ -174,3 +181,9 @@ $selected-colour: rgba($blue, 0.5);
@mixin not-link-like { @mixin not-link-like {
text-decoration: none; text-decoration: none;
} }
@mixin message {
@include font-family(secondary);
border: 0.2em solid rgba($black, 0.1);
@include default-box-shadow(top, 2);
}

66
app/assets/stylesheets/user-mailer.scss

@ -1,5 +1,7 @@
@import "settings"; @import "settings";
$max-table-width: 512px;
body { body {
width: 100% !important; width: 100% !important;
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
@ -76,6 +78,10 @@ p {
p, blockquote { p, blockquote {
margin: 1em; margin: 1em;
line-height: 1.3333em; line-height: 1.3333em;
&.signature {
margin: 0 0 1em;
}
} }
blockquote { blockquote {
@ -127,7 +133,7 @@ h1, h2, h3, h4, h5, h6 {
table { table {
border-collapse: collapse; border-collapse: collapse;
max-width: 512px; max-width: $max-table-width;
min-width: 280px; min-width: 280px;
mso-table-lspace: 0pt; mso-table-lspace: 0pt;
mso-table-rspace: 0pt; mso-table-rspace: 0pt;
@ -179,16 +185,34 @@ table#ecxbb_full_width {
} }
code { code {
color: #C33; color: firebrick;
font-size: 0.9em; white-space: pre;
margin: 0 0 2em;
display: block;
max-width: $max-table-width;
overflow: auto;
box-sizing: border-box;
&.signature {
margin: 0 0 1em;
font-size: 1.5em;
color: navy;
text-align: left;
white-space: normal;
overflow: hidden;
}
&.backtrace {
padding: 1em;
border: 1px solid #CCC;
background-color: lemonchiffon;
}
} }
pre { pre {
font-size: 1.5em; font-size: 1.5em;
padding: 1em; padding: 1em;
background-color: #333; word-break: break;
color: antiquewhite;
word-break: break-word;
} }
.diff, .ecxdiff { .diff, .ecxdiff {
@ -240,8 +264,8 @@ h3 {
padding: 10px 15px; padding: 10px 15px;
margin-left: 20px; margin-left: 20px;
border-bottom: 3px solid darken($colour-5, 10%); border-bottom: 3px solid darken($colour-5, 10%);
-webkit-box-shadow: 0 0.5em 1.5em -0.75em #000; -webkit-box-shadow: 0 0.5em 1.5em -0.75em $black;
box-shadow: 0 0.5em 1.5em -0.75em #000; box-shadow: 0 0.5em 1.5em -0.75em $black;
cursor: pointer !important; cursor: pointer !important;
} }
@ -266,3 +290,29 @@ h3 {
} }
} }
} }
.request-details {
width: 100%;
max-width: 800px;
overflow: auto;
th, td {
border: 1px solid #CCC;
font-size: 1em;
padding: 0.5em;
vertical-align: top;
font-family: monospace;
}
table tr {
background-color: aliceblue;
}
.spacer {
background-color: transparent;
td {
border: 0;
}
}
}

222
app/controllers/application_controller.rb

@ -1,35 +1,53 @@
class ApplicationController < BaseController class ApplicationController < BaseController
protect_from_forgery with: :exception, :except => [:do_confirm, :js_error, :admin_update] protect_from_forgery with: :exception, :except => [:do_confirm, :js_error, :admin_update]
before_filter :capture_page_info before_filter :application_setup
after_filter :capture_page_info
helper_method :protect, :policies helper_method :protect, :policies
# @@test_host
# @@test_location
def default_url_options def default_url_options
{ host: "#{request.protocol}#{request.host_with_port}", trailing_slash: true } { host: "#{request.protocol}#{request.host_with_port}", trailing_slash: true }
end end
def capture_page_info def capture_page_info
# capture request info in case an error occurs capture_response unless @user_type == :potential_bot || @user_type == :bot
# if request.method == "GET" && (params[:controller] != 'application' || params[:action] != 'contact') end
# session[:last_request]
# request_info = { def capture_response(response_code = nil)
# 'params' => params, Request.create(
# 'request' => { request_id: request.uuid,
# 'remote_ip' => request.remote_ip, session_id: session.id,
# 'uuid' => request.uuid, application: :bikebike,
# 'original_url' => request.original_url, response: (response_code || response.code || 0).to_i,
# 'env' => Hash.new data: {
# } user: logged_in? ? current_user.id : nil,
# } params: @original_params || params,
# request.env.each do |key, value| remote_ip: request.remote_ip,
# request_info['request']['env'][key.to_s] = value.to_s request_method: request.method,
# end url: request.original_url,
# # session['request_info'] = request_info user_agent: request.user_agent,
# end language: request.env['HTTP_ACCEPT_LANGUAGE'],
cookies: request.env['HTTP_COOKIE'],
requested_with: request.env['HTTP_X_REQUESTED_WITH']
})
@error_reports.each { |report| report_on(report) } if @error_reports
end
def report_on(report)
return if Rails.env.development? || Rails.env.test?
send_mail(:error_report, report.signature)
end
def application_setup
if request.user_agent =~ /Googlebot/
@user_type = :bot
elsif request.url =~ /^.*\.php(\?.*)?$/
@user_type = :potential_bot
else
@user_type = :normal
end
# get the current conferences and set them globally # get the current conferences and set them globally
status_hierarchy = { status_hierarchy = {
@ -82,51 +100,6 @@ class ApplicationController < BaseController
@is_policy_page = true @is_policy_page = true
end end
def js_error
# send and email if this is production
report = "A JavaScript error has occurred on <code>#{params[:location]}</code>"
if params[:location] == params[:url]
report += " on line <code>#{params[:lineNumber]}</code>"
else
report += " in <code>#{params[:url]}:#{params[:lineNumber]}</code>"
end
begin
# log the error
logger.info "A JavaScript error has occurred on #{params[:location]}:#{params[:lineNumber]}: #{params[:message]}"
if Rails.env.preview? || Rails.env.production?
# don't worry about bots
unless request.user_agent =~ /Googlebot/
request_info = {
'remote_ip' => request.remote_ip,
'uuid' => request.uuid,
'original_url' => request.original_url,
'env' => Hash.new
}
request.env.each do |key, value|
request_info['env'][key.to_s] = value.to_s
end
send_mail(:error_report,
"A JavaScript error has occurred",
report,
params[:message],
nil,
request_info,
params,
current_user,
Time.now.strftime("%d/%m/%Y %H:%M")
)
end
end
rescue Exception => exception2
logger.info exception2.to_s
logger.info exception2.backtrace.join("\n")
end
render json: {}
end
def confirmation_sent(user) def confirmation_sent(user)
template = 'login_confirmation_sent' template = 'login_confirmation_sent'
@page_title ||= 'page_titles.403.Please_Check_Email' @page_title ||= 'page_titles.403.Please_Check_Email'
@ -150,6 +123,7 @@ class ApplicationController < BaseController
def locale_not_available!(locale = nil) def locale_not_available!(locale = nil)
set_default_locale set_default_locale
@original_params = params.clone
params[:_original_action] = params[:action] params[:_original_action] = params[:action]
params[:action] = 'error-locale-not-available' params[:action] = 'error-locale-not-available'
@page_title = 'page_titles.404.Locale_Not_Available' @page_title = 'page_titles.404.Locale_Not_Available'
@ -167,6 +141,28 @@ class ApplicationController < BaseController
render 'application/locale_not_available', status: 404 render 'application/locale_not_available', status: 404
end end
def on_error(report, exception = nil)
@error_reports ||= []
@error_reports << report
logger.info report.backtrace
raise exception if exception.present? && Rails.env.development?
end
def js_error
stack = params[:stack] || "#{params[:message]}\n\tat #{params[:url] || params[:location]}:#{params[:line]}:#{params[:col]}"
requests = Request.where(session_id: session.id).order("created_at DESC")
on_error(
Report.create(
request_id: requests.first.request_id,
signature: params[:message],
severity: :error,
source: :javascript,
backtrace: stack))
render json: {}
end
unless Rails.env.test? unless Rails.env.test?
rescue_from StandardError do |exception| rescue_from StandardError do |exception|
handle_exception exception handle_exception exception
@ -177,37 +173,31 @@ class ApplicationController < BaseController
end end
def handle_exception(exception) def handle_exception(exception)
# log the error # remove memory location from anonymous classes so tat we have a common signature
logger.info exception.to_s classMatcher = /#<(.*?):0x[0-9a-f]+>/
logger.info exception.backtrace.join("\n") message = exception.message
message.gsub!(classMatcher, '\1') while message =~ classMatcher
stack = ([message] + exception.backtrace).join("\n ")
# send and email if this is production on_error(
if Rails.env.preview? || Rails.env.production? Report.create(
suppress(Exception) do request_id: request.uuid,
request_info = { signature: message,
'remote_ip' => request.remote_ip, severity: :error,
'uuid' => request.uuid, source: :application,
'original_url' => request.original_url, backtrace: stack), exception)
'env' => Hash.new
}
request.env.each do |key, value|
request_info['env'][key.to_s] = value.to_s
end
send_mail(:error_report,
"An error has occurred in #{Rails.env}",
nil,
exception.to_s,
exception.backtrace.join("\n"),
request_info,
params,
current_user,
Time.now.strftime("%d/%m/%Y %H:%M")
)
end
end end
# raise the error if we are in development so that we can debug it def i18n_exception(str, exception, locale, key)
raise exception if Rails.env.development? message = "#{exception.class.name}: #{exception.to_s}"
stack = "#{message}\n #{caller.join("\n ")}"
on_error(
Report.create(
request_id: request.uuid,
signature: message,
severity: :error,
source: :i18n,
backtrace: stack))
end end
def protect(&block) def protect(&block)
@ -291,6 +281,7 @@ class ApplicationController < BaseController
end end
def error_404(args = {}) def error_404(args = {})
@original_params = params.clone
params[:_original_action] = params[:action] params[:_original_action] = params[:action]
params[:action] = 'error-404' params[:action] = 'error-404'
@page_title = 'page_titles.404.Page_Not_Found' @page_title = 'page_titles.404.Page_Not_Found'
@ -308,6 +299,7 @@ class ApplicationController < BaseController
@template = template @template = template
@page_title ||= 'page_titles.403.Access_Denied' @page_title ||= 'page_titles.403.Access_Denied'
@main_title ||= @page_title @main_title ||= @page_title
@original_params = params.clone
params[:_original_action] = params[:action] params[:_original_action] = params[:action]
params[:action] = 'error-403' params[:action] = 'error-403'
@ -317,11 +309,13 @@ class ApplicationController < BaseController
def error_500(exception = nil) def error_500(exception = nil)
@page_title = 'page_titles.500.An_Error_Occurred' @page_title = 'page_titles.500.An_Error_Occurred'
@main_title = 'error.500.title' @main_title = 'error.500.title'
@original_params = params.clone
params[:_original_action] = params[:action] params[:_original_action] = params[:action]
params[:action] = 'error-500' params[:action] = 'error-500'
@exception = exception @exception = exception
super(exception) super(exception)
capture_response(500)
end end
def on_translation_change(object, data, locale, translator_id) def on_translation_change(object, data, locale, translator_id)
@ -353,39 +347,6 @@ class ApplicationController < BaseController
end end
end end
def i18n_exception(str, exception, locale, key)
# log it
logger.info "Missing translation found for: #{key}"
# send an email if this is production
if Rails.env.preview? || Rails.env.production?
begin
request_info = {
'remote_ip' => request.remote_ip,
'uuid' => request.uuid,
'original_url' => request.original_url,
'env' => Hash.new
}
request.env.each do |key, value|
request_info['env'][key.to_s] = value.to_s
end
send_mail(:error_report,
"A missing translation found in #{Rails.env}",
"<p>A translation for <code>#{key}</code> in <code>#{locale.to_s}</code> was found. The text that was rendered to the user was:</p><blockquote>#{str || 'nil'}</blockquote>",
exception.to_s,
nil,
request_info,
params,
current_user.id,
Time.now.strftime("%d/%m/%Y %H:%M")
)
rescue Exception => exception2
logger.info exception2.to_s
logger.info exception2.backtrace.join("\n")
end
end
end
def set_success_message(message, is_ajax = false) def set_success_message(message, is_ajax = false)
if is_ajax if is_ajax
@success_message = message @success_message = message
@ -613,10 +574,15 @@ class ApplicationController < BaseController
end end
def set_conference_registration! def set_conference_registration!
@registration = set_conference_registration set_conference_registration
raise ActiveRecord::PremissionDenied unless @registration.present? raise ActiveRecord::PremissionDenied unless @registration.present?
end end
def ensure_registration_is_complete!
set_conference_registration!
raise ActiveRecord::PremissionDenied unless @registration.registered?
end
def set_or_create_conference_registration def set_or_create_conference_registration
set_conference_registration set_conference_registration
return @registration if @registration.present? return @registration if @registration.present?

5
app/controllers/conferences_controller.rb

@ -82,6 +82,11 @@ class ConferencesController < ApplicationController
# get the current step # get the current step
@step = current_registration_step(@this_conference, current_user) @step = current_registration_step(@this_conference, current_user)
if @update_status.nil? && flash[:status_message].present?
@update_status = flash[:status_message][:status]
@update_message = flash[:status_message][:message]
end
if @step == :payment_form && (params[:token].present? || @test_token.present?) if @step == :payment_form && (params[:token].present? || @test_token.present?)
result = paypal_payment_confirm(@this_conference, current_user, params) result = paypal_payment_confirm(@this_conference, current_user, params)
data_to_instance_variables(result) data_to_instance_variables(result)

74
app/controllers/workshops_controller.rb

@ -26,7 +26,8 @@ class WorkshopsController < ApplicationController
def create_workshop def create_workshop
set_conference set_conference
set_conference_registration! ensure_registration_is_complete!
@workshop = Workshop.new @workshop = Workshop.new
@languages = [I18n.locale.to_sym] @languages = [I18n.locale.to_sym]
@needs = [] @needs = []
@ -47,7 +48,7 @@ class WorkshopsController < ApplicationController
def edit_workshop def edit_workshop
set_conference set_conference
set_conference_registration! ensure_registration_is_complete!
@workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) @workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless @workshop.present? return do_404 unless @workshop.present?
@ -82,7 +83,7 @@ class WorkshopsController < ApplicationController
def delete_workshop def delete_workshop
set_conference set_conference
set_conference_registration! ensure_registration_is_complete!
@workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) @workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless @workshop.present? return do_404 unless @workshop.present?
@ -95,9 +96,9 @@ class WorkshopsController < ApplicationController
@workshop.destroy @workshop.destroy
end end
return redirect_to register_step_path(@this_conference.slug, 'workshops') return redirect_to workshops_path(@this_conference.slug)
end end
return redirect_to view_workshop_url(@this_conference.slug, @workshop.id) return redirect_to view_workshop_path(@this_conference.slug, @workshop.id)
end end
@register_template = :workshops @register_template = :workshops
@ -106,13 +107,13 @@ class WorkshopsController < ApplicationController
def save_workshop def save_workshop
set_conference set_conference
set_conference_registration! ensure_registration_is_complete!
if params[:button].to_sym != :save if params[:button].to_sym != :save
if params[:workshop_id].present? if params[:workshop_id].present?
return redirect_to view_workshop_url(@this_conference.slug, params[:workshop_id]) return redirect_to view_workshop_path(@this_conference.slug, params[:workshop_id])
end end
return redirect_to register_step_path(@this_conference.slug, 'workshops') return redirect_to workshops_path(@this_conference.slug)
end end
if params[:workshop_id].present? if params[:workshop_id].present?
@ -120,8 +121,8 @@ class WorkshopsController < ApplicationController
return do_404 unless workshop.present? return do_404 unless workshop.present?
can_edit = workshop.can_edit?(current_user) can_edit = workshop.can_edit?(current_user)
else else
workshop = Workshop.new(:conference_id => @this_conference.id) workshop = Workshop.new(conference_id: @this_conference.id)
workshop.workshop_facilitators = [WorkshopFacilitator.new(:user_id => current_user.id, :role => :creator)] workshop.workshop_facilitators = [WorkshopFacilitator.new(user_id: current_user.id, role: :creator)]
can_edit = true can_edit = true
end end
@ -157,27 +158,27 @@ class WorkshopsController < ApplicationController
workshop.save workshop.save
# Rouge nil facilitators have been know to be created, just destroy them here now # Rouge nil facilitators have been know to be created, just destroy them here now
WorkshopFacilitator.where(:user_id => nil).destroy_all WorkshopFacilitator.where(user_id: nil).destroy_all
else else
return do_403 return do_403
end end
redirect_to view_workshop_url(@this_conference.slug, workshop.id) redirect_to view_workshop_path(@this_conference.slug, workshop.id)
end end
def toggle_workshop_interest def toggle_workshop_interest
set_conference set_conference
set_conference_registration! ensure_registration_is_complete!
workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless workshop return do_404 unless workshop
# save the current state # save the current state
interested = workshop.interested? current_user interested = workshop.interested? current_user
# remove all associated fields # remove all associated fields
WorkshopInterest.delete_all(:workshop_id => workshop.id, :user_id => current_user.id) WorkshopInterest.delete_all(workshop_id: workshop.id, user_id: current_user.id)
# creat the new interest row if we weren't interested before # creat the new interest row if we weren't interested before
WorkshopInterest.create(:workshop_id => workshop.id, :user_id => current_user.id) unless interested WorkshopInterest.create(workshop_id: workshop.id, user_id: current_user.id) unless interested
if request.xhr? if request.xhr?
render json: [ render json: [
@ -192,13 +193,13 @@ class WorkshopsController < ApplicationController
] ]
else else
# go back to the workshop # go back to the workshop
redirect_to view_workshop_url(@this_conference.slug, workshop.id) redirect_to view_workshop_path(@this_conference.slug, workshop.id)
end end
end end
def facilitate_workshop def facilitate_workshop
set_conference set_conference
set_conference_registration! ensure_registration_is_complete!
@workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) @workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless @workshop return do_404 unless @workshop
return do_403 if @workshop.facilitator?(current_user) || !current_user return do_403 if @workshop.facilitator?(current_user) || !current_user
@ -209,7 +210,7 @@ class WorkshopsController < ApplicationController
def facilitate_request def facilitate_request
set_conference set_conference
set_conference_registration! ensure_registration_is_complete!
workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless workshop return do_404 unless workshop
return do_403 if workshop.facilitator?(current_user) || !current_user return do_403 if workshop.facilitator?(current_user) || !current_user
@ -219,12 +220,12 @@ class WorkshopsController < ApplicationController
send_mail(:workshop_facilitator_request, workshop.id, current_user.id, params[:message]) send_mail(:workshop_facilitator_request, workshop.id, current_user.id, params[:message])
redirect_to sent_facilitate_workshop_url(@this_conference.slug, workshop.id) redirect_to sent_facilitate_workshop_path(@this_conference.slug, workshop.id)
end end
def sent_facilitate_request def sent_facilitate_request
set_conference set_conference
set_conference_registration! ensure_registration_is_complete!
@workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) @workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless @workshop return do_404 unless @workshop
return do_403 unless @workshop.requested_collaborator?(current_user) return do_403 unless @workshop.requested_collaborator?(current_user)
@ -236,7 +237,7 @@ class WorkshopsController < ApplicationController
def approve_facilitate_request def approve_facilitate_request
return do_403 unless logged_in? return do_403 unless logged_in?
set_conference set_conference
set_conference_registration! ensure_registration_is_complete!
workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless workshop.present? return do_404 unless workshop.present?
@ -253,24 +254,24 @@ class WorkshopsController < ApplicationController
LinguaFranca.with_locale(user.locale) do LinguaFranca.with_locale(user.locale) do
send_mail(:workshop_facilitator_request_approved, workshop.id, user.id) send_mail(:workshop_facilitator_request_approved, workshop.id, user.id)
end end
return redirect_to view_workshop_url(@this_conference.slug, workshop.id) return redirect_to view_workshop_path(@this_conference.slug, workshop.id)
end end
when :deny when :deny
if workshop.active_facilitator?(current_user) && workshop.requested_collaborator?(User.find(user_id)) if workshop.active_facilitator?(current_user) && workshop.requested_collaborator?(User.find(user_id))
WorkshopFacilitator.delete_all( WorkshopFacilitator.delete_all(
:workshop_id => workshop.id, workshop_id: workshop.id,
:user_id => user_id) user_id: user_id)
LinguaFranca.with_locale user.locale do LinguaFranca.with_locale user.locale do
send_mail(:workshop_facilitator_request_denied, workshop.id, user.id) send_mail(:workshop_facilitator_request_denied, workshop.id, user.id)
end end
return redirect_to view_workshop_url(@this_conference.slug, workshop.id) return redirect_to view_workshop_path(@this_conference.slug, workshop.id)
end end
when :remove when :remove
if workshop.can_remove?(current_user, user) if workshop.can_remove?(current_user, user)
WorkshopFacilitator.delete_all( WorkshopFacilitator.delete_all(
:workshop_id => workshop.id, workshop_id: workshop.id,
:user_id => user_id) user_id: user_id)
return redirect_to view_workshop_url(@this_conference.slug, workshop.id) return redirect_to view_workshop_path(@this_conference.slug, workshop.id)
end end
when :switch_ownership when :switch_ownership
if workshop.creator?(current_user) if workshop.creator?(current_user)
@ -282,7 +283,7 @@ class WorkshopsController < ApplicationController
workshop.id, user_id) workshop.id, user_id)
f.role = :creator f.role = :creator
f.save f.save
return redirect_to view_workshop_url(@this_conference.slug, workshop.id) return redirect_to view_workshop_path(@this_conference.slug, workshop.id)
end end
end end
@ -291,7 +292,7 @@ class WorkshopsController < ApplicationController
def add_workshop_facilitator def add_workshop_facilitator
set_conference set_conference
set_conference_registration! ensure_registration_is_complete!
user = User.find_user(params[:email]) user = User.find_user(params[:email])
@ -313,12 +314,12 @@ class WorkshopsController < ApplicationController
end end
end end
return redirect_to view_workshop_url(@this_conference.slug, params[:workshop_id]) return redirect_to view_workshop_path(@this_conference.slug, params[:workshop_id])
end end
def add_comment def add_comment
set_conference set_conference
set_conference_registration! ensure_registration_is_complete!
workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless workshop && current_user return do_404 unless workshop && current_user
@ -335,7 +336,7 @@ class WorkshopsController < ApplicationController
elsif params[:button] = 'add_comment' elsif params[:button] = 'add_comment'
new_comment = workshop.add_comment(current_user, params[:comment]) new_comment = workshop.add_comment(current_user, params[:comment])
workshop.active_facilitators.each do | u | workshop.active_facilitators.each do |u|
unless u.id == current_user.id unless u.id == current_user.id
LinguaFranca.with_locale u.locale do LinguaFranca.with_locale u.locale do
send_mail(:workshop_comment, workshop.id, new_comment.id, u.id) send_mail(:workshop_comment, workshop.id, new_comment.id, u.id)
@ -346,20 +347,21 @@ class WorkshopsController < ApplicationController
return do_404 return do_404
end end
return redirect_to view_workshop_url(@this_conference.slug, workshop.id, anchor: "comment-#{new_comment.id}") return redirect_to view_workshop_path(@this_conference.slug, workshop.id, anchor: "comment-#{new_comment.id}")
end end
rescue_from ActiveRecord::PremissionDenied do |exception| rescue_from ActiveRecord::PremissionDenied do |exception|
if !@this_conference.can_register? if !@this_conference.can_register?
do_404 do_404
elsif logged_in? elsif logged_in?
redirect_to 'conferences/register' flash[:status_message] = { message: :registration_required, status: :warning }
redirect_to register_path(@this_conference.slug)
else else
@register_template = :confirm_email @register_template = :confirm_email
@page_title = "articles.conference_registration.headings.#{@this_conference.registration_status == :open ? '': 'Pre_'}Registration_Details" @page_title = "articles.conference_registration.headings.#{@this_conference.registration_status == :open ? '': 'Pre_'}Registration_Details"
@main_title = "articles.conference_registration.headings.#{@this_conference.registration_status == :open ? '': 'Pre_'}Register" @main_title = "articles.conference_registration.headings.#{@this_conference.registration_status == :open ? '': 'Pre_'}Register"
@main_title_vars = { vars: { title: @this_conference.title } } @main_title_vars = { vars: { title: @this_conference.title } }
render 'conferences/register' render register_path(@this_conference.slug)
end end
end end

30
app/helpers/admin_helper.rb

@ -59,8 +59,8 @@ module AdminHelper
def get_administration_group(administration_step) def get_administration_group(administration_step)
admin_step = administration_step.to_sym admin_step = administration_step.to_sym
admin_step = administration_sub_steps[admin_step] if administration_sub_steps[admin_step].present? admin_step = administration_sub_steps[admin_step] if administration_sub_steps[admin_step].present?
administration_steps.each do | group, steps | administration_steps.each do |group, steps|
steps.each do | step | steps.each do |step|
return group if step == admin_step return group if step == admin_step
end end
end end
@ -75,35 +75,35 @@ module AdminHelper
case to.to_sym case to.to_sym
when :registered when :registered
ConferenceRegistration.where(conference_id: conference.id).each do | r | ConferenceRegistration.where(conference_id: conference.id).each do |r|
users << r.user if ((r.steps_completed || []).include? 'questions') && r.user.present? && r.is_attending != 'n' users << r.user if r.registered? && r.user.present? && r.attending?
end end
when :pre_registered when :pre_registered
ConferenceRegistration.where(conference_id: conference.id).each do | r | ConferenceRegistration.where(conference_id: conference.id).each do |r|
users << r.user if registration_status(r) == :preregistered && r.is_attending != 'n' users << r.user if r.attending?
end end
when :workshop_facilitators when :workshop_facilitators
user_hash = {} user_hash = {}
Workshop.where(conference_id: conference.id).each do | w | Workshop.where(conference_id: conference.id).each do |w|
w.active_facilitators.each do | u | w.active_facilitators.each do |u|
user_hash[u.id] ||= u if u.present? user_hash[u.id] ||= u if u.present?
end end
end end
users = user_hash.values users = user_hash.values
when :unregistered when :unregistered
ConferenceRegistration.where(conference_id: conference.id).each do | r | ConferenceRegistration.where(conference_id: conference.id).each do |r|
users << r.user if registration_status(r) == :unregistered && r.is_attending != 'n' users << r.user if !r.registered? && r.attending?
end end
when :housing_providers when :housing_providers
ConferenceRegistration.where(conference_id: conference.id, can_provide_housing: true).each do | r | ConferenceRegistration.where(conference_id: conference.id, can_provide_housing: true).each do |r|
users << r.user if r.user.present? users << r.user if r.user.present?
end end
when :guests when :guests
ConferenceRegistration.where(conference_id: conference.id, housing: 'house').each do | r | ConferenceRegistration.where(conference_id: conference.id, housing: 'house').each do |r|
users << r.user if r.user.present? && r.is_attending != 'n' users << r.user if r.user.present? && r.attending?
end end
when :all when :all
User.all.each do | u | User.all.each do |u|
users << u if u.present? && (u.is_subscribed.nil? || u.is_subscribed) users << u if u.present? && (u.is_subscribed.nil? || u.is_subscribed)
end end
end end
@ -151,7 +151,7 @@ module AdminHelper
end end
end end
(((((@schedule[@day] || {})[:times] || {})[@time] || {})[:item] || {})[:workshops] || {}).each do | l, w | (((((@schedule[@day] || {})[:times] || {})[@time] || {})[:item] || {})[:workshops] || {}).each do |l, w|
if w[:workshop].id != workshop.id if w[:workshop].id != workshop.id
f_a = w[:workshop].active_facilitators.map { | f | f.id } f_a = w[:workshop].active_facilitators.map { | f | f.id }
f_b = workshop.active_facilitators.map { | f | f.id } f_b = workshop.active_facilitators.map { | f | f.id }

46
app/helpers/form_helper.rb

@ -704,58 +704,14 @@ module FormHelper
return options return options
end end
def registration_step_menu
steps = current_registration_steps(@registration)
return '' unless steps.present? && steps.length > 1
pre_registration_steps = ''
post_registration_steps = ''
post_registration = false
steps.each do |step|
text = _"articles.conference_registration.headings.#{step[:name].to_s}"
if step[:name] == :workshops
post_registration = true
end
h = content_tag :li, class: [step[:enabled] ? :enabled : nil, @register_template == step[:name] ? :current : nil, post_registration ? :post : :pre].compact do
if step[:enabled]
content_tag :div, (link_to text, register_step_path(@this_conference.slug, step[:name])).html_safe, class: :step
else
content_tag :div, text, class: :step
end
end
if post_registration
post_registration_steps += h.html_safe
else
pre_registration_steps += h.html_safe
end
end
html = (
row class: 'flow-steps' do
columns do
(content_tag :ul, id: 'registration-steps' do
pre_registration_steps.html_safe +
post_registration_steps.html_safe
end).html_safe
end
end
)
return html.html_safe
end
def broadcast_options(conference = nil) def broadcast_options(conference = nil)
conference ||= @this_conference || @conference conference ||= @this_conference || @conference
options = [ options = [
:registered, :registered,
:pre_registered, :pre_registered,
:workshop_facilitators,
:unregistered, :unregistered,
:workshop_facilitators,
:housing_providers, :housing_providers,
:guests, :guests,
:all :all

36
app/mailers/user_mailer.rb

@ -123,15 +123,29 @@ class UserMailer < ActionMailer::Base
mail to: @user.named_email, subject: clean_subject(subject) mail to: @user.named_email, subject: clean_subject(subject)
end end
def error_report(subject, message, report, exception, request, params, user, time = nil) def error_report(report_signature)
@message = message @reports = Report.where(signature: report_signature).order('created_at DESC')
@report = report @report = @reports.first
@exception = exception
@request = request return unless @report.present?
@params = params
@time = time @title = case @report.source.to_sym
@user = User.find(user) if user.present? when :javascript
mail to: 'goodgodwin@hotmail.com', subject: clean_subject(subject) "JavaScript fatal report"
when :i18n
"Missing translation report"
else
"Fatal report"
end
subject = "#{@title}: #{report_signature}"
@request = Request.find_by_request_id(@report.request_id)
return unless @request.present?
@user = User.find(@request.data['user'].to_i) if @request.data['user'].present?
mail to: administrators, subject: clean_subject(subject)
end end
def contact(from, subject, message, email_list) def contact(from, subject, message, email_list)
@ -172,4 +186,8 @@ class UserMailer < ActionMailer::Base
@subject = subject @subject = subject
return subject return subject
end end
def administrators
User.where(role: :administrator).map(&:named_email).join(',')
end
end end

92
app/views/user_mailer/error_report.html.haml

@ -1,53 +1,43 @@
%p=@message.html_safe if @message.present? %h1=@title
%pre=@report
- if @exception.present? %h2 Error details
%h1 Backtrace %code.signature{class: "#{@report.source}-signature"}
%pre=@exception = "#{@report.signature}"
%code.backtrace{class: "#{@report.source}-backtrace"}=@report.backtrace
%h1 Details %h2 Request details
%table.error-report .request-details
%tr %table
%th Key %tr
%th Value %th Response code
%tr %td=@request.response
%td User ID - @request.data.each do |key, value|
%td=@user.present? ? @user.id : '' - if key.to_s == 'user'
%tr - if @user
%td User Name %tr
%td=@user.present? ? @user.name : '' %th User ID
%tr %td=@user.id
%td User Email %tr
%td=@user.present? ? @user.email : '' %th User Email
%tr %td=@user.email
%td IP Address %tr
%td=@request['remote_ip'] %th User Name
%tr %td=@user.firstname
%td UUID - else
%td=@request['uuid'] %tr
%tr %th User ID
%td URL %td NULL
%td=@request['original_url'] %tr.spacer
%tr %td{ colspan: 2 }
%td Time - else
%td=@time %tr
%th=key.to_s.titlecase
%h1 Params %td
%table.error-report - if value.is_a?(Hash)
%tr %table
%th Key - value.each do |k, v|
%th Value %tr
- @params.each do | key, value | %th=k
%tr %td=v
%td=key - else
%td=value = value.to_s
%h1 Request Environment
%table.error-report
%tr
%th Key
%th Value
- @request['env'].each do | key, value |
%tr
%td=key
%td=value

3
app/views/workshops/facilitate.html.haml

@ -1,11 +1,10 @@
= render 'conferences/page_header', page_key: 'Facilitate_Workshop' = render 'conferences/page_header', page_key: 'Facilitate_Workshop'
%article %article
= row do = row do
= form_tag facilitate_workshop_request_path(@this_conference.slug, @workshop.id), class: 'composition' do = form_tag facilitate_workshop_request_path(@this_conference.slug, @workshop.id), class: :composition do
= (hidden_field_tag :workshop_id, @workshop.id) if @workshop = (hidden_field_tag :workshop_id, @workshop.id) if @workshop
= columns(medium: 12) do = columns(medium: 12) do
%h2=_'articles.workshops.headings.facilitate', "Request to Facilitate ‘#{@workshop.title}’", vars: { workshop_title: @workshop.title } %h2=_'articles.workshops.headings.facilitate', "Request to Facilitate ‘#{@workshop.title}’", vars: { workshop_title: @workshop.title }
= registration_step_menu
=_('articles.workshops.paragraphs.facilitate_request','Please tell the current workshop facilitators who you are, why you want to help facilitate the workshop, and how you think you will help make the workshop better. All of the current facilitators will be emailed and they may ask more questions before approving or denying your request. Please note that this will reveal your email address to the facilitators.') =_('articles.workshops.paragraphs.facilitate_request','Please tell the current workshop facilitators who you are, why you want to help facilitate the workshop, and how you think you will help make the workshop better. All of the current facilitators will be emailed and they may ask more questions before approving or denying your request. Please note that this will reveal your email address to the facilitators.')
.text-area-field.input-field .text-area-field.input-field
= label_tag :message = label_tag :message

5
app/views/workshops/facilitate_request_sent.html.haml

@ -3,10 +3,9 @@
%article %article
= row do = row do
%h2=_'page_titles.conferences.Facilitate_Workshop' %h2=_'page_titles.conferences.Facilitate_Workshop'
= registration_step_menu
= columns(medium: 12) do = columns(medium: 12) do
=_('articles.workshops.paragraphs.facilitate_request_sent','Your request has been sent. You will receive an email once your request is approved or denied or if the current facilitators have any questions.') =_('articles.workshops.paragraphs.facilitate_request_sent','Your request has been sent. You will receive an email once your request is approved or denied or if the current facilitators have any questions.')
= columns(medium: 12) do = columns(medium: 12) do
.actions .actions
= link_to (_'actions.workshops.View'), view_workshop_path(@this_conference.slug, @workshop.id), :class => 'button' = link_to (_'actions.workshops.View'), view_workshop_path(@this_conference.slug, @workshop.id), class: :button
= link_to (_'actions.workshops.View_All'), register_step_path(@this_conference.slug, :wokshops), :class => 'button' = link_to (_'actions.workshops.View_All'), register_step_path(@this_conference.slug, :wokshops), class: :button

4
app/views/workshops/show.html.haml

@ -11,5 +11,5 @@
= row do = row do
= columns(medium: 12) do = columns(medium: 12) do
.actions.center .actions.center
= (link_to (_'actions.workshops.Edit'), edit_workshop_path(@this_conference.slug, @workshop.id), :class => [:button, :modify]) if @workshop.can_edit?(current_user) = (link_to (_'actions.workshops.Edit'), edit_workshop_path(@this_conference.slug, @workshop.id), class: [:button, :modify]) if @workshop.can_edit?(current_user)
= (link_to (_'actions.workshops.Delete'), delete_workshop_path(@this_conference.slug, @workshop.id), :class => 'button delete') if @workshop.can_delete?(current_user) = (link_to (_'actions.workshops.Delete'), delete_workshop_path(@this_conference.slug, @workshop.id), class: 'button delete') if @workshop.can_delete?(current_user)

7
config/locales/en.yml

@ -1687,6 +1687,7 @@ en:
payment_pending: Thank you! Your payment is currently pending. payment_pending: Thank you! Your payment is currently pending.
companion_unregistered: Your companion has not yet registered. Please ensure companion_unregistered: Your companion has not yet registered. Please ensure
that they do to guarantee you are housed together. that they do to guarantee you are housed together.
registration_required: Please complete your registration
step_names: step_names:
group_ride: Group ride? group_ride: Group ride?
hosting_other: Other hosting_other: Other
@ -2441,13 +2442,13 @@ en:
add: Add new add: Add new
options: options:
send_to: send_to:
registered: Everyone who has registered registered: Everyone who has completed their registration
pre_registered: Everyone who has pre-registered or registered pre_registered: Everyone who has begun or completed the registration process
unregistered: Everyone who has not completed their registration unregistered: Everyone who has not completed their registration
workshop_facilitators: All workshop facilitators workshop_facilitators: All workshop facilitators
housing_providers: Housing providers housing_providers: Housing providers
guests: Everyone who has requested housing guests: Everyone who has requested housing
all: Everyone all: Everyone who has ever registered for a conference
conferences: conferences:
types: types:
annual: Annual Bike!Bike! annual: Annual Bike!Bike!

1
config/locales/fr.yml

@ -1516,6 +1516,7 @@ fr:
companion_unregistered: Votre partenaire ne s’est pas encore inscrit ou inscrite. companion_unregistered: Votre partenaire ne s’est pas encore inscrit ou inscrite.
Veuillez vous assurer qu’il ou elle le fasse pour que vous soyez hébergés Veuillez vous assurer qu’il ou elle le fasse pour que vous soyez hébergés
ensemble. ensemble.
registration_required: Veuillez compléter votre inscription
policy: policy:
headings: headings:
The_Agreement: L’ Accord The_Agreement: L’ Accord

24
db/schema.rb

@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170613022506) do ActiveRecord::Schema.define(version: 20170719024801) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -361,6 +361,26 @@ ActiveRecord::Schema.define(version: 20170613022506) do
t.datetime "updated_at" t.datetime "updated_at"
end end
create_table "reports", force: :cascade do |t|
t.string "request_id"
t.string "signature"
t.string "severity"
t.string "source"
t.string "backtrace"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "requests", force: :cascade do |t|
t.string "request_id"
t.string "session_id"
t.json "data"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "application"
t.integer "response"
end
create_table "sessions", force: :cascade do |t| create_table "sessions", force: :cascade do |t|
t.string "session_id", null: false t.string "session_id", null: false
t.text "data" t.text "data"
@ -384,7 +404,7 @@ ActiveRecord::Schema.define(version: 20170613022506) do
t.integer "translator_id" t.integer "translator_id"
t.string "key" t.string "key"
t.text "value" t.text "value"
t.datetime "created_at" t.date "created_at"
end end
create_table "translations", force: :cascade do |t| create_table "translations", force: :cascade do |t|

21
features/workshops.feature

@ -3,12 +3,11 @@ Feature: Workshops
Given there is an upcoming conference Given there is an upcoming conference
And registration is open And registration is open
And I am logged in And I am logged in
And registered for the conference And registered
And on the registration page And on the registration page
Then I should see 'New Workshop' Then I should see 'New Workshop'
And should see 'View Workshops' And should see 'View Workshops'
Then I should see 'New Workshop'
But I should not see any workshops But I should not see any workshops
When I click on 'New Workshop' When I click on 'New Workshop'
@ -66,6 +65,24 @@ Feature: Workshops
Then I should see 'New Workshop' Then I should see 'New Workshop'
But I should not see any workshops But I should not see any workshops
Scenario: Un-Registered users cannot create workshops
Given there is an upcoming conference
And the conference has workshop info copy
And registration is open
And I am logged in
And on the landing page
Then I should see 'View Workshops'
When I click on 'View Workshops'
Then I should be on the workshops page
And I should see 'New Workshop'
But I should not see any workshops
When I click on 'New Workshop'
Then I should see 'Please complete your registration'
But I should not see 'Create a Workshop'
Scenario: Users can comment on and translate their own workshops Scenario: Users can comment on and translate their own workshops
Given that there is an upcoming conference Given that there is an upcoming conference
And registration is open And registration is open

Loading…
Cancel
Save