diff --git a/app/policies/event_policy.rb b/app/policies/event_policy.rb index 21e860c7e..7b1f5d2a7 100644 --- a/app/policies/event_policy.rb +++ b/app/policies/event_policy.rb @@ -8,7 +8,13 @@ def index? end def show? - admin? || record.publicly_visible? || (authenticated? && record.published?) + return true if admin? + + if record.ended? + authenticated? && record.published? && record.actively_registered?(user.person) + else + record.publicly_visible? || (authenticated? && record.published?) + end end def register? @@ -39,15 +45,29 @@ def owner? relation_scope do |relation| next relation if admin? - if authenticated? # logged in users can see events they are registered for even if registration is closed - relation.left_outer_joins(:registrants) - .published - .where("registration_close_date IS NULL OR registration_close_date >= ? OR people.id = ?", Time.current, user.person_id) - .distinct + + if authenticated? + relation + .joins( + "LEFT OUTER JOIN event_registrations + ON event_registrations.event_id = events.id + AND event_registrations.status IN ('registered', 'attended', 'incomplete_attendance') + LEFT OUTER JOIN people + ON people.id = event_registrations.registrant_id" + ) + .published + .where( + "(events.end_date >= :now AND (events.registration_close_date IS NULL OR events.registration_close_date >= :now)) + OR people.id = :person_id", + now: Time.current, + person_id: user.person_id + ) + .distinct else relation.publicly_visible .published - .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) + .where("events.end_date >= ?", Time.current) + .where("events.registration_close_date IS NULL OR events.registration_close_date >= ?", Time.current) end end end diff --git a/app/views/events/_card.html.erb b/app/views/events/_card.html.erb index 7057f2cb9..b0edbe501 100644 --- a/app/views/events/_card.html.erb +++ b/app/views/events/_card.html.erb @@ -1,8 +1,12 @@ <% event = event.decorate %> +<% ended = event.ended? %> <%= tag.div id: dom_id(event, :card), - class: "relative bg-white border border-gray-200 rounded-2xl shadow-sm hover:shadow-md - transition p-4 flex flex-col gap-4 h-full" do %> + class: class_names( + "relative rounded-2xl shadow-sm transition p-4 flex flex-col gap-4 h-full", + "bg-gray-100 border border-gray-300 opacity-60": ended, + "bg-white border border-gray-200 hover:shadow-md": !ended + ) do %>
<%= render "bookmarks/editable_bookmark_icon", resource: event.object %> @@ -50,6 +54,9 @@ <% end %> <% if registered %> + <% if ended %> + Event ended + <% end %> <% registration = event.active_registration_for(current_user.person) %> <% if registration&.slug.present? %> <%= link_to "View registration", registration_ticket_path(registration.slug), diff --git a/app/views/events/_registration_section.html.erb b/app/views/events/_registration_section.html.erb index 9238b97bf..90dbb78f1 100644 --- a/app/views/events/_registration_section.html.erb +++ b/app/views/events/_registration_section.html.erb @@ -5,21 +5,19 @@ <% slug_registered = slug_registration&.active? %> <% slug_cancelled = slug_registration.present? && slug_registration.status == "cancelled" %> <%= tag.div id: dom_id(event.object, "registration_section_#{instance}"), class: "registration-section flex flex-col items-center gap-4 mb-6" do %> - -
+
<% if event.ended? %> - Event ended + Event ended <% if allowed_to?(:manage?, event) %> <%= button_to "Register", event_registrant_registration_path(event_id: event), class: "admin-only bg-blue-100 btn btn-primary-outline" %> <% end %> - <% elsif registered %> - <% elsif slug_cancelled && event.registerable? %> - <%= button_to "Register again", registration_reactivate_path(slug_registration.slug), - class: "text-sm text-gray-500 hover:text-blue-600 underline bg-transparent border-0 cursor-pointer p-0" %> <% elsif !registered && !slug_registered %> - <% if !event.published? %> + <% if slug_cancelled && event.registerable? %> + <%= button_to "Register again", registration_reactivate_path(slug_registration.slug), + class: "text-sm text-gray-500 hover:text-blue-600 underline bg-transparent border-0 cursor-pointer p-0" %> + <% elsif !event.published? %> Not published <% elsif event.registerable? %> <% if user_signed_in? %> @@ -50,7 +48,7 @@ <% end %>
- <% if registered || slug_registered %> + <% if (registered || slug_registered) && !event.ended? %>

