Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
41fb6f9
Testing file v1
Ethan-Stone1 Mar 4, 2026
b4339cc
First changes on Event Duplication
Ethan-Stone1 Mar 6, 2026
df9fde6
Duplication and tests added
Ethan-Stone1 Mar 6, 2026
503e6a0
Update Tests
Ethan-Stone1 Mar 6, 2026
dd4ec04
Creating only one copy sends to Events list
Ethan-Stone1 Mar 6, 2026
c4782e8
Duplicatation fails if not specified between 1 and 100 events
Ethan-Stone1 Mar 6, 2026
916b1a2
Add conference duplication feature (Iteration 2)
zhouyijun111 Mar 6, 2026
a7df48b
yes erge remote-tracking branch 'origin/main' into feature/conference…
zhouyijun111 Mar 6, 2026
34ff0c3
Update test suite to fix latency failing tests
Ethan-Stone1 Mar 6, 2026
08619ba
Fix some tests
Ethan-Stone1 Mar 6, 2026
bf0b245
Add timeout for race condition test
Ethan-Stone1 Mar 6, 2026
f4d05b4
Add timeouts to the rest of the tests
Ethan-Stone1 Mar 6, 2026
0694aac
Rubocop, more test fixes
Ethan-Stone1 Mar 6, 2026
d01c55f
More test fixes
Ethan-Stone1 Mar 6, 2026
aee09c5
Implement Event Duplication
Ethan-Stone1 Mar 6, 2026
901cfdc
Implement Event Duplication
Ethan-Stone1 Mar 6, 2026
8e53c08
update ruby version
Ethan-Stone1 Mar 6, 2026
36ccd15
Bump ruby to 3.3.10
Ethan-Stone1 Mar 6, 2026
4e0f88a
Darwin in the gemfile
Ethan-Stone1 Mar 6, 2026
60d3f72
Fix registration form not displaying validation errors
li-xinwei Mar 11, 2026
000b5ae
Merge pull request #62 from cs169/fix/registration-error-messages
zhouyijun111 Mar 11, 2026
1b9e7a4
Make Duplication one transaction
Ethan-Stone1 Mar 13, 2026
edf6853
Merge pull request #60 from cs169/Duplicate_Event
Ethan-Stone1 Mar 18, 2026
3edb5a8
Merge branch 'main' of https://github.com/cs169/snapcon
Ethan-Stone1 Mar 20, 2026
d8e50e6
Update info.yml
li-xinwei Mar 20, 2026
69bea7a
Respect organizer email_notifications when sending proposal comment e…
zhouyijun111 Mar 20, 2026
8f5adc5
Migrate from Stripe Charges API to Stripe Checkout Sessions API
li-xinwei Mar 29, 2026
3a8c3f3
Add conference duplication feature (Iteration 2) (#59)
zhouyijun111 Apr 3, 2026
db28357
Add conference duplication feature (Iteration 2) (#59)
zhouyijun111 Apr 3, 2026
946137e
Add conference duplication feature (Iteration 2) (#59)
zhouyijun111 Apr 3, 2026
cf0ffbd
Merge pull request #59 from cs169/feature/conference-duplication
zhouyijun111 Apr 6, 2026
37832e7
Merge remote-tracking branch 'origin/main' into feature/organizer-com…
zhouyijun111 Apr 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,8 @@ RSpec/SubjectStub:
RSpec/VerifiedDoubles:
Exclude:
- 'spec/datatables/user_datatable_spec.rb'
- 'spec/features/ticket_purchases_spec.rb'
- 'spec/models/payment_spec.rb'
- 'spec/pdfs/ticket_pdf_spec.rb'

# Offense count: 1
Expand Down
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.3.8
3.3.10
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
ruby 3.3.8
ruby 3.3.10
nodejs 16.20.2
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ gem 'cloudinary'
# for internationalizing
gem 'rails-i18n'
# Windows: timezone data (required on Windows for tzinfo)
gem 'tzinfo-data', platforms: %i[ windows jruby ]
gem 'tzinfo-data', platforms: %i[windows jruby]

# as authentification framework
gem 'devise'
Expand Down
7 changes: 6 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ GEM
faraday (~> 2.0)
fastimage (2.3.0)
feature (1.4.0)
ffi (1.17.0-arm64-darwin)
ffi (1.17.0-x64-mingw-ucrt)
ffi (1.17.0-x86_64-linux-gnu)
font-awesome-sass (6.5.1)
Expand Down Expand Up @@ -383,6 +384,8 @@ GEM
next_rails (1.3.0)
colorize (>= 0.8.1)
nio4r (2.7.0)
nokogiri (1.16.6-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.6-x64-mingw-ucrt)
racc (~> 1.4)
nokogiri (1.16.6-x86_64-linux)
Expand Down Expand Up @@ -641,6 +644,7 @@ GEM
actionpack (>= 5.2)
activesupport (>= 5.2)
sprockets (>= 3.0.0)
sqlite3 (1.7.2-arm64-darwin)
sqlite3 (1.7.2-x64-mingw-ucrt)
sqlite3 (1.7.2-x86_64-linux)
ssrf_filter (1.1.2)
Expand Down Expand Up @@ -705,6 +709,7 @@ GEM
zeitwerk (2.6.13)

PLATFORMS
arm64-darwin-25
x64-mingw-ucrt
x86_64-linux

Expand Down Expand Up @@ -827,7 +832,7 @@ DEPENDENCIES
whenever

RUBY VERSION
ruby 3.3.10p183
ruby 3.3.8p144

BUNDLED WITH
2.5.6
62 changes: 54 additions & 8 deletions app/assets/javascripts/osem-switch.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,61 @@
function checkboxSwitch(selector){
$(selector).bootstrapSwitch();

$(selector).on('switchChange.bootstrapSwitch', function(event, state) {
var url = $(this).attr('url') + state;
var method = $(this).attr('method') || 'patch';
// Prevent duplicated event handlers when the page is re-rendered.
$(selector).off('switchChange.bootstrapSwitch');
$(selector).off('.osemSwitchGuard');

$.ajax({
url: url,
type: method,
dataType: 'script'
});
// Mark as user-initiated before bootstrapSwitch triggers switchChange.
// Important: bootstrapSwitch often binds clicks on its generated wrapper/label,
// so we must listen on those too (not only on the hidden checkbox input).
$(selector).each(function() {
var $input = $(this);
$input.data('osem-user-toggle', false);

var $wrapper = $input.closest('.bootstrap-switch');
if ($wrapper.length === 0) {
$wrapper = $input.parent();
}

$wrapper.off('click.osemSwitchGuard mouseup.osemSwitchGuard touchend.osemSwitchGuard pointerup.osemSwitchGuard');
$wrapper.on(
'click.osemSwitchGuard mouseup.osemSwitchGuard touchend.osemSwitchGuard pointerup.osemSwitchGuard',
function() {
$input.data('osem-user-toggle', true);
}
);
});

$(selector).on('switchChange.bootstrapSwitch', function(_event, state) {
var $el = $(this);
if (!$el.data('osem-user-toggle')) {
return;
}

// bootstrapSwitch can emit multiple switchChange events per user click.
// Delay the request slightly, then read the final checkbox state to send once.
var existingTimer = $el.data('osem-user-toggle-timer');
if (existingTimer) {
clearTimeout(existingTimer);
}

var method = $el.attr('method') || 'patch';
var urlBase = $el.attr('url');

var timer = setTimeout(function() {
$el.data('osem-user-toggle', false);

var checked = $el.is(':checked');
var url = urlBase + (checked ? 'true' : 'false');

$.ajax({
url: url,
type: method,
dataType: 'script'
});
}, 180);

$el.data('osem-user-toggle-timer', timer);
});
}

Expand Down
7 changes: 5 additions & 2 deletions app/assets/stylesheets/osem-payments.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.stripe-button-el {
float: right;
.payment-actions {
margin-top: 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
45 changes: 44 additions & 1 deletion app/controllers/admin/conferences_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,62 @@ def index
end

def new
@conference = Conference.new
if params[:duplicate_from].present?
source = Conference.find_by(short_title: params[:duplicate_from])
if source && can?(:read, source)
@conference = Conference.new(
description: source.description,
timezone: source.timezone,
start_hour: source.start_hour,
end_hour: source.end_hour,
color: source.color,
custom_css: source.custom_css,
ticket_layout: source.ticket_layout,
registration_limit: source.registration_limit,
booth_limit: source.booth_limit,
organization_id: source.organization_id
)
@duplicate_from_source = source.short_title
else
@conference = Conference.new
end
else
@conference = Conference.new
end
end

def create
@conference = Conference.new(conference_params)

if params[:duplicate_from].present?
source = Conference.find_by(short_title: params[:duplicate_from])
if source && can?(:read, source)
@conference.assign_attributes(
description: source.description,
custom_css: source.custom_css,
ticket_layout: source.ticket_layout,
registration_limit: source.registration_limit,
booth_limit: source.booth_limit,
color: source.color,
start_hour: source.start_hour,
end_hour: source.end_hour
)
end
end

if @conference.save
# user that creates the conference becomes organizer of that conference
current_user.add_role :organizer, @conference

if params[:duplicate_from].present?
source = Conference.find_by(short_title: params[:duplicate_from])
@conference.copy_associations_from(source) if source && can?(:read, source)
end

redirect_to admin_conference_path(id: @conference.short_title),
notice: 'Conference was successfully created.'
else
@duplicate_from_source = params[:duplicate_from]
flash.now[:error] = 'Could not create conference. ' + @conference.errors.full_messages.to_sentence
render action: 'new'
end
Expand Down
24 changes: 24 additions & 0 deletions app/controllers/admin/events_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,30 @@ def toggle_attendance
end
end

def duplicate
count = params[:count].to_i # Invalid input will be treated as 0, which will be caught by validation below

# Validate count
unless count.between?(1, 100)
flash[:alert] = 'Invalid number of duplicates. Please enter a number between 1 and 100.'
redirect_to admin_conference_program_event_path(@conference.short_title, @event)
return
end

duplicator = EventDuplicator.new(@event, current_user)
duplicated_events = duplicator.duplicate(count)

flash[:notice] = if duplicated_events.length == 1
"Event '#{duplicated_events.first.title}' duplicated successfully."
else
"#{duplicated_events.length} copies of '#{@event.title}' created successfully."
end
redirect_to admin_conference_program_events_path(@conference.short_title)
rescue StandardError
flash[:alert] = 'Could not duplicate event'
redirect_to admin_conference_program_event_path(@conference.short_title, @event)
end

def destroy
@event = Event.find(params[:id])
if @event.destroy
Expand Down
32 changes: 30 additions & 2 deletions app/controllers/admin/roles_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class RolesController < Admin::BaseController
before_action :set_selection
authorize_resource :role, except: :index
# Show flash message with ajax calls
after_action :prepare_unobtrusive_flash, only: :toggle_user
after_action :prepare_unobtrusive_flash, only: %i[toggle_user toggle_comment_notifications]

def index
@roles = Role.where(resource: @conference)
Expand All @@ -21,7 +21,11 @@ def show
else
toggle_user_admin_conference_role_path(@conference.short_title, @role.name)
end
@users = @role.users
@users_roles = UsersRole.where(role: @role).includes(:user)
@comment_notifications_url =
if @track.nil?
toggle_comment_notifications_admin_conference_role_path(@conference.short_title, @role.name)
end
end

def edit
Expand Down Expand Up @@ -103,6 +107,30 @@ def toggle_user
end
end

def toggle_comment_notifications
user = User.find_by(email: user_params[:email])
state = user_params[:state]

redirect_url = admin_conference_role_path(@conference.short_title, @role.name)
unless user
redirect_to redirect_url, error: 'Could not find user. Please provide a valid email!' and return
end

users_role = UsersRole.find_by(user: user, role: @role)
unless users_role
redirect_to redirect_url, error: 'Could not find organizer setting for this user.' and return
end

# Be tolerant to different representations coming from the client (e.g. "true", "1", true).
email_notifications = ActiveModel::Type::Boolean.new.cast(state)
users_role.update!(email_notifications: email_notifications)

respond_to do |format|
format.js
format.html { redirect_to redirect_url, notice: 'Successfully updated notification setting.' }
end
end

protected

def set_selection
Expand Down
Loading
Loading