diff --git a/app/abilities/ability.rb b/app/abilities/ability.rb index 84d2e113e08..313e604e064 100644 --- a/app/abilities/ability.rb +++ b/app/abilities/ability.rb @@ -71,7 +71,7 @@ def initialize(user) can [:create, :destroy], CommunityMember, :user_id => user.id can [:destroy, :edit, :update], CommunityMember, :community => user_is_community_organizer can [:destroy], CommunityMember, :user_id => user.id - can [:new, :create], Event, :community => user_is_community_organizer + can [:create, :edit, :new, :update], Event, :community => user_is_community_organizer if user.moderator? can [:hide, :hidecomment], DiaryEntry diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index e76e03c9e22..b0604836c5a 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -1,7 +1,7 @@ class EventsController < ApplicationController layout "site" before_action :authorize_web - before_action :set_event, :only => [:edit, :show] + before_action :set_event, :only => [:edit, :show, :update] # This needs to be one before load_and_authorize_resource, so cancancan will be handed # an event that contains a community, based on the input parameter community_id. before_action :set_params_for_new, :only => [:new] @@ -33,22 +33,29 @@ def new # POST /events.json def create @event = Event.new(event_params) + @event_organizer = EventOrganizer.new(:event => @event, :user => current_user) - respond_to do |format| - if @event.save - warn_if_event_in_past - format.html { redirect_to @event, :notice => t(".success") } - format.json { render :show, :status => :created, :location => @event } - else - format.html { render :new } - format.json { render :json => @event.errors, :status => :unprocessable_entity } - end + if @event.save && @event_organizer.save + warn_if_event_in_past + redirect_to @event, :notice => t(".success") + else + flash.now[:alert] = t(".failure") + render :new end end # GET /events/1/edit def edit; end + def update + if @event.update(event_params) + redirect_to @event, :notice => t(".success") + else + flash.now[:alert] = t(".failure") + render :edit + end + end + # GET /events/1 # GET /events/1.json def show diff --git a/app/models/event.rb b/app/models/event.rb index 1d53f51e001..72ed286967e 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -17,6 +17,7 @@ class Event < ApplicationRecord belongs_to :community + has_many :event_organizers scope :future, -> { where("moment >= ?", Time.now.utc) } scope :past, -> { where("moment < ?", Time.now.utc) } @@ -47,6 +48,10 @@ class Event < ApplicationRecord } ) + def organizers + EventOrganizer.where(:event_id => id) + end + def past? moment < Time.now.utc end diff --git a/app/models/event_organizer.rb b/app/models/event_organizer.rb new file mode 100644 index 00000000000..0982c1fb4c9 --- /dev/null +++ b/app/models/event_organizer.rb @@ -0,0 +1,4 @@ +class EventOrganizer < ApplicationRecord + belongs_to :event + belongs_to :user +end diff --git a/app/views/events/edit.html.erb b/app/views/events/edit.html.erb new file mode 100644 index 00000000000..82a4785900d --- /dev/null +++ b/app/views/events/edit.html.erb @@ -0,0 +1,3 @@ +

<%= t(".edit_event") %>

+ +<%= render "form", :event => @event %> diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index 6e646d8d20a..2d89e2c312e 100644 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -32,7 +32,9 @@ <%= t(".hosted_by") %>: <%= link_to @event.community.name, community_path(@event.community) %>

- <%= t(".organized_by") %>: TBD + <%= t(".organized_by") %>: <% @event.organizers.each do |organizer| %> + <%= link_to organizer.user.display_name, user_path(organizer.user) %> + <% end %>

