From 0fea22224f3e160c1398ebf40f4429d99f5d8372 Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Sun, 1 Feb 2026 11:32:38 -0500 Subject: [PATCH 01/19] scope dashboard to exclude reg closed --- app/models/event.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/models/event.rb b/app/models/event.rb index 84681c227..5b9085ae6 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -33,8 +33,15 @@ class Event < ApplicationRecord attributes :title, :description end - scope :featured, -> { where(featured: true) } - scope :visitor_featured, -> { where(visitor_featured: true) } + + scope :featured, -> { + where(featured: true) + .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) + } + scope :visitor_featured, -> { + where(vistor_featured: true) + .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) + } scope :published, ->(published = nil) { publicly_visible(published) } scope :publicly_visible, ->(publicly_visible = nil) { publicly_visible ? where(publicly_visible: publicly_visible): where(publicly_visible: true) } scope :category_names, ->(names) { tag_names(:categories, names) } From 4369b25b7ea24da1561ff4076bf5e5712667e13b Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Sun, 1 Feb 2026 12:19:18 -0500 Subject: [PATCH 02/19] add scoped index registered users can see their events if registration is closed --- app/controllers/events_controller.rb | 6 +++--- app/models/event.rb | 3 +++ app/policies/event_policy.rb | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 app/policies/event_policy.rb diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index fd4a35933..acf8ea924 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -4,9 +4,9 @@ class EventsController < ApplicationController before_action :authorize_admin!, only: %i[ edit update destroy ] def index - unpaginated = current_user.super_user? ? Event.all : Event.published - unpaginated = unpaginated.search_by_params(params) - @events = unpaginated.order(start_date: :desc) + authorize! + base_scope = authorized_scope(Event.all) + @events = base_scope.search_by_params(params).order(start_date: :desc) end def show diff --git a/app/models/event.rb b/app/models/event.rb index 5b9085ae6..a02f786c4 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -34,6 +34,7 @@ class Event < ApplicationRecord end + # Action Policy scope :featured, -> { where(featured: true) .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) @@ -42,11 +43,13 @@ class Event < ApplicationRecord where(vistor_featured: true) .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) } + scope :published, ->(published = nil) { publicly_visible(published) } scope :publicly_visible, ->(publicly_visible = nil) { publicly_visible ? where(publicly_visible: publicly_visible): where(publicly_visible: true) } scope :category_names, ->(names) { tag_names(:categories, names) } scope :sector_names, ->(names) { tag_names(:sectors, names) } + def self.search_by_params(params) stories = self.all stories = stories.search(params[:query]) if params[:query].present? diff --git a/app/policies/event_policy.rb b/app/policies/event_policy.rb new file mode 100644 index 000000000..5249267c5 --- /dev/null +++ b/app/policies/event_policy.rb @@ -0,0 +1,18 @@ +class EventPolicy < ApplicationPolicy + # See https://actionpolicy.evilmartians.io/#/writing_policies + # + # override or add new rules here that are not defined in ApplicationPolicy + + skip_pre_check :verify_authenticated! + + relation_scope do |relation| + if admin? + relation + else + relation.left_outer_joins(:registrants) + .where(publicly_visible: true) + .where("registration_close_date IS NULL OR registration_close_date >= ? OR users.id = ?", Time.current, user.id) + .distinct + end + end +end From a384fd01ee7b6f189afa3d1cf360c5f9a975f8c9 Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Sun, 1 Feb 2026 12:36:48 -0500 Subject: [PATCH 03/19] add public scope --- app/controllers/events_controller.rb | 1 + app/models/event.rb | 2 +- app/policies/event_policy.rb | 5 ++++- app/views/events/index.html.erb | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index acf8ea924..a861915d7 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -1,5 +1,6 @@ class EventsController < ApplicationController include AhoyViewTracking, AssetUpdatable + skip_before_action :authenticate_user!, only: %i[ index] before_action :set_event, only: %i[ show edit update destroy ] before_action :authorize_admin!, only: %i[ edit update destroy ] diff --git a/app/models/event.rb b/app/models/event.rb index a02f786c4..37c5dbb66 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -40,7 +40,7 @@ class Event < ApplicationRecord .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) } scope :visitor_featured, -> { - where(vistor_featured: true) + where(visitor_featured: true) .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) } diff --git a/app/policies/event_policy.rb b/app/policies/event_policy.rb index 5249267c5..50edf300d 100644 --- a/app/policies/event_policy.rb +++ b/app/policies/event_policy.rb @@ -8,11 +8,14 @@ class EventPolicy < ApplicationPolicy relation_scope do |relation| if admin? relation - else + elsif authenticated? relation.left_outer_joins(:registrants) .where(publicly_visible: true) .where("registration_close_date IS NULL OR registration_close_date >= ? OR users.id = ?", Time.current, user.id) .distinct + else + relation.where(publicly_visible: true) + .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) end end end diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb index 80b925da1..f9d0391dc 100644 --- a/app/views/events/index.html.erb +++ b/app/views/events/index.html.erb @@ -17,7 +17,7 @@
- <% if current_user.super_user? %> + <% if current_user&.super_user? %> <%= link_to "New Event", new_event_path, class: "whitespace-nowrap btn btn-secondary-outline" %> From 589aa46a683eecd8cbcd8879f03cb4227d7c0009 Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Sun, 1 Feb 2026 12:57:30 -0500 Subject: [PATCH 04/19] add authorize to all event actions --- app/controllers/events_controller.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index a861915d7..98f4a2d0f 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -2,7 +2,6 @@ class EventsController < ApplicationController include AhoyViewTracking, AssetUpdatable skip_before_action :authenticate_user!, only: %i[ index] before_action :set_event, only: %i[ show edit update destroy ] - before_action :authorize_admin!, only: %i[ edit update destroy ] def index authorize! @@ -11,16 +10,19 @@ def index end def show + authorize! @event @event = @event.decorate track_view(@event) end - def new # all logged in users can create events + def new + authorize! @event = Event.new.decorate set_form_variables end def edit + authorize! @event set_form_variables unless @event.created_by == current_user || current_user.super_user? redirect_to events_path, alert: "You are not authorized to edit this event." @@ -28,6 +30,7 @@ def edit end def create + authorize! @event = Event.new(event_params).decorate @event.created_by ||= current_user @@ -47,6 +50,7 @@ def create end def update + authorize! @event respond_to do |format| if @event.update(event_params) format.html { redirect_to events_path, notice: "Event was successfully updated." } @@ -60,6 +64,7 @@ def update end def destroy + authorize! @event @event.destroy respond_to do |format| @@ -91,9 +96,4 @@ def event_params :publicly_visible ) end - - def authorize_admin! - redirect_to events_path, - alert: "You are not authorized to perform this action." unless current_user.super_user? - end end From 0a20add26c6210326388988670e7ab7a683f0df0 Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Sun, 1 Feb 2026 13:08:16 -0500 Subject: [PATCH 05/19] update views --- app/views/events/_card.html.erb | 2 +- app/views/events/_form.html.erb | 2 +- app/views/events/index.html.erb | 2 +- app/views/events/show.html.erb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/events/_card.html.erb b/app/views/events/_card.html.erb index afb03b0d3..246f955e1 100644 --- a/app/views/events/_card.html.erb +++ b/app/views/events/_card.html.erb @@ -41,7 +41,7 @@
- <% if current_user.super_user? %> + <% if allowed_to?(:edit?, event) %> <%= link_to "Edit", edit_event_path(event), data: { turbo_frame: "_top"}, diff --git a/app/views/events/_form.html.erb b/app/views/events/_form.html.erb index 5ce00c953..5cb2b29db 100644 --- a/app/views/events/_form.html.erb +++ b/app/views/events/_form.html.erb @@ -20,7 +20,7 @@
- <% if current_user.super_user? %> + <% if allowed_to?(:manage?, @event) %>
<%= f.input :publicly_visible, as: :boolean, label: "Publicly visible?", wrapper_html: { class: "ml-2" } %> <%= f.input :featured, as: :boolean, label: "Featured?", wrapper_html: { class: "ml-2" } %> diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb index f9d0391dc..2bfdfa502 100644 --- a/app/views/events/index.html.erb +++ b/app/views/events/index.html.erb @@ -17,7 +17,7 @@
- <% if current_user&.super_user? %> + <% if allowed_to?(:new?, Event) %> <%= link_to "New Event", new_event_path, class: "whitespace-nowrap btn btn-secondary-outline" %> diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index 37c658144..b079d2186 100644 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -8,7 +8,7 @@
<%= link_to "Index", events_path, class: "btn btn-secondary-outline" %> - <% if current_user.super_user? %> + <% if allowed_to?(:edit, @event) %> <%= link_to "Edit", edit_event_path(@event), class: "btn btn-secondary-outline admin-only bg-blue-100" %> <% end %> From 8fd856743d92b53af29223512eb0fb34e1a25e95 Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Sun, 1 Feb 2026 13:17:19 -0500 Subject: [PATCH 06/19] add specs --- app/policies/event_policy.rb | 4 + spec/policies/event_policy_spec.rb | 163 +++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 spec/policies/event_policy_spec.rb diff --git a/app/policies/event_policy.rb b/app/policies/event_policy.rb index 50edf300d..4aca10778 100644 --- a/app/policies/event_policy.rb +++ b/app/policies/event_policy.rb @@ -5,6 +5,10 @@ class EventPolicy < ApplicationPolicy skip_pre_check :verify_authenticated! + def show? + admin? || record&.publicly_visible? + end + relation_scope do |relation| if admin? relation diff --git a/spec/policies/event_policy_spec.rb b/spec/policies/event_policy_spec.rb new file mode 100644 index 000000000..db15abade --- /dev/null +++ b/spec/policies/event_policy_spec.rb @@ -0,0 +1,163 @@ +require "rails_helper" + +RSpec.describe EventPolicy, type: :policy do + let(:admin_user) { build_stubbed :user, super_user: true } + let(:regular_user) { build_stubbed :user, super_user: false } + let(:published_event) { build_stubbed :event, publicly_visible: true } + let(:unpublished_event) { build_stubbed :event, publicly_visible: false } + let(:open_registration_event) { build_stubbed :event, publicly_visible: true, registration_close_date: 1.day.from_now } + let(:closed_registration_event) { build_stubbed :event, publicly_visible: true, registration_close_date: 1.day.ago } + + def policy_for(record: nil, user:) + described_class.new(record, user: user) + end + + describe "#index?" do + context "with admin user" do + subject { policy_for(user: admin_user) } + + it { is_expected.to be_allowed_to(:index?) } + end + + context "with regular user" do + subject { policy_for(user: regular_user) } + + it { is_expected.to be_allowed_to(:index?) } + end + + context "with no user" do + subject { policy_for(user: nil) } + + it { is_expected.to be_allowed_to(:index?) } + end + end + + describe "#show?" do + context "when event is publicly visible" do + context "with admin user" do + subject { policy_for(record: published_event, user: admin_user) } + + it { is_expected.to be_allowed_to(:show?) } + end + + context "with regular user" do + subject { policy_for(record: published_event, user: regular_user) } + + it { is_expected.to be_allowed_to(:show?) } + end + + context "with no user" do + subject { policy_for(record: published_event, user: nil) } + + it { is_expected.to be_allowed_to(:show?) } + end + end + + context "when event is not publicly visible" do + context "with admin user" do + subject { policy_for(record: unpublished_event, user: admin_user) } + + it { is_expected.to be_allowed_to(:show?) } + end + + context "with regular user" do + subject { policy_for(record: unpublished_event, user: regular_user) } + + it { is_expected.not_to be_allowed_to(:show?) } + end + + context "with no user" do + subject { policy_for(record: unpublished_event, user: nil) } + + it { is_expected.not_to be_allowed_to(:show?) } + end + end + end + + describe "#manage?" do + context "with admin user" do + subject { policy_for(user: admin_user) } + + it { is_expected.to be_allowed_to(:manage?) } + end + + context "with regular user" do + subject { policy_for(user: regular_user) } + + it { is_expected.not_to be_allowed_to(:manage?) } + end + + context "with no user" do + subject { policy_for(user: nil) } + + it { is_expected.not_to be_allowed_to(:manage?) } + end + end + + describe "aliases to :manage?" do + let(:policy) { policy_for(user: admin_user) } + + describe "#new?" do + it "is an alias of :manage? authorization rule" do + expect(:new?).to be_an_alias_of(policy, :manage?) + end + end + + describe "#create?" do + it "is an alias of :manage? authorization rule" do + expect(:create?).to be_an_alias_of(policy, :manage?) + end + end + + describe "#edit?" do + it "is an alias of :manage? authorization rule" do + expect(:edit?).to be_an_alias_of(policy, :manage?) + end + end + + describe "#update?" do + it "is an alias of :manage? authorization rule" do + expect(:update?).to be_an_alias_of(policy, :manage?) + end + end + + describe "#destroy?" do + it "is an alias of :manage? authorization rule" do + expect(:destroy?).to be_an_alias_of(policy, :manage?) + end + end + end + + describe "relation_scope" do + context "with admin user" do + let(:policy) { policy_for(record: Event, user: admin_user) } + + it "returns all events" do + scope = policy.apply_scope(Event.all, type: :active_record_relation) + expect(scope).to eq(Event.all) + end + end + + context "with regular user" do + let(:policy) { policy_for(record: Event, user: regular_user) } + + it "returns only publicly visible events with open registration" do + scope = policy.apply_scope(Event.all, type: :active_record_relation) + expect(scope.to_sql).to include('`events`.`publicly_visible` = 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`') + end + end + + context "with no user" do + let(:policy) { policy_for(record: Event, user: nil) } + + it "returns only publicly visible events with open registration" do + scope = policy.apply_scope(Event.all, type: :active_record_relation) + expect(scope.to_sql).to include('`events`.`publicly_visible` = 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`') + end + end + end +end From 00d581b1a9f70e9b10d46fbeb3ebe4c357650a7d Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:17:56 -0500 Subject: [PATCH 07/19] remove imagemagick --- Dockerfile | 2 -- Dockerfile.dev | 1 - 2 files changed, 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index b01252e89..fed3244cd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,6 @@ FROM base AS assets RUN apt-get update -qq && apt-get install -y \ build-essential \ nodejs \ - imagemagick \ libvips \ poppler-utils \ tzdata \ @@ -53,7 +52,6 @@ RUN SECRET_KEY_BASE=1 \ FROM base AS server RUN apt-get update -qq && apt-get install --no-install-recommends -y \ - imagemagick \ libvips \ poppler-utils \ tzdata \ diff --git a/Dockerfile.dev b/Dockerfile.dev index 4c7963f03..dd2c56a6e 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -6,7 +6,6 @@ RUN apt-get update -qq && apt-get install -y \ git \ curl \ default-libmysqlclient-dev \ - imagemagick \ libvips \ tzdata \ libxml2-dev \ From 7301a109f5143e03351743283ff142003457eaae Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:24:03 -0500 Subject: [PATCH 08/19] rename visitor_featured to public_featured --- ...2162104_rename_visitor_featured_to_public_featured.rb | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 db/migrate/20260202162104_rename_visitor_featured_to_public_featured.rb diff --git a/db/migrate/20260202162104_rename_visitor_featured_to_public_featured.rb b/db/migrate/20260202162104_rename_visitor_featured_to_public_featured.rb new file mode 100644 index 000000000..ee998a61a --- /dev/null +++ b/db/migrate/20260202162104_rename_visitor_featured_to_public_featured.rb @@ -0,0 +1,9 @@ +class RenameVisitorFeaturedToPublicFeatured < ActiveRecord::Migration[8.1] + def change + rename_column :community_news, :visitor_featured, :public_featured + rename_column :events, :visitor_featured, :public_featured + rename_column :resources, :visitor_featured, :public_featured + rename_column :stories, :visitor_featured, :public_featured + rename_column :workshops, :visitor_featured, :public_featured + end +end From dc200ae5232409dd75f5e4091c76d0663c2e0eef Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:26:10 -0500 Subject: [PATCH 09/19] add public flag --- .../20260202162452_add_public_to_dashboard_models.rb | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 db/migrate/20260202162452_add_public_to_dashboard_models.rb diff --git a/db/migrate/20260202162452_add_public_to_dashboard_models.rb b/db/migrate/20260202162452_add_public_to_dashboard_models.rb new file mode 100644 index 000000000..2bd16e30d --- /dev/null +++ b/db/migrate/20260202162452_add_public_to_dashboard_models.rb @@ -0,0 +1,9 @@ +class AddPublicToDashboardModels < ActiveRecord::Migration[8.1] + def change + add_column :community_news, :public, :boolean, default: false, null: false + add_column :events, :public, :boolean, default: false, null: false + add_column :resources, :public, :boolean, default: false, null: false + add_column :stories, :public, :boolean, default: false, null: false + add_column :workshops, :public, :boolean, default: false, null: false + end +end From c10ceff00334f29070bea94d62385d78272ef85b Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Mon, 2 Feb 2026 12:05:53 -0500 Subject: [PATCH 10/19] change to use inactive name --- .../event_registrations_controller.rb | 2 +- app/controllers/events_controller.rb | 2 +- app/models/event.rb | 13 +++++-------- app/policies/event_policy.rb | 6 +++--- ..._publicly_visible_to_inactive_in_events.rb | 6 ++++++ db/schema.rb | 19 ++++++++++++------- db/seeds/dummy_dev_seeds.rb | 2 +- spec/factories/events.rb | 2 +- 8 files changed, 30 insertions(+), 22 deletions(-) create mode 100644 db/migrate/20260202164015_rename_publicly_visible_to_inactive_in_events.rb diff --git a/app/controllers/event_registrations_controller.rb b/app/controllers/event_registrations_controller.rb index 80f542c43..e3c5a5dfe 100644 --- a/app/controllers/event_registrations_controller.rb +++ b/app/controllers/event_registrations_controller.rb @@ -74,7 +74,7 @@ def destroy # Optional hooks for setting variables for forms or index def set_form_variables - @events = Event.publicly_visible.order(:start_date) + @events = Event.where(inactive: false).order(:start_date) @registrants = User.active.order(:last_name, :first_name) end diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index 98f4a2d0f..0697ce756 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -93,7 +93,7 @@ def event_params :featured, :start_date, :end_date, :registration_close_date, - :publicly_visible + :inactive ) end end diff --git a/app/models/event.rb b/app/models/event.rb index 37c5dbb66..e58eb892a 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -20,7 +20,7 @@ class Event < ApplicationRecord # Validations validates_presence_of :title, :start_date, :end_date - validates_inclusion_of :publicly_visible, in: [ true, false ] + validates_inclusion_of :inactive, in: [ true, false ] validates_numericality_of :cost_cents, greater_than_or_equal_to: 0, allow_nil: true # Nested attributes @@ -44,8 +44,9 @@ class Event < ApplicationRecord .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) } - scope :published, ->(published = nil) { publicly_visible(published) } - scope :publicly_visible, ->(publicly_visible = nil) { publicly_visible ? where(publicly_visible: publicly_visible): where(publicly_visible: true) } + + scope :published, ->(published = nil) { published.to_s.present? ? + where(inactive: !published) : where(inactive: false) } scope :category_names, ->(names) { tag_names(:categories, names) } scope :sector_names, ->(names) { tag_names(:sectors, names) } @@ -59,12 +60,8 @@ def self.search_by_params(params) stories end - def inactive? - !publicly_visible - end - def registerable? - publicly_visible && + !inactive && (registration_close_date.nil? || registration_close_date >= Time.current) end diff --git a/app/policies/event_policy.rb b/app/policies/event_policy.rb index 4aca10778..894c39d1d 100644 --- a/app/policies/event_policy.rb +++ b/app/policies/event_policy.rb @@ -6,7 +6,7 @@ class EventPolicy < ApplicationPolicy skip_pre_check :verify_authenticated! def show? - admin? || record&.publicly_visible? + admin? || record&.published end relation_scope do |relation| @@ -14,11 +14,11 @@ def show? relation elsif authenticated? relation.left_outer_joins(:registrants) - .where(publicly_visible: true) + .where(inactive: false) .where("registration_close_date IS NULL OR registration_close_date >= ? OR users.id = ?", Time.current, user.id) .distinct else - relation.where(publicly_visible: true) + relation.where(inactive: false) .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) end end diff --git a/db/migrate/20260202164015_rename_publicly_visible_to_inactive_in_events.rb b/db/migrate/20260202164015_rename_publicly_visible_to_inactive_in_events.rb new file mode 100644 index 000000000..59bdcfb63 --- /dev/null +++ b/db/migrate/20260202164015_rename_publicly_visible_to_inactive_in_events.rb @@ -0,0 +1,6 @@ +class RenamePubliclyVisibleToInactiveInEvents < ActiveRecord::Migration[8.1] + def change + rename_column :events, :publicly_visible, :inactive + change_column_default :events, :inactive, true + end +end diff --git a/db/schema.rb b/db/schema.rb index 9153e0909..d61097724 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_01_27_171722) do +ActiveRecord::Schema[8.1].define(version: 2026_02_02_164015) do create_table "action_text_mentions", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.bigint "action_text_rich_text_id", null: false t.datetime "created_at", null: false @@ -268,12 +268,13 @@ t.integer "created_by_id", null: false t.boolean "featured" t.integer "project_id" + t.boolean "public", default: false, null: false + t.boolean "public_featured", default: false, null: false t.boolean "published" t.string "reference_url" t.string "title" t.datetime "updated_at", null: false t.integer "updated_by_id", null: false - t.boolean "visitor_featured", default: false, null: false t.integer "windows_type_id" t.string "youtube_url" t.index ["author_id"], name: "index_community_news_on_author_id" @@ -316,12 +317,13 @@ t.text "description", size: :medium t.datetime "end_date", precision: nil t.boolean "featured", default: false, null: false - t.boolean "publicly_visible", default: false, null: false + t.boolean "inactive", default: true, null: false + t.boolean "public", default: false, null: false + t.boolean "public_featured", default: false, null: false t.datetime "registration_close_date", precision: nil t.datetime "start_date", precision: nil t.string "title" t.datetime "updated_at", null: false - t.boolean "visitor_featured", default: false, null: false t.index ["created_by_id"], name: "index_events_on_created_by_id" end @@ -663,12 +665,13 @@ t.integer "legacy_id" t.boolean "male", default: false t.integer "position" + t.boolean "public", default: false, null: false + t.boolean "public_featured", default: false, null: false t.text "text", size: :long t.string "title" t.datetime "updated_at", precision: nil, null: false t.string "url" t.integer "user_id" - t.boolean "visitor_featured", default: false, null: false t.integer "windows_type_id" t.integer "workshop_id" t.index ["user_id"], name: "index_resources_on_user_id" @@ -704,13 +707,14 @@ t.boolean "featured", default: false, null: false t.boolean "permission_given" t.integer "project_id" + t.boolean "public", default: false, null: false + t.boolean "public_featured", default: false, null: false t.boolean "published", default: false, null: false t.integer "spotlighted_facilitator_id" t.bigint "story_idea_id" t.string "title" t.datetime "updated_at", null: false t.integer "updated_by_id", null: false - t.boolean "visitor_featured", default: false, null: false t.string "website_url" t.integer "windows_type_id", null: false t.integer "workshop_id" @@ -1035,6 +1039,8 @@ t.text "project", size: :long t.text "project_spanish", size: :long t.string "pub_issue" + t.boolean "public", default: false, null: false + t.boolean "public_featured", default: false, null: false t.boolean "searchable", default: false t.text "setup", size: :long t.text "setup_spanish", size: :long @@ -1057,7 +1063,6 @@ t.string "title" t.datetime "updated_at", precision: nil, null: false t.integer "user_id" - t.boolean "visitor_featured", default: false, null: false t.text "visualization", size: :long t.text "visualization_spanish", size: :long t.text "warm_up", size: :long diff --git a/db/seeds/dummy_dev_seeds.rb b/db/seeds/dummy_dev_seeds.rb index 573a021e8..9d5b4ef73 100644 --- a/db/seeds/dummy_dev_seeds.rb +++ b/db/seeds/dummy_dev_seeds.rb @@ -106,7 +106,7 @@ .first_or_create!( description: Faker::Lorem.paragraph(sentence_count: 6), featured: [ true, false ].sample, - publicly_visible: [ true, true, false ].sample, + inactive: [ false, false, true ].sample, registration_close_date: registration_close, created_by_id: User.first.id, created_at: Time.current - rand(10..90).days, diff --git a/spec/factories/events.rb b/spec/factories/events.rb index 7773d5cc1..2145365b4 100644 --- a/spec/factories/events.rb +++ b/spec/factories/events.rb @@ -5,7 +5,7 @@ start_date { 12.day.from_now } end_date { 14.days.from_now } registration_close_date { 13.days.from_now } - publicly_visible { true } + inactive { false } cost_cents { 1099 } trait :registration_closed do From df1214a81057eedb8867bf534521011f7bdbdf57 Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Mon, 2 Feb 2026 12:19:29 -0500 Subject: [PATCH 11/19] update dashboard policy update scopes --- app/models/community_news.rb | 2 +- app/models/event.rb | 4 ++-- app/models/resource.rb | 2 +- app/models/story.rb | 2 +- app/models/workshop.rb | 2 +- app/policies/dashboard_policy.rb | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/models/community_news.rb b/app/models/community_news.rb index ec70cf65d..a9772e8a4 100644 --- a/app/models/community_news.rb +++ b/app/models/community_news.rb @@ -43,7 +43,7 @@ class CommunityNews < ApplicationRecord end scope :featured, -> { where(featured: true) } - scope :visitor_featured, -> { where(visitor_featured: true) } + scope :public_featured, -> { where(public_featured: true) } scope :category_names, ->(names) { tag_names(:categories, names) } scope :sector_names, ->(names) { tag_names(:sectors, names) } scope :community_news_name, ->(community_news_name) { diff --git a/app/models/event.rb b/app/models/event.rb index e58eb892a..efd9c50d4 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -39,8 +39,8 @@ class Event < ApplicationRecord where(featured: true) .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) } - scope :visitor_featured, -> { - where(visitor_featured: true) + scope :public_featured, -> { + where(public_featured: true) .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) } diff --git a/app/models/resource.rb b/app/models/resource.rb index 7cb0b6a30..65cc819b0 100644 --- a/app/models/resource.rb +++ b/app/models/resource.rb @@ -77,7 +77,7 @@ class Resource < ApplicationRecord scope :category_names, ->(names) { tag_names(:categories, names) } scope :sector_names, ->(names) { tag_names(:sectors, names) } scope :featured, ->(featured = nil) { featured.present? ? where(featured: featured) : where(featured: true) } - scope :visitor_featured, -> { where(visitor_featured: true) } + scope :public_featured, -> { where(public_featured: true) } scope :kinds, ->(kinds) { kinds = Array(kinds).flatten.map(&:to_s) where(kind: kinds) diff --git a/app/models/story.rb b/app/models/story.rb index b7fde0746..6320c1a9b 100644 --- a/app/models/story.rb +++ b/app/models/story.rb @@ -50,7 +50,7 @@ class Story < ApplicationRecord # Scopes scope :featured, -> { where(featured: true) } - scope :visitor_featured, -> { where(visitor_featured: true) } + scope :public_featured, -> { where(public_featured: true) } scope :category_names, ->(names) { tag_names(:categories, names) } scope :sector_names, ->(names) { tag_names(:sectors, names) } scope :story_name, ->(story_name) { diff --git a/app/models/workshop.rb b/app/models/workshop.rb index 0bcc7534b..c5726c937 100644 --- a/app/models/workshop.rb +++ b/app/models/workshop.rb @@ -119,7 +119,7 @@ class Workshop < ApplicationRecord scope :sector_names, ->(names) { tag_names(:sectors, names) } scope :created_by_id, ->(created_by_id) { where(user_id: created_by_id) } scope :featured, -> { where(featured: true) } - scope :visitor_featured, -> { where(visitor_featured: true) } + scope :public_featured, -> { where(public_featured: true) } scope :legacy, -> { where(legacy: true) } scope :published, ->(published = nil) { published.to_s.present? ? where(inactive: !published) : where(inactive: false) } diff --git a/app/policies/dashboard_policy.rb b/app/policies/dashboard_policy.rb index 009322416..da78cb6cc 100644 --- a/app/policies/dashboard_policy.rb +++ b/app/policies/dashboard_policy.rb @@ -9,7 +9,7 @@ def index? if authenticated? relation.featured else - relation.visitor_featured + relation.public_featured end end end From 52484b3925c29210fe2476a8d6c7e06924cc8795 Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Mon, 2 Feb 2026 12:34:10 -0500 Subject: [PATCH 12/19] update forms --- app/views/community_news/_form.html.erb | 13 ++++++-- app/views/events/_form.html.erb | 8 +++-- app/views/resources/_form.html.erb | 15 ++++----- app/views/stories/_form.html.erb | 43 ++++++++++++++----------- app/views/workshops/_form.html.erb | 25 ++++++++++---- 5 files changed, 65 insertions(+), 39 deletions(-) diff --git a/app/views/community_news/_form.html.erb b/app/views/community_news/_form.html.erb index d9423f48d..d9fa408ef 100644 --- a/app/views/community_news/_form.html.erb +++ b/app/views/community_news/_form.html.erb @@ -10,10 +10,17 @@
<%= f.input :title, as: :text, input_html: { rows: 1, class: "w-full" } %> -
- <%= f.input :published, as: :boolean, wrapper_html: { class: "flex items-center" } %> - <%= f.input :featured, as: :boolean, wrapper_html: { class: "flex items-center" } %> + <%# if allowed_to?(:manage?, story) %> +
+ <%= f.input :published, as: :boolean %> + + <%# f.input :inactive, as: :boolean, label: "Hidden?" %> + <%= f.input :featured, as: :boolean, label: "Featured?" %> + <%= f.input :public, as: :boolean, label: "Public?" %> + <%= f.input :public_featured, as: :boolean, label: "Public Featured?" %>
+ + <%# end %>
<%= rhino_editor(f, :body, label: "Body / Description") %> diff --git a/app/views/events/_form.html.erb b/app/views/events/_form.html.erb index 5cb2b29db..4bfb5db4a 100644 --- a/app/views/events/_form.html.erb +++ b/app/views/events/_form.html.erb @@ -21,9 +21,11 @@
<% if allowed_to?(:manage?, @event) %> -
- <%= f.input :publicly_visible, as: :boolean, label: "Publicly visible?", wrapper_html: { class: "ml-2" } %> - <%= f.input :featured, as: :boolean, label: "Featured?", wrapper_html: { class: "ml-2" } %> +
+ <%= f.input :inactive, as: :boolean, label: "Hidden?" %> + <%= f.input :featured, as: :boolean, label: "Featured?" %> + <%= f.input :public, as: :boolean, label: "Public?" %> + <%= f.input :public_featured, as: :boolean, label: "Public Featured?" %>
<% end %>
diff --git a/app/views/resources/_form.html.erb b/app/views/resources/_form.html.erb index 484819ff9..f37bf7781 100644 --- a/app/views/resources/_form.html.erb +++ b/app/views/resources/_form.html.erb @@ -16,15 +16,14 @@
-
-
<%= f.input :featured, as: :boolean, wrapper: false, label_html: { class: "inline" } %>
- -
- <%= f.input :inactive, as: :boolean, - label: "Hidden?", - wrapper: false, label_html: { class: "inline" } %> + <% if allowed_to?(:manage?, @resource) %> +
+ <%= f.input :inactive, as: :boolean, label: "Hidden?" %> + <%= f.input :featured, as: :boolean, label: "Featured?" %> + <%= f.input :public, as: :boolean, label: "Public?" %> + <%= f.input :public_featured, as: :boolean, label: "Public Featured?" %>
-
+ <% end %>
<% if f.object.url.present? %> diff --git a/app/views/stories/_form.html.erb b/app/views/stories/_form.html.erb index fb42e9371..f8c564281 100644 --- a/app/views/stories/_form.html.erb +++ b/app/views/stories/_form.html.erb @@ -11,10 +11,17 @@ input_html: { rows: 2, value: f.object.title || @story_idea&.title } %>
-
- <%= f.input :published, as: :boolean, wrapper_html: { class: "flex items-center" } %> - <%= f.input :featured, as: :boolean, wrapper_html: { class: "flex items-center" } %> + <%# if allowed_to?(:manage?, story) %> +
+ <%= f.input :published, as: :boolean %> + + <%# f.input :inactive, as: :boolean, label: "Hidden?" %> + <%= f.input :featured, as: :boolean, label: "Featured?" %> + <%= f.input :public, as: :boolean, label: "Public?" %> + <%= f.input :public_featured, as: :boolean, label: "Public Featured?" %>
+ + <%# end %>
@@ -126,23 +133,23 @@
- <% if story_idea %> -
- <%= label_tag :promote_idea_assets, class: "flex items-start space-x-3 cursor-pointer" do %> - <%= check_box_tag :promote_idea_assets, true, false, class: "mt-1 h-5 w-5 text-blue-600 rounded border-gray-300 focus:ring-blue-500" %> + <% if story_idea %> +
+ <%= label_tag :promote_idea_assets, class: "flex items-start space-x-3 cursor-pointer" do %> + <%= check_box_tag :promote_idea_assets, true, false, class: "mt-1 h-5 w-5 text-blue-600 rounded border-gray-300 focus:ring-blue-500" %> -
- - If the story idea was submitted with attachments, check this box to transfer them to this new story. - +
+ + If the story idea was submitted with attachments, check this box to transfer them to this new story. + -