diff --git a/spec/factories/events.rb b/spec/factories/events.rb index 8bf9f2e77..24ee68031 100644 --- a/spec/factories/events.rb +++ b/spec/factories/events.rb @@ -29,6 +29,12 @@ publicly_featured { true } end + trait :ended do + start_date { 14.days.ago } + end_date { 12.days.ago } + registration_close_date { 13.days.ago } + end + trait :registration_closed do registration_close_date { 13.days.ago } end diff --git a/spec/policies/event_policy_spec.rb b/spec/policies/event_policy_spec.rb index 598ff26aa..001b4df66 100644 --- a/spec/policies/event_policy_spec.rb +++ b/spec/policies/event_policy_spec.rb @@ -6,6 +6,7 @@ let(:published_event) { build_stubbed :event, :published } let(:public_event) { build_stubbed :event, publicly_visible: true } let(:unpublished_event) { build_stubbed :event, :unpublished } + let(:ended_event) { build_stubbed :event, :published, :ended } let(:open_registration_event) { build_stubbed :event, registration_close_date: 1.day.from_now } let(:closed_registration_event) { build_stubbed :event, registration_close_date: 1.day.ago } @@ -54,6 +55,36 @@ def policy_for(record: nil, user:) end end + context "when event has ended" do + context "with admin user" do + subject { policy_for(record: ended_event, user: admin_user) } + + it { is_expected.to be_allowed_to(:show?) } + end + + context "with registered user" do + subject { policy_for(record: ended_event, user: regular_user) } + + before { allow(ended_event).to receive(:actively_registered?).with(regular_user.person).and_return(true) } + + it { is_expected.to be_allowed_to(:show?) } + end + + context "with unregistered user" do + subject { policy_for(record: ended_event, user: regular_user) } + + before { allow(ended_event).to receive(:actively_registered?).with(regular_user.person).and_return(false) } + + it { is_expected.not_to be_allowed_to(:show?) } + end + + context "with no user" do + subject { policy_for(record: ended_event, user: nil) } + + it { is_expected.not_to be_allowed_to(:show?) } + end + end + context "when event is not visible" do context "with admin user" do subject { policy_for(record: unpublished_event, user: admin_user) } @@ -175,22 +206,27 @@ def policy_for(record: nil, user:) context "with regular user" do let(:policy) { policy_for(record: Event, user: regular_user) } - it "returns only visible events with open registration" do + it "returns only visible events with open registration or active registrations" do scope = policy.apply_scope(Event.all, type: :active_record_relation) - expect(scope.to_sql).to include('`events`.`published` = TRUE') - expect(scope.to_sql).to include('registration_close_date IS NULL OR registration_close_date >=') - expect(scope.to_sql).to include('LEFT OUTER JOIN `event_registrations`') + sql = scope.to_sql + expect(sql).to include('`events`.`published` = TRUE') + expect(sql).to include("events.end_date >=") + expect(sql).to include("events.registration_close_date IS NULL OR events.registration_close_date >=") + expect(sql).to include("LEFT OUTER JOIN event_registrations") + expect(sql).to include("event_registrations.status IN") end end context "with no user" do let(:policy) { policy_for(record: Event, user: nil) } - it "returns only visible events with open registration" do + it "excludes ended events and events with closed registration" do scope = policy.apply_scope(Event.all, type: :active_record_relation) - expect(scope.to_sql).to include('`events`.`published` = TRUE') - expect(scope.to_sql).to include('registration_close_date IS NULL OR registration_close_date >=') - expect(scope.to_sql).not_to include('LEFT OUTER JOIN `registrants`') + sql = scope.to_sql + expect(sql).to include('`events`.`published` = TRUE') + expect(sql).to include("events.end_date >=") + expect(sql).to include("events.registration_close_date IS NULL OR events.registration_close_date >=") + expect(sql).not_to include("LEFT OUTER JOIN") end end end diff --git a/spec/requests/events_spec.rb b/spec/requests/events_spec.rb index eac574b37..8bb317d04 100644 --- a/spec/requests/events_spec.rb +++ b/spec/requests/events_spec.rb @@ -30,9 +30,9 @@ end context "when user time_zone is set" do - # 19:00 UTC = 12:00 noon PT = 15:00 (3 pm) ET (June 15, 2025 with DST) - let(:utc_start) { Time.utc(2025, 6, 15, 19, 0, 0) } - let(:utc_end) { Time.utc(2025, 6, 15, 20, 0, 0) } + # 19:00 UTC = 12:00 noon PT = 15:00 (3 pm) ET (June 15, 2031 with DST) + let(:utc_start) { Time.utc(2031, 6, 15, 19, 0, 0) } + let(:utc_end) { Time.utc(2031, 6, 15, 20, 0, 0) } let!(:event_with_fixed_times) do create(:event, :published, start_date: utc_start, @@ -63,6 +63,37 @@ end end + describe "GET /show" do + context "when event has ended" do + let(:ended_event) { create(:event, :published, :ended) } + + it "allows admin to view" do + sign_in admin + get event_path(ended_event) + expect(response).to have_http_status(:ok) + end + + it "allows registered user to view" do + user_with_person = create(:user, :with_person) + sign_in user_with_person + create(:event_registration, event: ended_event, registrant: user_with_person.person, status: "registered") + get event_path(ended_event) + expect(response).to have_http_status(:ok) + end + + it "redirects unregistered user" do + sign_in user + get event_path(ended_event) + expect(response).to redirect_to(root_path) + end + + it "redirects unauthenticated user" do + get event_path(ended_event) + expect(response).to redirect_to(root_path) + end + end + end + describe "GET /new" do context "as admin" do it "renders successfully" do diff --git a/spec/system/events_show_spec.rb b/spec/system/events_show_spec.rb index 5451c4342..a34a0f4b4 100644 --- a/spec/system/events_show_spec.rb +++ b/spec/system/events_show_spec.rb @@ -171,6 +171,7 @@ context "event ended" do before do event.update!(end_date: 1.day.ago) + create(:event_registration, event: event, registrant: user.person, status: "registered") end it "shows 'Event ended' and hides registration buttons" do @@ -181,6 +182,37 @@ expect(page).not_to have_button("Register") expect(page).not_to have_button("De-register") end + + it "shows 'View Registration' for registered user" do + sign_in(user) + visit event_path(event) + + expect(page).to have_text("Event ended") + expect(page).to have_text("View Registration") + end + + it "hides calendar links" do + sign_in(user) + visit event_path(event) + + expect(page).not_to have_text("Add to Your Calendar") + end + + it "blocks unregistered users" do + other_user = create(:user, :with_person) + sign_in(other_user) + visit event_path(event) + + expect(page).to have_current_path(root_path) + end + + it "allows admin to view" do + sign_in(admin) + visit event_path(event) + + expect(page).to have_text("Event ended") + expect(page).to have_text("My Event") + end end context "guest with reg slug param" do