diff --git a/config/locales/en.yml b/config/locales/en.yml index ceaf4bcba13..5a3a7ea5fca 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -702,6 +702,9 @@ en: events: create: success: Event was created successfully. + failure: Event was not created. + edit: + edit_event: Edit Event index: description: "Description" events: "Events" @@ -726,6 +729,9 @@ en: organized_by: "Organized by" past: "Event is in the past." when: "When" + update: + success: "The event was successfully updated." + failure: "The event was not updated." friendships: make_friend: heading: "Add %{user} as a friend?" diff --git a/db/migrate/20221008224134_create_event_organizers.rb b/db/migrate/20221008224134_create_event_organizers.rb new file mode 100644 index 00000000000..3d37351631b --- /dev/null +++ b/db/migrate/20221008224134_create_event_organizers.rb @@ -0,0 +1,10 @@ +class CreateEventOrganizers < ActiveRecord::Migration[7.0] + def change + create_table :event_organizers do |t| + t.references :event, :foreign_key => true, :null => false, :index => true + t.references :user, :foreign_key => true, :null => false, :index => true + + t.timestamps + end + end +end diff --git a/db/structure.sql b/db/structure.sql index 8d7607bdd3b..c8d207ccd1f 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -777,6 +777,38 @@ CREATE TABLE public.diary_entry_subscriptions ( ); +-- +-- Name: event_organizers; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.event_organizers ( + id bigint NOT NULL, + event_id bigint NOT NULL, + user_id bigint NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: event_organizers_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.event_organizers_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: event_organizers_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.event_organizers_id_seq OWNED BY public.event_organizers.id; + + -- -- Name: events; Type: TABLE; Schema: public; Owner: - -- @@ -1820,6 +1852,13 @@ ALTER TABLE ONLY public.diary_comments ALTER COLUMN id SET DEFAULT nextval('publ ALTER TABLE ONLY public.diary_entries ALTER COLUMN id SET DEFAULT nextval('public.diary_entries_id_seq'::regclass); +-- +-- Name: event_organizers id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.event_organizers ALTER COLUMN id SET DEFAULT nextval('public.event_organizers_id_seq'::regclass); + + -- -- Name: events id; Type: DEFAULT; Schema: public; Owner: - -- @@ -2151,6 +2190,14 @@ ALTER TABLE ONLY public.diary_entry_subscriptions ADD CONSTRAINT diary_entry_subscriptions_pkey PRIMARY KEY (user_id, diary_entry_id); +-- +-- Name: event_organizers event_organizers_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.event_organizers + ADD CONSTRAINT event_organizers_pkey PRIMARY KEY (id); + + -- -- Name: events events_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -2715,6 +2762,20 @@ CREATE INDEX index_community_members_on_user_id ON public.community_members USIN CREATE INDEX index_diary_entry_subscriptions_on_diary_entry_id ON public.diary_entry_subscriptions USING btree (diary_entry_id); +-- +-- Name: index_event_organizers_on_event_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_event_organizers_on_event_id ON public.event_organizers USING btree (event_id); + + +-- +-- Name: index_event_organizers_on_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_event_organizers_on_user_id ON public.event_organizers USING btree (user_id); + + -- -- Name: index_events_on_community_id; Type: INDEX; Schema: public; Owner: - -- @@ -3346,6 +3407,14 @@ ALTER TABLE ONLY public.active_storage_variant_records ADD CONSTRAINT fk_rails_993965df05 FOREIGN KEY (blob_id) REFERENCES public.active_storage_blobs(id); +-- +-- Name: event_organizers fk_rails_b1c2c61554; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.event_organizers + ADD CONSTRAINT fk_rails_b1c2c61554 FOREIGN KEY (user_id) REFERENCES public.users(id); + + -- -- Name: oauth_access_grants fk_rails_b4b53e07b8; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -3354,6 +3423,14 @@ ALTER TABLE ONLY public.oauth_access_grants ADD CONSTRAINT fk_rails_b4b53e07b8 FOREIGN KEY (application_id) REFERENCES public.oauth_applications(id) NOT VALID; +-- +-- Name: event_organizers fk_rails_c1e082c91e; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.event_organizers + ADD CONSTRAINT fk_rails_c1e082c91e FOREIGN KEY (event_id) REFERENCES public.events(id); + + -- -- Name: active_storage_attachments fk_rails_c3b3935057; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -3764,6 +3841,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20220821143545'), ('20220925043305'), ('20221008144036'), +('20221008224134'), ('21'), ('22'), ('23'), diff --git a/test/controllers/events_controller_test.rb b/test/controllers/events_controller_test.rb index e4ac9f5ded8..a44f8d32d22 100644 --- a/test/controllers/events_controller_test.rb +++ b/test/controllers/events_controller_test.rb @@ -207,6 +207,68 @@ def controller_mock.render(_partial) # assert_equal I18n.t("events.create.failure"), flash[:alert] end + def test_update_as_organizer + # arrange + cm = create(:community_member, :organizer) + session_for(cm.user) + e1 = create(:event, :community => cm.community) # original object + e2 = build(:event, :community => cm.community) # new data + # act + put event_url(e1), :params => { :event => e2.as_json }, :xhr => true + # assert + assert_redirected_to event_path(e1) + # TODO: Is it better to use t() to translate? + assert_equal "The event was successfully updated.", flash[:notice] + e1.reload + # Assign the id of e1 to e2, so we can do an equality test easily. + e2.id = e1.id + assert_equal(e2, e1) + end + + def test_update_as_non_organizer + # arrange + cm = create(:community_member) + session_for(cm.user) + e1 = create(:event, :community => cm.community) # original object + e2 = build(:event, :community => cm.community) # new data + # act + put event_url(e1), :params => { :event => e2.as_json }, :xhr => true + # assert + assert_redirected_to :controller => :errors, :action => :forbidden + end + + def test_update_put_failure + # arrange + cm = create(:community_member, :organizer) + session_for(cm.user) + ev = create(:event, :community => cm.community) + def ev.update(_params) + false + end + + controller_mock = EventsController.new + def controller_mock.set_event + @event = Event.new + end + + def controller_mock.render(_partial) + # Can't do assert_equal here. + # assert_equal :edit, partial + end + + # act + EventsController.stub :new, controller_mock do + Event.stub :new, ev do + assert_difference "Event.count", 0 do + put event_url(ev), :params => { :event => ev.as_json }, :xhr => true + end + end + end + + # assert + assert_equal I18n.t("events.update.failure"), flash[:alert] + end + def test_in_past_warns # arrange cm = create(:community_member, :organizer) diff --git a/test/factories/event_organizers.rb b/test/factories/event_organizers.rb new file mode 100644 index 00000000000..175c60eaf5b --- /dev/null +++ b/test/factories/event_organizers.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :event_organizer do + event + user + end +end diff --git a/test/helpers/events_helper_test.rb b/test/helpers/events_helper_test.rb new file mode 100644 index 00000000000..9a89dbcadd8 --- /dev/null +++ b/test/helpers/events_helper_test.rb @@ -0,0 +1,15 @@ +require "test_helper" + +class EventsHelperTest < ActionView::TestCase + def test_event_location_url + event = create(:event) + location = event_location(event) + assert_match %r{^#{event.location}$}, location + end + + def test_event_location_no_url + event = create(:event, :location_url => nil) + location = event_location(event) + assert_match(/^#{event.location}$/, location) + end +end diff --git a/test/models/event_organizer_test.rb b/test/models/event_organizer_test.rb new file mode 100644 index 00000000000..0cd6db89b79 --- /dev/null +++ b/test/models/event_organizer_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class EventOrganizerTest < ActiveSupport::TestCase + def test_eventorganizer_validations + validate({}, true) + end +end