Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
a8ebb18
Add failing tests for ical generation with nil event data
sikesbc Mar 4, 2026
41fb6f9
Testing file v1
Ethan-Stone1 Mar 4, 2026
4e57949
Handle nil event data in ical generation
sikesbc Mar 5, 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
981ce24
Add tentative_acceptance_code
Ethan-Stone1 Apr 13, 2026
f1260f4
Small changes
Ethan-Stone1 Apr 13, 2026
d2d7db6
Modify Email after clicking tentative_accept
Ethan-Stone1 Apr 13, 2026
35c6042
Get request patched
Ethan-Stone1 Apr 13, 2026
c38016d
Reroute back to events page
Ethan-Stone1 Apr 13, 2026
7a3dbf3
Make tentative accept page read only, use preset and 'committee feedb…
Ethan-Stone1 Apr 13, 2026
4815b6d
Can reject a tentatively accepted event
Ethan-Stone1 Apr 14, 2026
9cae742
Change unable to send email message
Ethan-Stone1 Apr 14, 2026
a580661
Renaming buttons, changing some text
Ethan-Stone1 Apr 14, 2026
3b9d51f
Email no longer shows up if unable to send it
Ethan-Stone1 Apr 14, 2026
e3b58e1
Indentation fixes
Ethan-Stone1 Apr 14, 2026
2e8d3e7
Bug fix
Ethan-Stone1 Apr 14, 2026
4782ebe
update ruby version
Ethan-Stone1 Apr 14, 2026
6c2d833
Add dynamic variable substitution to conference description markdown
sikesbc Apr 16, 2026
af20d3c
Show available template variables on conference description fields
sikesbc Apr 16, 2026
f6d3108
Add failing tests for EmailTemplateParser with nil user
sikesbc Apr 15, 2026
df8c1a5
Allow EmailTemplateParser to work without a user
sikesbc Apr 15, 2026
a8c92de
Add failing tests for markdown_with_varibles helper
sikesbc Apr 16, 2026
c113343
Add failing test for code of conduct modal rendering without CoC
sikesbc Apr 16, 2026
cb6c57f
Hide code of conduct modal when no CoC is set
sikesbc Apr 16, 2026
94bf69a
Add failing tests for conference menu without org grouping
sikesbc Apr 16, 2026
e7c4083
Remove organization grouping from conference menu
sikesbc Apr 16, 2026
a2eb1ad
Add tests
Ethan-Stone1 Apr 20, 2026
37832e7
Merge remote-tracking branch 'origin/main' into feature/organizer-com…
zhouyijun111 Apr 22, 2026
0b47097
Merge pull request #68 from cs169/feature/organizer-comment-notificat…
zhouyijun111 Apr 22, 2026
b96238a
Small refactor, add new route and remove if patch
Ethan-Stone1 Apr 22, 2026
89100a3
Render properly
Ethan-Stone1 Apr 22, 2026
7ac0219
Remove @conference
Ethan-Stone1 Apr 22, 2026
8957dc5
Update tests because new routes
Ethan-Stone1 Apr 24, 2026
6c5ac11
Merge pull request #63 from cs169/fix-ical-nil-event-data
sikesbc Apr 24, 2026
672fd11
Merge pull request #70 from cs169/epic1-dynamic-variable-substitution
sikesbc Apr 24, 2026
e580134
Merge pull request #71 from cs169/fix/hide-code-of-conduct-link
sikesbc Apr 24, 2026
f00b39f
Merge pull request #72 from cs169/fix/conference-menu-remove-org
sikesbc Apr 24, 2026
97d6b5b
Asynchronous tests again
Ethan-Stone1 Apr 24, 2026
9ceb825
Rubocop
Ethan-Stone1 Apr 24, 2026
fe10ea5
Update ruby to 3.3.10
Ethan-Stone1 Apr 24, 2026
e092041
Merge branch 'main' into tentative_accept
Ethan-Stone1 Apr 24, 2026
8a4bc56
fix rubocop offenses
sikesbc Apr 24, 2026
67fac4d
update conference specs for tentatively_accepted state
sikesbc Apr 24, 2026
cc42290
fix hash alignment in conference spec
sikesbc Apr 24, 2026
63f29cf
Merge pull request #74 from cs169/tentative_accept
sikesbc Apr 24, 2026
b46192a
add ben demo conference seed task
sikesbc Apr 24, 2026
ff9d70e
fix ben demo seed validations
sikesbc Apr 24, 2026
492f4ea
fix schedule timezone in ben demo seed
sikesbc Apr 24, 2026
45a6a00
delete conference roles before destroying in seed
sikesbc Apr 24, 2026
cc6a0a5
Force SSO for new sign-ups (closes #64) (#69)
li-xinwei Apr 24, 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 @@ -668,7 +672,7 @@ GEM
turbolinks-source (5.2.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
tzinfo-data (1.2025.3)
tzinfo-data (1.2026.1)
tzinfo (>= 1.0.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
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
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
6 changes: 3 additions & 3 deletions app/controllers/admin/emails_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ def index

def email_params
params.require(:email_settings).permit(:send_on_registration,
:send_on_accepted, :send_on_rejected, :send_on_confirmed_without_registration,
:send_on_accepted, :send_on_tentative_accepted, :send_on_rejected, :send_on_confirmed_without_registration,
:send_on_submitted_proposal,
:submitted_proposal_subject, :submitted_proposal_body,
:registration_subject, :accepted_subject, :rejected_subject, :confirmed_without_registration_subject,
:registration_body, :accepted_body, :rejected_body, :confirmed_without_registration_body,
:registration_subject, :accepted_subject, :tentative_accepted_subject, :rejected_subject, :confirmed_without_registration_subject,
:registration_body, :accepted_body, :tentative_accepted_body, :rejected_body, :confirmed_without_registration_body,
:send_on_conference_dates_updated, :conference_dates_updated_subject, :conference_dates_updated_body,
:send_on_conference_registration_dates_updated, :conference_registration_dates_updated_subject, :conference_registration_dates_updated_body,
:send_on_venue_updated, :venue_updated_subject, :venue_updated_body,
Expand Down
64 changes: 64 additions & 0 deletions app/controllers/admin/events_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,46 @@ def new
@superevents = @program.events.where(superevent: true)
end

def preview_tentative_accept
email_settings = @conference.email_settings
@tentative_subject = email_settings.tentative_accepted_subject.presence
default_body = email_settings.tentative_accepted_body.presence
@missing_committee_review = @event.committee_review.blank?
@tentative_body = EmailTemplateParser.parse_template(default_body, email_settings.get_values(@conference, @event.submitter, @event)) unless @missing_committee_review
render :tentative_accept
end

def tentative_accept
email_settings = @conference.email_settings

if @event.committee_review.blank?
flash.now[:alert] = 'Committee feedback is required before sending a tentative acceptance email.'
@tentative_subject = email_settings.tentative_accepted_subject.presence
email_settings.tentative_accepted_body.presence
@missing_committee_review = true
render :tentative_accept and return
end

@tentative_subject = email_settings.tentative_accepted_subject.presence
default_body = email_settings.tentative_accepted_body.presence
@missing_committee_review = false
@tentative_body = EmailTemplateParser.parse_template(default_body, email_settings.get_values(@conference, @event.submitter, @event))
send_mail = email_settings.send_on_tentative_accepted

begin
@event.tentatively_accept(send_mail: send_mail, subject: @tentative_subject, body: @tentative_body)
@event.save!
flash[:notice] = 'Event tentatively accepted!'
redirect_to admin_conference_program_events_path(conference_id: @conference.short_title) and return
rescue ActiveRecord::RecordInvalid => e
flash.now[:error] = "Could not save tentative acceptance: #{e.record.errors.full_messages.join(', ')}"
render :tentative_accept
rescue Transitions::InvalidTransition => e
flash.now[:error] = "Update state failed. #{e.message}"
render :tentative_accept
end
end

def accept
send_mail = @event.program.conference.email_settings.send_on_accepted
subject = @event.program.conference.email_settings.accepted_subject.blank?
Expand Down Expand Up @@ -182,6 +222,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