From 1cad60b77b31a6232de06f774a49a4c4a75ea111 Mon Sep 17 00:00:00 2001 From: mayra lucia navarro Date: Tue, 15 Aug 2023 02:23:43 -0500 Subject: [PATCH 1/4] Create Factory for events --- .../admin/events_controller_spec.rb | 39 +++++++------ .../controllers/api/events_controller_spec.rb | 55 ++++++------------- spec/system/managing_events_spec.rb | 2 +- 3 files changed, 38 insertions(+), 58 deletions(-) diff --git a/spec/controllers/admin/events_controller_spec.rb b/spec/controllers/admin/events_controller_spec.rb index 177bbea2..ec847a7b 100644 --- a/spec/controllers/admin/events_controller_spec.rb +++ b/spec/controllers/admin/events_controller_spec.rb @@ -3,7 +3,7 @@ require './spec/rails_helper' RSpec.describe Admin::EventsController, type: :controller do - describe '#index' do + describe 'GET #index' do context 'when user is not authenticated' do it 'returns 302' do get :index @@ -33,7 +33,7 @@ end end - describe '#edit' do + describe 'GET #edit' do context 'when user is not authenticated' do let(:event) { FactoryBot.create(:event) } @@ -50,7 +50,7 @@ let(:event) { FactoryBot.create(:event) } it 'returns 401' do - get :edit, params: {id: event.id} + get :edit, params: { id: event.id } expect(response).to have_http_status(401) end end @@ -61,18 +61,18 @@ let(:event) { FactoryBot.create(:event) } it 'returns 200' do - get :edit, params: {id: event.id} + get :edit, params: { id: event.id } expect(response).to have_http_status(200) end end end - describe '#update' do + describe 'PUT #update' do context 'when user is not authenticated' do let(:event) { FactoryBot.create(:event) } it 'returns 302' do - post :update, params: { id: event.id } + put :update, params: { id: event.id } expect(response).to have_http_status(302) expect(response).to redirect_to(new_user_session_path) end @@ -109,7 +109,7 @@ end end - describe '#create' do + describe 'POST #create' do context 'when user is not authenticated' do it 'returns 302' do post :create @@ -134,7 +134,14 @@ it 'returns 302' do post :create, - params: { event: { title: 'My event', description: 'Great event', date: 1.month.from_now, type: 'Meetup' } } + params: { + event: { + title: 'My event', + description: 'Great event', + date: 1.month.from_now, + type: 'Meetup', + }, + } expect(response).to redirect_to(admin_events_path) end @@ -146,18 +153,16 @@ let(:user) { create(:user, :admin) } let(:event) { create(:event) } - before(:each) do - sign_in user - end + before(:each) { sign_in user } it 'redirects to index page when event exists' do - delete :destroy, params: {id: event.id} + delete :destroy, params: { id: event.id } expect(response).to redirect_to(admin_events_path) expect(Event.exists?(event.id)).to be(false) end it 'returns 404 when the event does not exist' do - delete :destroy, params: {id: 'fakefake'} + delete :destroy, params: { id: 'fakefake' } expect(response).to have_http_status(404) end end @@ -166,12 +171,10 @@ let(:user) { create(:user) } let(:event) { create(:event) } - before(:each) do - sign_in user - end + before(:each) { sign_in user } it 'returns 401' do - delete :destroy, params: {id: event.id} + delete :destroy, params: { id: event.id } expect(response).to have_http_status(401) expect(Event.exists?(event.id)).to be(true) end @@ -181,7 +184,7 @@ let(:event) { create(:event) } it 'redirects to login' do - delete :destroy, params: {id: event.id} + delete :destroy, params: { id: event.id } expect(response).to redirect_to(new_user_session_path) expect(Event.exists?(event.id)).to be(true) end diff --git a/spec/controllers/api/events_controller_spec.rb b/spec/controllers/api/events_controller_spec.rb index fcf0ad2a..21fe40b9 100644 --- a/spec/controllers/api/events_controller_spec.rb +++ b/spec/controllers/api/events_controller_spec.rb @@ -13,30 +13,19 @@ describe 'GET #past' do before do - july_meetup = - Meetup.create( - title: 'July Meetup', - location: 'virtual', - date: DateTime.new(2021, 7, 31, 16).utc, - ) - Meetup.create( - title: 'August Meetup', - location: 'virtual', - date: DateTime.new(2021, 8, 31, 16).utc, - ) + july_meetup = create(:event, title: 'July Meetup', date: DateTime.new(2021, 7, 31, 16).utc) + create(:event, title: 'August Meetup', date: DateTime.new(2021, 8, 31, 16).utc) Panel.create(title: 'Future Panel', location: 'Denver, Colorado', date: DateTime.now + 1.week) speaker = - Speaker.create( + create( + :speaker, name: 'Speaker Name', - tagline: 'Software Developer', - bio: 'Lorem Ipsum', - image_url: 'https://picsum.photos/200', links: { twitter: 'http://example.com/twitter-link', mastodon: 'http://example.com/mastodon-link', - personal_website: 'http://example.com/personal-website-link', - } + website: 'http://example.com/personal-website-link', + }, ) EventSpeaker.create( event: july_meetup, @@ -71,11 +60,13 @@ july_meetup = body['data']['2021']['July'].first expect(july_meetup['speakers'].first['name']).to eq('Speaker Name') - expect(july_meetup['speakers'].first['links']).to eq({ - 'twitter' => 'http://example.com/twitter-link', - 'mastodon' => 'http://example.com/mastodon-link', - 'personal_website' => 'http://example.com/personal-website-link', - }) + expect(july_meetup['speakers'].first['links']).to eq( + { + 'twitter' => 'http://example.com/twitter-link', + 'mastodon' => 'http://example.com/mastodon-link', + 'website' => 'http://example.com/personal-website-link', + }, + ) end it 'includes talk titles' do @@ -108,25 +99,11 @@ describe 'GET #upcoming' do before do august_meetup = - Meetup.create( - title: 'August Meetup', - location: 'virtual', - date: DateTime.new(2022, 8, 31, 16).utc, - ) - Meetup.create( - title: 'September Meetup', - location: 'virtual', - date: DateTime.new(2022, 9, 25, 16).utc, - ) + create(:event, title: 'August Meetup', date: DateTime.new(2022, 8, 31, 16).utc) + create(:event, title: 'September Meetup', date: DateTime.new(2022, 9, 25, 16).utc) Panel.create(title: 'Future Panel', location: 'Denver, Colorado', date: DateTime.now - 1.week) - speaker = - Speaker.create( - name: 'Speaker Name', - tagline: 'Software Developer', - bio: 'Lorem Ipsum', - image_url: 'https://picsum.photos/200', - ) + speaker = create(:speaker, name: 'Speaker Name') EventSpeaker.create( event: august_meetup, speaker: speaker, diff --git a/spec/system/managing_events_spec.rb b/spec/system/managing_events_spec.rb index 91cdfecc..841439d1 100644 --- a/spec/system/managing_events_spec.rb +++ b/spec/system/managing_events_spec.rb @@ -36,7 +36,7 @@ describe 'with invalid data' do it 'shows errors' do click_on 'Save' - # byebug + expect(page).to have_current_path(admin_events_path) expect(page).to have_text("Title can't be blank") end From 216fafb12c66221d97df4e294935685c3ede02de Mon Sep 17 00:00:00 2001 From: mayra lucia navarro Date: Tue, 15 Aug 2023 02:25:32 -0500 Subject: [PATCH 2/4] Generate Speaker module for admin - Add HTTParty to validate links - Add views, controllers, policies, model for speaker --- Gemfile | 3 + Gemfile.lock | 11 +- app/controllers/admin/speakers_controller.rb | 95 +++++++++++ app/helpers/admin/speakers_helper.rb | 6 + app/models/speaker.rb | 38 +++++ app/policies/speaker_policy.rb | 26 +++ app/views/admin/events/_form.html.erb | 2 +- app/views/admin/events/edit.html.erb | 2 +- app/views/admin/events/index.html.erb | 63 +++---- app/views/admin/events/new.html.erb | 2 +- app/views/admin/speakers/_form.html.erb | 46 +++++ app/views/admin/speakers/edit.html.erb | 6 + app/views/admin/speakers/index.html.erb | 51 ++++++ .../speakers/index/_speaker_links.html.erb | 25 +++ app/views/admin/speakers/new.html.erb | 7 + app/views/admin/speakers/show.html.erb | 10 ++ app/views/layouts/admin/_flashes.html.erb | 2 +- app/views/layouts/admin/_form_errors.html.erb | 8 +- app/views/layouts/admin/_header.html.erb | 11 +- config/routes.rb | 2 + .../admin/speakers_controller_spec.rb | 160 ++++++++++++++++++ spec/factories/speakers.rb | 24 +++ spec/models/speaker_spec.rb | 51 +++++- spec/rails_helper.rb | 6 + spec/system/managing_speakers_spec.rb | 45 +++++ 25 files changed, 657 insertions(+), 45 deletions(-) create mode 100644 app/controllers/admin/speakers_controller.rb create mode 100644 app/helpers/admin/speakers_helper.rb create mode 100644 app/policies/speaker_policy.rb create mode 100644 app/views/admin/speakers/_form.html.erb create mode 100644 app/views/admin/speakers/edit.html.erb create mode 100644 app/views/admin/speakers/index.html.erb create mode 100644 app/views/admin/speakers/index/_speaker_links.html.erb create mode 100644 app/views/admin/speakers/new.html.erb create mode 100644 app/views/admin/speakers/show.html.erb create mode 100644 spec/controllers/admin/speakers_controller_spec.rb create mode 100644 spec/factories/speakers.rb create mode 100644 spec/system/managing_speakers_spec.rb diff --git a/Gemfile b/Gemfile index 4ea6e88f..46ef2662 100644 --- a/Gemfile +++ b/Gemfile @@ -34,6 +34,9 @@ gem 'google_drive', git: 'https://github.com/wnbrb/google-drive-ruby.git', branc # cuts off a string of HTML gem 'truncate_html', '~> 0.9.3' +# quickly call web links +gem 'httpparty', '~> 0.2' + group :development, :test do gem 'byebug', platforms: %i[mri mingw x64_mingw] gem 'faker', '~> 2.18.0' diff --git a/Gemfile.lock b/Gemfile.lock index 27c873d3..72ae619e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -120,7 +120,7 @@ GEM railties (>= 5.0.0) faker (2.18.0) i18n (>= 1.6, < 2) - faraday (2.7.10) + faraday (2.7.4) faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-mashify (0.1.1) @@ -154,8 +154,13 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) hashie (5.0.0) + httparty (0.21.0) + mini_mime (>= 1.0.0) + multi_xml (>= 0.5.2) httpclient (2.8.3) - i18n (1.14.1) + httpparty (0.2.0) + httparty (> 0) + i18n (1.12.0) concurrent-ruby (~> 1.0) io-console (0.6.0) irb (1.7.4) @@ -188,6 +193,7 @@ GEM minitest (5.19.0) msgpack (1.7.2) multi_json (1.15.0) + multi_xml (0.6.0) multipart-post (2.3.0) net-imap (0.3.7) date @@ -396,6 +402,7 @@ DEPENDENCIES factory_bot_rails (~> 6.2.0) faker (~> 2.18.0) google_drive! + httpparty (~> 0.2) jbuilder (~> 2.7) jsbundling-rails (~> 1.1) jwt (>= 2.6.0) diff --git a/app/controllers/admin/speakers_controller.rb b/app/controllers/admin/speakers_controller.rb new file mode 100644 index 00000000..abcb7e11 --- /dev/null +++ b/app/controllers/admin/speakers_controller.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true +module Admin + class SpeakersController < AdminController + before_action :authorize_event + before_action :set_speaker, only: %w[show edit update destroy] + + # GET /admin/speakers + def index + @speakers = Speaker.ordered_by_name + end + + # GET /admin/speakers/1 + def show; end + + # GET /admin/speakers/new + def new + @speaker = Speaker.new + end + + # GET /admin/speakers/1/edit + def edit + render_not_found unless @speaker + end + + # POST /admin/speakers + def create + @speaker = Speaker.new(speaker_params) + + respond_to do |format| + if @speaker.save + format.html do + redirect_to edit_admin_speaker_url(@speaker), + notice: 'Speaker was successfully created.' + end + else + format.html { render :new, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /admin/speakers/1 + def update + respond_to do |format| + if @speaker.update(speaker_params) + format.html do + redirect_to edit_admin_speaker_url(@speaker), + notice: 'Speaker was successfully updated.' + end + format.json { render :show, status: :ok, location: @speaker } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @speaker.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /admin/speakers/1 + # def destroy + # @speaker.destroy + + # respond_to do |format| + # format.html do + # redirect_to admin_speakers_url, notice: 'Speaker was successfully destroyed.' + # end + # end + # end + + private + + def authorize_event + authorize Event + end + + # Use callbacks to share common setup or constraints between actions. + def set_speaker + @speaker = Speaker.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def speaker_params + params.require(:speaker).permit( + :name, + :bio, + :tagline, + :image_url, + :github, + :linkedin, + :mastodon, + :website, + :twitter, + :other, + ) + end + end +end diff --git a/app/helpers/admin/speakers_helper.rb b/app/helpers/admin/speakers_helper.rb new file mode 100644 index 00000000..d430c448 --- /dev/null +++ b/app/helpers/admin/speakers_helper.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +module Admin + module SpeakersHelper + #Add helpers + end +end diff --git a/app/models/speaker.rb b/app/models/speaker.rb index 71aed937..251be285 100644 --- a/app/models/speaker.rb +++ b/app/models/speaker.rb @@ -1,8 +1,46 @@ # frozen_string_literal: true class Speaker < ApplicationRecord + include HTTParty + SOCIAL_MEDIA_LINKS = %w[github linkedin mastodon twitter website other].freeze + has_many :event_speakers, dependent: :destroy has_many :events, through: :event_speakers validates :name, :bio, :image_url, presence: true + validate :links, :validate_social_media_brand + validate :url_exists?, if: ->(s) { s.links.compact_blank.present? } + + store :links, accessors: SOCIAL_MEDIA_LINKS, coder: JSON + + before_validation :format_links + + scope :ordered_by_name, -> { order(:name) } + + def validate_social_media_brand + return if links.blank? || links.keys.all? { |key| SOCIAL_MEDIA_LINKS.include?(key) } + + errors.add(:links, 'This social media is not allowed') + end + + def format_links + return if links.blank? + + links.transform_keys!(&:downcase) + end + + def url_exists? + links.each { |_, url| errors.add(:links, 'This url is not valid') unless url_valid?(url) } + end + + def empty_links? + links.compact_blank.empty? + end + + private + + def url_valid?(url) + response = HTTParty.get(url) + response.code == 200 + end end diff --git a/app/policies/speaker_policy.rb b/app/policies/speaker_policy.rb new file mode 100644 index 00000000..80fc3dc2 --- /dev/null +++ b/app/policies/speaker_policy.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true +class SpeakerPolicy < ApplicationPolicy + def index? + user.admin? + end + + def new? + user.admin? + end + + def create? + user.admin? + end + + def edit? + user.admin? + end + + def show? + edit? + end + + def update? + user.admin? + end +end diff --git a/app/views/admin/events/_form.html.erb b/app/views/admin/events/_form.html.erb index 80cef8aa..74105c8e 100644 --- a/app/views/admin/events/_form.html.erb +++ b/app/views/admin/events/_form.html.erb @@ -1,4 +1,4 @@ - <%= form_for [:admin, @event.becomes(Event)] do |f| %> + <%= form_for [:admin, event.becomes(Event)] do |f| %> <%= render 'layouts/admin/form_errors', object: f.object %>
<%= f.text_field :title, class: "form-control" %> diff --git a/app/views/admin/events/edit.html.erb b/app/views/admin/events/edit.html.erb index 4208a30c..3b8fdfd0 100644 --- a/app/views/admin/events/edit.html.erb +++ b/app/views/admin/events/edit.html.erb @@ -1,6 +1,6 @@

Edit event

- <%=render 'form'%> + <%=render 'form', event: @event %>
diff --git a/app/views/admin/events/index.html.erb b/app/views/admin/events/index.html.erb index 98930c17..c48313f6 100644 --- a/app/views/admin/events/index.html.erb +++ b/app/views/admin/events/index.html.erb @@ -1,7 +1,7 @@

Event List

- <%= link_to new_admin_event_path, class: "btn btn-primary btn-lg mb-3" do %> + <%= link_to new_admin_event_path, class: "btn btn-primary btn-md mb-4" do %> Create new Event <% end %> @@ -9,34 +9,37 @@
- - - - - - - - - - - - - - <% @events.each do |event| %> - - - - - - - - - - <% end %> - -
TitleTypeSpeakersDateLocationDescriptionActions
<%= event.title %><%= event.type %><%= event.speakers.map(&:name).join(", ") %><%= event.date.strftime('%B %d, %Y %I:%M%p %:z') %><%= event.location %><%= truncate_html(event.description) %> - <%= link_to 'Edit', edit_admin_event_path(event) %> - <%= link_to 'Delete', admin_event_path(event), method: :delete %> -
+
+ + + + + + + + + + + + + + <% @events.each do |event| %> + + + + + + + + + + + <% end %> + +
TitleTypeSpeakersDateLocationDescriptionActions
<%= event.title %><%= event.type %><%= event.speakers.map(&:name).join(", ") %><%= event.date.strftime('%B %d, %Y %I:%M%p %:z') %><%= event.location %><%= truncate_html(event.description) %> + <%= link_to 'Edit', edit_admin_event_path(event) %> + <%= button_to "Delete", admin_event_url(event), form_class: "", class: "link-primary border-0 bg-transparent text-decoration-underline", method: :delete %> +
+
diff --git a/app/views/admin/events/new.html.erb b/app/views/admin/events/new.html.erb index a6f5ed2d..4f4c1d0a 100644 --- a/app/views/admin/events/new.html.erb +++ b/app/views/admin/events/new.html.erb @@ -1,6 +1,6 @@

New Event

- <%= render 'form'%> + <%= render 'form', event: @event %>
diff --git a/app/views/admin/speakers/_form.html.erb b/app/views/admin/speakers/_form.html.erb new file mode 100644 index 00000000..5592d23d --- /dev/null +++ b/app/views/admin/speakers/_form.html.erb @@ -0,0 +1,46 @@ +<%= form_for [:admin, speaker] do |f| %> + <%= render "layouts/admin/form_errors", object: f.object %> +
+ <%= f.text_field :name, class: "form-control" %> + <%= f.label :name %> +
+
+ <%= f.text_area :bio, rows: 40, class: "form-control", style: "height: 200px" %> + <%= f.label :bio %> +
+
+ <%= f.text_field :tagline, class: "form-control" %> + <%= f.label :tagline %> +
+
+ <%= f.url_field :image_url, class: "form-control" %> + <%= f.label :image_url, "Image link" %> +
+ +
+
+ + <% accordion_collapse_class = speaker.links.compact_blank.present? ? "accordion-collapse collapse show" : "accordion-collapse collapse" %> +
+
+ <% Speaker::SOCIAL_MEDIA_LINKS.each do |link| %> +
+
+ <%= f.label link.to_sym, class: "input-group-text" %> + <%= f.text_field link.to_sym, class: "form-control", placeholder: "https://socialmedia.url", aria: { label: link.capitalize, describedby: link } %> +
+
+ <% end %> +
+
+
+
+ +
+ <%= f.submit "Save", class: "btn btn-primary btn-lg" %> + <%=link_to "Cancel", admin_speakers_path, class: "btn btn-outline-primary btn-lg" %> +
+<% end %> diff --git a/app/views/admin/speakers/edit.html.erb b/app/views/admin/speakers/edit.html.erb new file mode 100644 index 00000000..9a1cce65 --- /dev/null +++ b/app/views/admin/speakers/edit.html.erb @@ -0,0 +1,6 @@ +
+
+

Edit speaker

+ <%=render 'form', speaker: @speaker %> +
+
diff --git a/app/views/admin/speakers/index.html.erb b/app/views/admin/speakers/index.html.erb new file mode 100644 index 00000000..26fb4c83 --- /dev/null +++ b/app/views/admin/speakers/index.html.erb @@ -0,0 +1,51 @@ +

Speaker List

+
+
+ <%= link_to new_admin_speaker_path, class: "btn btn-primary btn-md mb-4" do %> + Create new Speaker + + <% end %> +
+
+ +
+
+
+ + + + + + + + + + + + + + + <% @speakers.each do |speaker| %> + + + + + + + + + + <% end %> + +
IdNameBioTaglineImageLinksActions
<%= speaker.id %><%= speaker.name %><%= truncate_html(speaker.bio, length: 70) %><%= speaker.tagline %> +
+ <%=image_tag speaker.image_url, class:"rounded-circle object-fit-cover", height: 53, width: 53, alt:"Profile photo for #{speaker.name}" %> +
+
+ <%= render "admin/speakers/index/speaker_links", speaker: speaker%> + + <%= link_to "Edit", edit_admin_speaker_path(speaker) %> +
+
+
+
diff --git a/app/views/admin/speakers/index/_speaker_links.html.erb b/app/views/admin/speakers/index/_speaker_links.html.erb new file mode 100644 index 00000000..568e1aac --- /dev/null +++ b/app/views/admin/speakers/index/_speaker_links.html.erb @@ -0,0 +1,25 @@ +<% if speaker.empty_links? %> + - +<% else %> + <% if speaker.github.present? %> + + <% end %> + <% if speaker.mastodon.present? %> + + <% end %> + <% if speaker.linkedin.present? %> + + <% end %> + <% if speaker.twitter.present? %> + + <% end %> + <% if speaker.website.present? %> + + <% end %> + <% if speaker.other.present? %> + + <% end %> +<% end %> + + + diff --git a/app/views/admin/speakers/new.html.erb b/app/views/admin/speakers/new.html.erb new file mode 100644 index 00000000..24c66f79 --- /dev/null +++ b/app/views/admin/speakers/new.html.erb @@ -0,0 +1,7 @@ +
+
+

New Speaker

+ + <%= render "form", speaker: @speaker %> +
+
diff --git a/app/views/admin/speakers/show.html.erb b/app/views/admin/speakers/show.html.erb new file mode 100644 index 00000000..71136389 --- /dev/null +++ b/app/views/admin/speakers/show.html.erb @@ -0,0 +1,10 @@ +

<%= notice %>

+ +<%= render @speaker %> + +
+ <%= link_to "Edit this speaker", edit_admin_speaker_path(@speaker) %> | + <%= link_to "Back to speakers", admin_speakers_path %> + + <%= button_to "Destroy this speaker", @speaker, method: :delete %> +
diff --git a/app/views/layouts/admin/_flashes.html.erb b/app/views/layouts/admin/_flashes.html.erb index 39c35b92..d6958919 100644 --- a/app/views/layouts/admin/_flashes.html.erb +++ b/app/views/layouts/admin/_flashes.html.erb @@ -1,5 +1,5 @@ <% if flash.any? %> -