- (This will override any attachments uploaded in the form below once you click submit.) -

-
- <% end %> -
- <% end %> +

+ (This will override any attachments uploaded in the form below once you click submit.) +

+
+ <% end %> +
+ <% end %> <% end %>
<%= render "assets/form", owner: @story %>
diff --git a/app/views/workshops/_form.html.erb b/app/views/workshops/_form.html.erb index 285c5055d..18772e13d 100644 --- a/app/views/workshops/_form.html.erb +++ b/app/views/workshops/_form.html.erb @@ -77,10 +77,15 @@
-
- <%= f.input :featured, as: :boolean, wrapper: false %> - <%= f.input :inactive, as: :boolean, label: "Hidden?", wrapper: false %> + <%# if allowed_to?(:manage?, @workshop) %> +
+ <%= f.input :inactive, as: :boolean, label: "Hidden?" %> + <%= f.input :featured, as: :boolean, label: "Featured?" %> + <%= f.input :public, as: :boolean, label: "Public?" %> + <%= f.input :public_featured, as: :boolean, label: "Public Featured?" %>
+ + <%# end %>
<% else %> <%= f.hidden_field :user_id, value: current_user&.id %> @@ -280,8 +285,11 @@ for="<%= id %>" class=" flex items-center gap-2 p-3 cursor-pointer w-auto min-w-[180px] bg-white border border-gray-200 - rounded-lg shadow-sm hover:bg-gray-100 transition"> - <%= hidden_field_tag "workshop[sector_ids][]", "" %> + rounded-lg shadow-sm hover:bg-gray-100 transition + " + > + <%= hidden_field_tag "workshop[sector_ids][]", "" %> + <%= check_box_tag "workshop[sector_ids][]", sector.id, @workshop.sector_ids.include?(sector.id), @@ -309,8 +317,11 @@ for="<%= id %>" class=" flex items-center gap-2 p-3 cursor-pointer w-auto min-w-[180px] bg-white border - border-gray-200 rounded-lg shadow-sm hover:bg-gray-100 transition"> - <%= hidden_field_tag "workshop[category_ids][]", "" %> + border-gray-200 rounded-lg shadow-sm hover:bg-gray-100 transition + " + > + <%= hidden_field_tag "workshop[category_ids][]", "" %> + <%= check_box_tag "workshop[category_ids][]", category.id, @workshop.category_ids.include?(category.id), From cd2ba269d61208f51e174d649e037758de9c06a9 Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Mon, 2 Feb 2026 12:40:06 -0500 Subject: [PATCH 13/19] fix cache --- app/controllers/dashboard_controller.rb | 4 ++-- app/models/workshop.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index d4c1c9d68..16eccb191 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -7,8 +7,8 @@ def index if turbo_frame_request? case turbo_frame_request_id when "dashboard_workshops" - ids = Rails.cache.fetch("featured_and_visitor_featured_workshop_ids", expires_in: 1.year) do - Workshop.featured_or_visitor_featured.pluck(:id) + ids = Rails.cache.fetch("featured_and_public_featured_workshop_ids", expires_in: 1.year) do + Workshop.featured_or_public_featured.pluck(:id) end base_scope = Workshop.includes(:bookmarks, :windows_type, :primary_asset) diff --git a/app/models/workshop.rb b/app/models/workshop.rb index c5726c937..0ce758b53 100644 --- a/app/models/workshop.rb +++ b/app/models/workshop.rb @@ -141,8 +141,8 @@ class Workshop < ApplicationRecord .select("workshops.*, COUNT(bookmarks.id) AS bookmarks_count") .group("workshops.id") } - scope :featured_or_visitor_featured, -> { - where("(featured = ? OR visitor_featured = ?) AND inactive = ?", true, true, false) + scope :featured_or_public_featured, -> { + where("(featured = ? OR public_featured = ?) AND inactive = ?", true, true, false) } # Search Cop From d3e1d73544948b3c3131554dfc53339838fabb4b Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:36:25 -0500 Subject: [PATCH 14/19] update spec --- app/models/workshop.rb | 8 +++--- app/policies/event_policy.rb | 2 +- spec/policies/dashboard_policy_spec.rb | 4 +-- spec/policies/event_policy_spec.rb | 12 ++++---- spec/requests/events_spec.rb | 28 +++++++++---------- ...facilitator_adds_event_to_calendar_test.rb | 4 +-- .../facilitator_registers_for_event_test.rb | 4 +-- spec/views/events/_form.html.erb_spec.rb | 28 ++++++++++--------- spec/views/events/edit.html.erb_spec.rb | 15 +++++----- spec/views/events/index.html.erb_spec.rb | 9 ++++-- spec/views/events/new.html.erb_spec.rb | 3 +- spec/views/events/show.html.erb_spec.rb | 1 + 12 files changed, 63 insertions(+), 55 deletions(-) diff --git a/app/models/workshop.rb b/app/models/workshop.rb index 0ce758b53..f1b0416e5 100644 --- a/app/models/workshop.rb +++ b/app/models/workshop.rb @@ -282,13 +282,13 @@ def attachable_content_type end def invalidate_featured_cache_if_changed - if featured_or_visitor_featured_changed? - Rails.cache.delete("featured_and_visitor_featured_workshop_ids") + if featured_or_public_featured_changed? + Rails.cache.delete("featured_and_public_featured_workshop_ids") end end - def featured_or_visitor_featured_changed? - featured_changed? || visitor_featured_changed? || inactive_changed? + def featured_or_public_featured_changed? + featured_changed? || public_featured_changed? || inactive_changed? end def attach_assets_from_idea! diff --git a/app/policies/event_policy.rb b/app/policies/event_policy.rb index 894c39d1d..da81bb70d 100644 --- a/app/policies/event_policy.rb +++ b/app/policies/event_policy.rb @@ -6,7 +6,7 @@ class EventPolicy < ApplicationPolicy skip_pre_check :verify_authenticated! def show? - admin? || record&.published + admin? || !record&.inactive? end relation_scope do |relation| diff --git a/spec/policies/dashboard_policy_spec.rb b/spec/policies/dashboard_policy_spec.rb index d6b522071..da853e832 100644 --- a/spec/policies/dashboard_policy_spec.rb +++ b/spec/policies/dashboard_policy_spec.rb @@ -50,9 +50,9 @@ def policy_for(record: nil, user:) context "without user" do let(:policy) { policy_for(record: Workshop, user: nil) } - it "returns visitor_featured scope for unauthenticated users" do + it "returns public_featured scope for unauthenticated users" do scope = policy.apply_scope(Workshop.all, type: :active_record_relation) - expect(scope.to_sql).to include('`workshops`.`visitor_featured` = TRUE') + expect(scope.to_sql).to include('`workshops`.`public_featured` = TRUE') end end end diff --git a/spec/policies/event_policy_spec.rb b/spec/policies/event_policy_spec.rb index db15abade..2d4c6c93a 100644 --- a/spec/policies/event_policy_spec.rb +++ b/spec/policies/event_policy_spec.rb @@ -3,10 +3,10 @@ RSpec.describe EventPolicy, type: :policy do let(:admin_user) { build_stubbed :user, super_user: true } let(:regular_user) { build_stubbed :user, super_user: false } - let(:published_event) { build_stubbed :event, publicly_visible: true } - let(:unpublished_event) { build_stubbed :event, publicly_visible: false } - let(:open_registration_event) { build_stubbed :event, publicly_visible: true, registration_close_date: 1.day.from_now } - let(:closed_registration_event) { build_stubbed :event, publicly_visible: true, registration_close_date: 1.day.ago } + let(:published_event) { build_stubbed :event, inactive: false } + let(:unpublished_event) { build_stubbed :event, inactive: true } + let(:open_registration_event) { build_stubbed :event, inactive: false, registration_close_date: 1.day.from_now } + let(:closed_registration_event) { build_stubbed :event, inactive: false, registration_close_date: 1.day.ago } def policy_for(record: nil, user:) described_class.new(record, user: user) @@ -143,7 +143,7 @@ def policy_for(record: nil, user:) it "returns only publicly visible events with open registration" do scope = policy.apply_scope(Event.all, type: :active_record_relation) - expect(scope.to_sql).to include('`events`.`publicly_visible` = TRUE') + expect(scope.to_sql).to include('`events`.`inactive` = FALSE') 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`') end @@ -154,7 +154,7 @@ def policy_for(record: nil, user:) it "returns only publicly visible events with open registration" do scope = policy.apply_scope(Event.all, type: :active_record_relation) - expect(scope.to_sql).to include('`events`.`publicly_visible` = TRUE') + expect(scope.to_sql).to include('`events`.`inactive` = FALSE') 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`') end diff --git a/spec/requests/events_spec.rb b/spec/requests/events_spec.rb index c9d4ecce1..5a16a19bf 100644 --- a/spec/requests/events_spec.rb +++ b/spec/requests/events_spec.rb @@ -8,7 +8,7 @@ "start_date": 1.day.from_now, "end_date": 2.days.from_now, "registration_close_date": 3.days.ago, - "publicly_visible": true + "inactive": false } } @@ -49,9 +49,9 @@ describe "GET /new" do it "renders a successful response" do - sign_in user + sign_in admin allow_any_instance_of(ApplicationController). - to receive(:current_user).and_return(user) + to receive(:current_user).and_return(admin) get new_event_url @@ -79,7 +79,7 @@ get edit_event_url(event) expect(response).to have_http_status(:found) # 302 redirect - expect(response).to redirect_to(events_path) + expect(response).to redirect_to(root_path) end end end @@ -87,27 +87,27 @@ describe "POST /create" do context "with valid parameters" do it "creates a new Event" do - sign_in user + sign_in admin expect { post events_url, params: { event: valid_attributes } }.to change(Event, :count).by(1) end it "redirects to the events index" do - sign_in user + sign_in admin post events_url, params: { event: valid_attributes } expect(response).to redirect_to(events_url) end it "displays notice if present" do - sign_in user + sign_in admin post events_url, params: { event: { title: "sample title", description: "sample description", start_date: 1.day.from_now, end_date: 2.days.from_now, registration_close_date: 3.days.ago, - publicly_visible: true + inactive: false } } follow_redirect! # flash shows after redirect @@ -117,14 +117,14 @@ context "with invalid parameters" do it "does not create a new Event" do - sign_in user + sign_in admin expect { post events_url, params: { event: invalid_attributes } }.to change(Event, :count).by(0) end it "renders a response with validation errors (i.e. to display the 'new' template)" do - sign_in user + sign_in admin post events_url, params: { event: invalid_attributes } expect(response).to have_http_status(:unprocessable_content) end @@ -141,7 +141,7 @@ context "when signed in as admin" do it "updates the requested event" do - sign_in user + sign_in admin allow_any_instance_of(ApplicationController). to receive(:current_user).and_return(admin) patch event_url(event), params: { event: new_attributes } @@ -151,7 +151,7 @@ end it "redirects to the events index" do - sign_in user + sign_in admin allow_any_instance_of(ApplicationController). to receive(:current_user).and_return(admin) @@ -169,7 +169,7 @@ patch event_url(event), params: { event: new_attributes } expect(response).to have_http_status(:found) - expect(response).to redirect_to(events_path) + expect(response).to redirect_to(root_path) end end end @@ -197,7 +197,7 @@ end it "redirects to the events list" do - sign_in user + sign_in admin allow_any_instance_of(ApplicationController). to receive(:current_user).and_return(admin) delete event_url(event) diff --git a/spec/system/facilitator_adds_event_to_calendar_test.rb b/spec/system/facilitator_adds_event_to_calendar_test.rb index b507f4dd2..88a25ac8f 100644 --- a/spec/system/facilitator_adds_event_to_calendar_test.rb +++ b/spec/system/facilitator_adds_event_to_calendar_test.rb @@ -12,7 +12,7 @@ start_date: 1.day.from_now, end_date: 2.days.from_now, registration_close_date: 1.day.from_now, - publicly_visible: true, + inactive: false, featured: true ) create(:event, @@ -20,7 +20,7 @@ start_date: 2.days.from_now, end_date: 3.days.from_now, registration_close_date: 1.day.from_now, - publicly_visible: true, + inactive: false, featured: true, created_by: user ) diff --git a/spec/system/facilitator_registers_for_event_test.rb b/spec/system/facilitator_registers_for_event_test.rb index 8193553e4..47f519481 100644 --- a/spec/system/facilitator_registers_for_event_test.rb +++ b/spec/system/facilitator_registers_for_event_test.rb @@ -12,7 +12,7 @@ start_date: 1.day.from_now, end_date: 2.days.from_now, registration_close_date: 1.day.from_now, - publicly_visible: true, +inactive: false, featured: true ) create(:event, @@ -20,7 +20,7 @@ start_date: 2.days.from_now, end_date: 3.days.from_now, registration_close_date: 1.day.from_now, - publicly_visible: true, + inactive: false, featured: true, created_by: user ) diff --git a/spec/views/events/_form.html.erb_spec.rb b/spec/views/events/_form.html.erb_spec.rb index 8aa48972c..b03198005 100644 --- a/spec/views/events/_form.html.erb_spec.rb +++ b/spec/views/events/_form.html.erb_spec.rb @@ -6,6 +6,7 @@ before do assign(:event, event) allow(view).to receive(:current_user).and_return(build_stubbed(:user, super_user: true)) + allow(view).to receive(:allowed_to?).with(:manage?, event).and_return(true) end it "renders all form fields" do @@ -17,7 +18,7 @@ expect(rendered).to have_selector("input[type='datetime-local'][name='event[start_date]']") expect(rendered).to have_selector("input[type='datetime-local'][name='event[end_date]']") expect(rendered).to have_selector("input[type='datetime-local'][name='event[registration_close_date]']") - expect(rendered).to have_selector("input[type='checkbox'][name='event[publicly_visible]']") + expect(rendered).to have_selector("input[type='checkbox'][name='event[inactive]']") end it "renders all form labels" do @@ -29,7 +30,7 @@ expect(rendered).to have_selector("label", text: "Start time") expect(rendered).to have_selector("label", text: "End time") expect(rendered).to have_selector("label", text: "Registration close time") - expect(rendered).to have_selector("label", text: "Publicly visible") + expect(rendered).to have_selector("label", text: "Hidden?") end it "renders submit button" do @@ -46,7 +47,7 @@ start_date: DateTime.new(2024, 1, 15, 10, 0), end_date: DateTime.new(2024, 1, 15, 16, 0), registration_close_date: DateTime.new(2024, 1, 10, 23, 59), - publicly_visible: true) + inactive: false) end it "populates form fields with existing data" do @@ -54,7 +55,8 @@ expect(rendered).to have_field("event[title]", with: "Existing Event") expect(rendered).to have_selector("textarea", text: "Existing description") - expect(rendered).to have_selector("input[type='checkbox'][checked='checked']") + expect(rendered).to have_selector("input[type='checkbox'][name='event[inactive]']") + expect(rendered).not_to have_selector("input[type='checkbox'][checked='checked']") end it "populates datetime fields with properly formatted values" do @@ -93,24 +95,24 @@ end end - context "when publicly_visible is false" do - let(:event) { create(:event, publicly_visible: false) } + context "when inactive is true" do + let(:event) { create(:event, inactive: true) } - it "renders unchecked checkbox" do + it "renders checked checkbox" do render - expect(rendered).to have_selector("input[type='checkbox'][name='event[publicly_visible]']") - expect(rendered).not_to have_selector("input[type='checkbox'][checked='checked']") + expect(rendered).to have_selector("input[type='checkbox'][name='event[inactive]'][checked='checked']") end end - context "when publicly_visible is true" do - let(:event) { create(:event, publicly_visible: true) } + context "when inactive is false" do + let(:event) { create(:event, inactive: false) } - it "renders checked checkbox" do + it "renders unchecked checkbox" do render - expect(rendered).to have_selector("input[type='checkbox'][name='event[publicly_visible]'][checked='checked']") + expect(rendered).to have_selector("input[type='checkbox'][name='event[inactive]']") + expect(rendered).not_to have_selector("input[type='checkbox'][checked='checked']") end end end diff --git a/spec/views/events/edit.html.erb_spec.rb b/spec/views/events/edit.html.erb_spec.rb index b226376bd..b823f5eaa 100644 --- a/spec/views/events/edit.html.erb_spec.rb +++ b/spec/views/events/edit.html.erb_spec.rb @@ -8,12 +8,13 @@ start_date: DateTime.new(2024, 1, 15, 10, 0), end_date: DateTime.new(2024, 1, 15, 16, 0), registration_close_date: DateTime.new(2024, 1, 10, 23, 59), - publicly_visible: true) + inactive: false) end before do assign(:event, event) allow(view).to receive(:current_user).and_return(build_stubbed(:user, super_user: true)) + allow(view).to receive(:allowed_to?).with(:manage?, event).and_return(true) end it "renders the editing event heading" do @@ -28,7 +29,8 @@ expect(rendered).to have_selector("form") expect(rendered).to have_field("event[title]", with: "Original Title") expect(rendered).to have_selector("textarea[name='event[description]']", text: "Original description") - expect(rendered).to have_selector("input[type='checkbox'][name='event[publicly_visible]'][checked='checked']") + expect(rendered).to have_selector("input[type='checkbox'][name='event[inactive]']") + expect(rendered).not_to have_selector("input[type='checkbox'][checked='checked']") end it "renders action links" do @@ -58,18 +60,17 @@ end end - context "when event is not publicly visible" do + context "when event is inactive" do let(:event) do create(:event, title: "Private Event", - publicly_visible: false) + inactive: true) end - it "renders unchecked checkbox" do + it "renders checked checkbox" do render - expect(rendered).to have_selector("input[type='checkbox'][name='event[publicly_visible]']") - expect(rendered).not_to have_selector("input[type='checkbox'][name='event[publicly_visible]'][checked='checked']") + expect(rendered).to have_selector("input[type='checkbox'][name='event[inactive]'][checked='checked']") end end end diff --git a/spec/views/events/index.html.erb_spec.rb b/spec/views/events/index.html.erb_spec.rb index 1a80980c1..7d3d56466 100644 --- a/spec/views/events/index.html.erb_spec.rb +++ b/spec/views/events/index.html.erb_spec.rb @@ -5,26 +5,29 @@ let(:event_closed) { create(:event, title: "Event 1", start_date: 1.day.from_now, end_date: 2.days.from_now, - publicly_visible: true, + inactive: false, registration_close_date: -3.days.from_now) } let(:event_open) { create(:event, title: "Event 2", start_date: 3.days.from_now, end_date: 4.days.from_now, registration_close_date: 5.days.from_now, - publicly_visible: true) + inactive: false) } let(:event_open_2) { create(:event, title: "Event 2", start_date: 3.days.from_now, end_date: 4.days.from_now, registration_close_date: nil, - publicly_visible: true) + inactive: false) } let(:events) { [ event_open, event_open ] } before do assign(:events, events) allow(view).to receive(:current_user).and_return(user) + allow(view).to receive(:allowed_to?).with(:new?, Event).and_return(true) + allow(view).to receive(:allowed_to?).with(:edit?, Event).and_return(true) + allow(view).to receive(:allowed_to?).with(:update?, Bookmark).and_return(true) end it "renders each event with checkbox and details" do diff --git a/spec/views/events/new.html.erb_spec.rb b/spec/views/events/new.html.erb_spec.rb index 5cd6a2922..79d2a6697 100644 --- a/spec/views/events/new.html.erb_spec.rb +++ b/spec/views/events/new.html.erb_spec.rb @@ -6,6 +6,7 @@ before do assign(:event, event) allow(view).to receive(:current_user).and_return(build_stubbed(:user, super_user: true)) + allow(view).to receive(:allowed_to?).with(:manage?, event).and_return(true) end it "renders the new event heading" do @@ -23,7 +24,7 @@ expect(rendered).to have_selector("input[type='datetime-local'][name='event[start_date]']") expect(rendered).to have_selector("input[type='datetime-local'][name='event[end_date]']") expect(rendered).to have_selector("input[type='datetime-local'][name='event[registration_close_date]']") - expect(rendered).to have_selector("input[type='checkbox'][name='event[publicly_visible]']") + expect(rendered).to have_selector("input[type='checkbox'][name='event[inactive]']") end it "renders the Cancel link" do diff --git a/spec/views/events/show.html.erb_spec.rb b/spec/views/events/show.html.erb_spec.rb index 261aada5d..b992473fa 100644 --- a/spec/views/events/show.html.erb_spec.rb +++ b/spec/views/events/show.html.erb_spec.rb @@ -13,6 +13,7 @@ before do assign(:event, event.decorate) allow(view).to receive(:current_user).and_return(build_stubbed(:user, super_user: true)) + allow(view).to receive(:allowed_to?).and_return(true) end it "renders the event title" do From 5e0fdfe6d6b730fe49373147472331b8fce98890 Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:55:56 -0500 Subject: [PATCH 15/19] add params and fix policy --- app/controllers/community_news_controller.rb | 2 +- app/controllers/events_controller.rb | 6 ++++-- app/controllers/resources_controller.rb | 2 +- app/controllers/stories_controller.rb | 2 +- app/controllers/workshops_controller.rb | 2 ++ app/policies/event_policy.rb | 4 +++- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/controllers/community_news_controller.rb b/app/controllers/community_news_controller.rb index a07116777..5a087c6fd 100644 --- a/app/controllers/community_news_controller.rb +++ b/app/controllers/community_news_controller.rb @@ -93,7 +93,7 @@ def set_community_news # Strong parameters def community_news_params params.require(:community_news).permit( - :title, :rhino_body, :published, :featured, + :title, :rhino_body, :published, :featured, :public, :public_featured, :reference_url, :youtube_url, :project_id, :author_id, :created_by_id, :updated_by_id diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index 0697ce756..2a2f4df7e 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -1,6 +1,6 @@ class EventsController < ApplicationController include AhoyViewTracking, AssetUpdatable - skip_before_action :authenticate_user!, only: %i[ index] + skip_before_action :authenticate_user!, only: %i[ index show] before_action :set_event, only: %i[ show edit update destroy ] def index @@ -93,7 +93,9 @@ def event_params :featured, :start_date, :end_date, :registration_close_date, - :inactive + :inactive, + :public, + :public_featured ) end end diff --git a/app/controllers/resources_controller.rb b/app/controllers/resources_controller.rb index 071cbb1d6..f82cccda4 100644 --- a/app/controllers/resources_controller.rb +++ b/app/controllers/resources_controller.rb @@ -147,7 +147,7 @@ def resource_id_param def resource_params params.require(:resource).permit( - :rhino_text, :kind, :male, :female, :title, :featured, :inactive, :url, + :rhino_text, :kind, :male, :female, :title, :featured, :inactive, :public, :public_featured, :url, :agency, :author, :filemaker_code, :windows_type_id, :position, categorizable_items_attributes: [ :id, :category_id, :_destroy ], category_ids: [], sectorable_items_attributes: [ :id, :sector_id, :is_leader, :_destroy ], sector_ids: [] diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb index 0f37da84f..c442bdc89 100644 --- a/app/controllers/stories_controller.rb +++ b/app/controllers/stories_controller.rb @@ -110,7 +110,7 @@ def set_story # Strong parameters def story_params params.require(:story).permit( - :title, :rhino_body, :featured, :published, :youtube_url, :website_url, + :title, :rhino_body, :featured, :published, :public, :public_featued, :youtube_url, :website_url, :windows_type_id, :project_id, :workshop_id, :external_workshop_title, :created_by_id, :updated_by_id, :story_idea_id, :spotlighted_facilitator_id ) diff --git a/app/controllers/workshops_controller.rb b/app/controllers/workshops_controller.rb index 7a3476b2b..429093615 100644 --- a/app/controllers/workshops_controller.rb +++ b/app/controllers/workshops_controller.rb @@ -239,6 +239,8 @@ def workshop_params :title, :featured, :inactive, :full_name, :user_id, :windows_type_id, :workshop_idea_id, :month, :year, + :public, + :public_featured, :time_intro, :time_closing, :time_creation, :time_demonstration, :time_warm_up, :time_opening, :time_opening_circle, diff --git a/app/policies/event_policy.rb b/app/policies/event_policy.rb index da81bb70d..e47272f8d 100644 --- a/app/policies/event_policy.rb +++ b/app/policies/event_policy.rb @@ -6,7 +6,9 @@ class EventPolicy < ApplicationPolicy skip_pre_check :verify_authenticated! def show? - admin? || !record&.inactive? + admin? || + record.public? || + (!record.inactive? && authenticated?) end relation_scope do |relation| From e8b8168e549494f80459280f948bd54aede8a878 Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:20:34 -0500 Subject: [PATCH 16/19] fix variable name --- app/views/community_news/_form.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/community_news/_form.html.erb b/app/views/community_news/_form.html.erb index d9fa408ef..ed1ba1048 100644 --- a/app/views/community_news/_form.html.erb +++ b/app/views/community_news/_form.html.erb @@ -10,7 +10,7 @@
<%= f.input :title, as: :text, input_html: { rows: 1, class: "w-full" } %> - <%# if allowed_to?(:manage?, story) %> + <%# if allowed_to?(:manage?, @commenity_news) %>
<%= f.input :published, as: :boolean %> From aff27f118caeb6e26760d7f970cd538657992d47 Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:31:45 -0500 Subject: [PATCH 17/19] as public to scope --- app/models/event.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/event.rb b/app/models/event.rb index efd9c50d4..796245af1 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -40,7 +40,7 @@ class Event < ApplicationRecord .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) } scope :public_featured, -> { - where(public_featured: true) + where(public: true, public_featured: true) .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) } From 8fc32b682bcfd092dcbf76476dfa34c6dc9dc133 Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Mon, 2 Feb 2026 17:35:49 -0500 Subject: [PATCH 18/19] handle unauthenticated user --- .../_editable_bookmark_button.html.erb | 25 +++++++------ app/views/events/show.html.erb | 37 ++++--------------- 2 files changed, 21 insertions(+), 41 deletions(-) diff --git a/app/views/bookmarks/_editable_bookmark_button.html.erb b/app/views/bookmarks/_editable_bookmark_button.html.erb index 5bdb604d6..18408f7e8 100644 --- a/app/views/bookmarks/_editable_bookmark_button.html.erb +++ b/app/views/bookmarks/_editable_bookmark_button.html.erb @@ -1,8 +1,8 @@ -<% resource = Draper.undecorate(resource) %> - -<%= tag.span id: dom_id(resource, :bookmark_button), data: {controller: "optimistic-bookmark"} do %> - <% if resource.bookmarks.any? { |b| b.user_id == current_user.id } %> - <%= button_to bookmark_path(current_user.bookmark_for(resource)), +<% if allowed_to?(:update?, Bookmark) %> + <% resource = Draper.undecorate(resource) %> + <%= tag.span id: dom_id(resource, :bookmark_button), data: {controller: "optimistic-bookmark"} do %> + <% if resource.bookmarks.any? { |b| b.user_id == current_user.id } %> + <%= button_to bookmark_path(current_user.bookmark_for(resource)), method: :delete, class: "inline-flex items-center gap-2 px-4 py-2 border border-gray-500 text-primary rounded-lg @@ -12,18 +12,19 @@ turbo_confirm: "Remove bookmark?", action: "click->optimistic-bookmark#toggle" } do %> - - Bookmarked - <% end %> - <% else %> - <%= button_to bookmarks_path(bookmark: { bookmarkable_id: resource.id, bookmarkable_type: resource.class.name }), + + Bookmarked + <% end %> + <% else %> + <%= button_to bookmarks_path(bookmark: { bookmarkable_id: resource.id, bookmarkable_type: resource.class.name }), class: "inline-flex items-center gap-2 px-4 py-2 border border-gray-500 text-primary rounded-lg hover:bg-primary hover:text-white transition-colors duration-200 font-medium shadow-sm leading-none", data: { action: "click->optimistic-bookmark#toggle" } do %> - - Bookmark + + Bookmark + <% end %> <% end %> <% end %> <% end %> diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index b079d2186..926c7e233 100644 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -1,9 +1,7 @@
-
-
<%= link_to "Index", events_path, class: "btn btn-secondary-outline" %> @@ -13,41 +11,29 @@ class: "btn btn-secondary-outline admin-only bg-blue-100" %> <% end %> - - <%= render "bookmarks/editable_bookmark_button", resource: @event.object %> - + <%= render "bookmarks/editable_bookmark_button", resource: @event.object %>
-
-
- -
- <%= title_with_badges(@event, font_size: "text-3xl") %> -
+
<%= title_with_badges(@event, font_size: "text-3xl") %>
- -
- <%= @event.times(display_day: true, display_date: true) %> -
+
<%= @event.times(display_day: true, display_date: true) %>
<% if @event.labelled_cost %> -
- <%= @event.labelled_cost %> -
+
<%= @event.labelled_cost %>
<% end %>
- <% registered = @event.event_registrations.exists?(registrant_id: current_user.id) %> + <% registered = @event.event_registrations.exists?(registrant_id: current_user&.id) %> <% if registered %> <%= button_to "De-register", @@ -55,7 +41,6 @@ method: :delete, data: { turbo_confirm: "Are you sure?" }, class: "btn btn-secondary-outline" %> - <% elsif @event.registerable? %> <%= button_to "Register", event_registrant_registration_path(event_id: @event), @@ -73,22 +58,19 @@ <% if registered %> -
- <%= @event.calendar_links %> -
+
<%= @event.calendar_links %>
<% end %> -
<% if @event.registration_close_date %>

Registration Close Date

+

<%= @event.registration_close_date&.strftime("%B %-d, %Y %l:%M %P") || "—" %>

<% end %> -
@@ -101,11 +83,8 @@
-

- <%= @event.detail.presence || "—" %> -

+

<%= @event.detail.presence || "—" %>

-
From 9282edc00475c4b7568021f4ee6e140226e59af4 Mon Sep 17 00:00:00 2001 From: Justin Miller <16829344+jmilljr24@users.noreply.github.com> Date: Mon, 2 Feb 2026 17:50:06 -0500 Subject: [PATCH 19/19] clean up --- spec/policies/event_policy_spec.rb | 13 +++++++------ spec/system/workshops_spec.rb | 4 ---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/spec/policies/event_policy_spec.rb b/spec/policies/event_policy_spec.rb index 2d4c6c93a..45a108116 100644 --- a/spec/policies/event_policy_spec.rb +++ b/spec/policies/event_policy_spec.rb @@ -3,7 +3,8 @@ RSpec.describe EventPolicy, type: :policy do let(:admin_user) { build_stubbed :user, super_user: true } let(:regular_user) { build_stubbed :user, super_user: false } - let(:published_event) { build_stubbed :event, inactive: false } + let(:published_event) { build_stubbed :event, inactive: false } + let(:public_event) { build_stubbed :event, inactive: false, public: true } let(:unpublished_event) { build_stubbed :event, inactive: true } let(:open_registration_event) { build_stubbed :event, inactive: false, registration_close_date: 1.day.from_now } let(:closed_registration_event) { build_stubbed :event, inactive: false, registration_close_date: 1.day.ago } @@ -33,7 +34,7 @@ def policy_for(record: nil, user:) end describe "#show?" do - context "when event is publicly visible" do + context "when event is visible" do context "with admin user" do subject { policy_for(record: published_event, user: admin_user) } @@ -47,13 +48,13 @@ def policy_for(record: nil, user:) end context "with no user" do - subject { policy_for(record: published_event, user: nil) } + subject { policy_for(record: public_event, user: nil) } it { is_expected.to be_allowed_to(:show?) } end end - context "when event is not publicly visible" do + context "when event is not visible" do context "with admin user" do subject { policy_for(record: unpublished_event, user: admin_user) } @@ -141,7 +142,7 @@ def policy_for(record: nil, user:) context "with regular user" do let(:policy) { policy_for(record: Event, user: regular_user) } - it "returns only publicly visible events with open registration" do + it "returns only visible events with open registration" do scope = policy.apply_scope(Event.all, type: :active_record_relation) expect(scope.to_sql).to include('`events`.`inactive` = FALSE') expect(scope.to_sql).to include('registration_close_date IS NULL OR registration_close_date >=') @@ -152,7 +153,7 @@ def policy_for(record: nil, user:) context "with no user" do let(:policy) { policy_for(record: Event, user: nil) } - it "returns only publicly visible events with open registration" do + it "returns only visible events with open registration" do scope = policy.apply_scope(Event.all, type: :active_record_relation) expect(scope.to_sql).to include('`events`.`inactive` = FALSE') expect(scope.to_sql).to include('registration_close_date IS NULL OR registration_close_date >=') diff --git a/spec/system/workshops_spec.rb b/spec/system/workshops_spec.rb index 88dbf36c8..56c66e571 100644 --- a/spec/system/workshops_spec.rb +++ b/spec/system/workshops_spec.rb @@ -69,8 +69,6 @@ visit new_workshop_path(windows_type_id: adult_window.id) - save_and_open_page - fill_in "workshop_title", with: 'My New Workshop' select adult_window.short_name, from: 'workshop_windows_type_id' find('#body-button').click @@ -78,8 +76,6 @@ click_on 'Submit' - save_and_open_page - # expect(Workshop.last.title).to eq('My New Workshop') expect(page).to have_content('My New Workshop') # expect(page).to have_content('Learn something new')