diff --git a/app/controllers/admin/casting_call_interviews_controller.rb b/app/controllers/admin/casting_call_interviews_controller.rb new file mode 100644 index 0000000..c9f0a92 --- /dev/null +++ b/app/controllers/admin/casting_call_interviews_controller.rb @@ -0,0 +1,64 @@ +class Admin::CastingCallInterviewsController < Admin::ApplicationController + before_action :set_casting_call_interview, only: [:edit, :update, :show, :complete] + before_action :build_casting_call_interview, only: [:new, :create] + + def index + @casting_call_interviews = casting_call_interviews.order_by_recent.paginate(page: params[:page]) + end + + def new + @accounts = accounts + end + + def create + @casting_call_interview.attributes = casting_call_interview_params + + if @casting_call_interview.save + redirect_to [:admin, :casting_call_interviews], notice: t(".notice") + else + render :new + end + end + + def edit + @accounts = accounts + end + + def update + if @casting_call_interview.update(casting_call_interview_params) + redirect_to [:admin, :casting_call_interviews], notice: t(".notice") + else + render :edit + end + end + + def complete + if @casting_call_interview.update(interviewed_at: Time.zone.now) + redirect_to [:admin, :casting_call_interviews], notice: t(".notice") + else + redirect_to [:admin, :casting_call_interviews], notice: t(".alert") + end + end + + private + + def casting_call_interview_params + params.require(:casting_call_interview).permit(:casting_call_id, :performer_name, :interview_date, :zoom_meeting_url) + end + + def casting_call_interviews + policy_scope CastingCallInterview + end + + def set_casting_call_interview + @casting_call_interview = authorize policy_scope(CastingCallInterview).find(params[:id]) + end + + def accounts + policy_scope Account + end + + def build_casting_call_interview + @casting_call_interview = authorize policy_scope(CastingCallInterview).build + end +end \ No newline at end of file diff --git a/app/controllers/casting_call_interviews_controller.rb b/app/controllers/casting_call_interviews_controller.rb new file mode 100644 index 0000000..0bdb9f2 --- /dev/null +++ b/app/controllers/casting_call_interviews_controller.rb @@ -0,0 +1,28 @@ +class CastingCallInterviewsController < ApplicationController + before_action :set_project + before_action :set_casting_call_interview, only: [:show] + + include ProjectLayout + + def index + @casting_call_interviews = casting_call_interviews.completed.order_by_recent.paginate(page: params[:page]) + end + + def show + @files = @casting_call_interview.files.paginate(page: params[:page]) + end + + private + + def set_project + @project = policy_scope(Project).find(params[:project_id]) + end + + def set_casting_call_interview + @casting_call_interview = authorize casting_call_interviews.find(params[:id]) + end + + def casting_call_interviews + authorize policy_scope(CastingCallInterview) + end +end diff --git a/app/controllers/casting_calls_controller.rb b/app/controllers/casting_calls_controller.rb new file mode 100644 index 0000000..c06d729 --- /dev/null +++ b/app/controllers/casting_calls_controller.rb @@ -0,0 +1,76 @@ +class CastingCallsController < ApplicationController + layout "project" + + before_action :set_project + before_action :build_casting_call, only: [:new, :create] + before_action :set_casting_call, only: [:show, :edit, :update, :cancel] + + def index + @casting_calls = casting_calls.order_by_recent.paginate(page: params[:page]) + end + + def new + end + + def create + @casting_call.attributes = casting_call_params_with_email + + if @casting_call.save + log_create_analytics + castme_url = url_for([@project, @casting_call]) + SubmitHubspotFormJob.perform_later(email: @casting_call.user_email, castme_url: castme_url, form_guid: ENV["HUBSPOT_CASTING_CALL_REQUEST_FORM_GUID"]) + else + render :new + end + end + + def show + render layout: 'application' + end + + def edit + end + + def update + if @casting_call.update(casting_call_params) + redirect_to [@project, :casting_calls], notice: t(".notice") + else + render :edit + end + end + + def cancel + @casting_call.update(cancelled_at: Time.zone.now) + redirect_to [@project, :casting_calls], notice: t(".notice") + end + + private + + def casting_call_params + params.require(:casting_call).permit(:title, :description, :project_description, :interview_instructions, :interview_requirements, :questions) + end + + def casting_call_params_with_email + casting_call_params.merge(user_email: Current.user.email) + end + + def set_project + @project = policy_scope(Project).find(params[:project_id]) + end + + def set_casting_call + @casting_call = authorize casting_calls.find(params[:id]) + end + + def casting_calls + authorize policy_scope(@project.casting_calls) + end + + def build_casting_call + @casting_call = authorize @project.casting_calls.build + end + + def log_create_analytics + TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_casting_call, user_agent: request.user_agent, user_ip: request.remote_ip) + end +end diff --git a/app/controllers/interview_downloads_controller.rb b/app/controllers/interview_downloads_controller.rb new file mode 100644 index 0000000..46a9b57 --- /dev/null +++ b/app/controllers/interview_downloads_controller.rb @@ -0,0 +1,30 @@ +class InterviewDownloadsController < ApplicationController + include ProjectContext + + before_action :set_project, only: [:create] + before_action :set_casting_call_interview, only: :create + + include ProjectLayout + + def create + download = @project.downloads.create!(name: @casting_call_interview.zip_file_name, release_type: "CastingCallInterview") + + other_downloads_in_progress = @project.downloads.unfinished_desc_order.offset(1) + + if other_downloads_in_progress.any? + in_progress_downloads_details = render_to_string "_other_pending_downloads", locals: { downloads: other_downloads_in_progress, release_type: "CastingCallInterview" }, :layout => false + ProjectsChannel.broadcast_download_generation_update(download, in_progress_downloads_details) + else + ProjectsChannel.broadcast_download_generation_update(download, I18n.t("interview_downloads.download.pending", release_type: "Casting Call Interview")) + end + + GenerateInterviewFilesZipJob.perform_later(@project, download, @casting_call_interview) + end + + private + + def set_casting_call_interview + authorize(Download) + @casting_call_interview = policy_scope(@project.casting_call_interviews).find(params[:casting_call_interview_id]) + end +end diff --git a/app/controllers/public/casting_call_interviews_controller.rb b/app/controllers/public/casting_call_interviews_controller.rb new file mode 100644 index 0000000..8884ea9 --- /dev/null +++ b/app/controllers/public/casting_call_interviews_controller.rb @@ -0,0 +1,25 @@ +class Public::CastingCallInterviewsController < Public::BaseController + skip_after_action :verify_authorized + before_action :set_casting_call_interview, only: [:show, :update] + + def show + end + + def update + if @casting_call_interview.update(casting_call_interview_params) + redirect_to casting_call_interview_url(token: @casting_call_interview.token), notice: t(".notice") + else + render :show + end + end + + private + + def set_casting_call_interview + @casting_call_interview = CastingCallInterview.find_by_token(params[:token]) + end + + def casting_call_interview_params + params.require(:casting_call_interview).permit(files: []) + end +end diff --git a/app/controllers/public/casting_calls_controller.rb b/app/controllers/public/casting_calls_controller.rb new file mode 100644 index 0000000..66bf280 --- /dev/null +++ b/app/controllers/public/casting_calls_controller.rb @@ -0,0 +1,14 @@ +class Public::CastingCallsController < Public::BaseController + skip_after_action :verify_authorized + before_action :set_casting_call, only: [:show] + + def show + render layout: 'application' + end + + private + + def set_casting_call + @casting_call = CastingCall.find_by_token(params[:token]) + end +end diff --git a/app/jobs/generate_interview_files_zip_job.rb b/app/jobs/generate_interview_files_zip_job.rb new file mode 100644 index 0000000..a580427 --- /dev/null +++ b/app/jobs/generate_interview_files_zip_job.rb @@ -0,0 +1,43 @@ +class GenerateInterviewFilesZipJob < ApplicationJob + queue_as :default + include Rails.application.routes.url_helpers + include ActionView::Helpers::UrlHelper + + before_perform do |job| + @project = job.arguments.first + @download = job.arguments.second + @casting_call_interview = job.arguments.third + @download.update!(status: :pending) + end + + def perform(project, download, casting_call_interview) + ::InterviewFilesCollectionService.new(casting_call_interview.files, @download.name).build do |dir, files| + zipfile_name = "#{dir}/#{@download.name}.zip" + Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile| + files.each do |attachment| + zipfile.add(attachment, File.join("#{dir}/", attachment)) + end + end + + @download.file.attach(io: File.open(zipfile_name), filename: @download.name) + end + rescue StandardError => e + Raven.extra_context( + message: "Failed to generate download for project (##{project.id})", + release_type: "CastingCallInterview" + ) + + @download.failure! + ProjectsChannel.broadcast_download_generation_update(@download, I18n.t("interview_downloads.download.failure")) + end + + after_perform do |job| + if @download.pending? && @download.file.attached? + @download.success! + + downloads_folder_link = link_to("Files > Downloads", project_downloads_path(I18n.locale, @project)) + download_button = link_to("Download", rails_blob_path(@download.file, disposition: "attachment", only_path: true), class: "btn btn-success", target: :_blank) + ProjectsChannel.broadcast_download_generation_update(@download, I18n.t("interview_downloads.download.success", downloads_folder_link: downloads_folder_link, download_button: download_button, release_type: "Casting Call Interview")) + end + end +end diff --git a/app/models/casting_call.rb b/app/models/casting_call.rb new file mode 100644 index 0000000..1ae7d07 --- /dev/null +++ b/app/models/casting_call.rb @@ -0,0 +1,18 @@ +class CastingCall < ApplicationRecord + belongs_to :project + has_many :casting_call_interviews, dependent: :destroy + + has_secure_token + + def status + if cancelled? + "Cancelled" + else + "Active" + end + end + + def cancelled? + self.cancelled_at.present? + end +end diff --git a/app/models/casting_call_interview.rb b/app/models/casting_call_interview.rb new file mode 100644 index 0000000..3427ed3 --- /dev/null +++ b/app/models/casting_call_interview.rb @@ -0,0 +1,22 @@ +class CastingCallInterview < ApplicationRecord + belongs_to :casting_call + has_many_attached :files + + has_secure_token + + validates :performer_name, presence: true + + scope :completed, -> { where.not(interviewed_at: nil) } + + def join_zoom_meeting_url + uri = URI.parse(self.zoom_meeting_url) + zoom_meeting_id = uri.path.gsub("/j/", "") + zoom_meeting_pwd = uri.query.gsub("pwd=", "") + + "zoommtg://zoom.us/join?confno=#{zoom_meeting_id}&pwd=#{zoom_meeting_pwd}" + end + + def zip_file_name + "#{self.casting_call.title.parameterize}_#{self.performer_name.parameterize}_#{Time.now.strftime('%Y-%m-%d_%H-%M-%S')}" + end +end diff --git a/app/policies/casting_call_interview_policy.rb b/app/policies/casting_call_interview_policy.rb new file mode 100644 index 0000000..ac7a232 --- /dev/null +++ b/app/policies/casting_call_interview_policy.rb @@ -0,0 +1,29 @@ +class CastingCallInterviewPolicy < ApplicationPolicy + def index? + true + end + + def show? + true + end + + def create? + true + end + + def destroy? + true + end + + def update? + true + end + + def complete? + true + end + + def download? + true + end +end diff --git a/app/policies/casting_call_policy.rb b/app/policies/casting_call_policy.rb new file mode 100644 index 0000000..a2b4596 --- /dev/null +++ b/app/policies/casting_call_policy.rb @@ -0,0 +1,25 @@ +class CastingCallPolicy < ApplicationPolicy + def index? + true + end + + def show? + true + end + + def create? + true + end + + def destroy? + true + end + + def update? + true + end + + def cancel? + true + end +end \ No newline at end of file diff --git a/app/services/interview_files_collection_service.rb b/app/services/interview_files_collection_service.rb new file mode 100644 index 0000000..c99a6f8 --- /dev/null +++ b/app/services/interview_files_collection_service.rb @@ -0,0 +1,24 @@ +class InterviewFilesCollectionService + def initialize(files, folder_name) + @files = files + @folder_name = folder_name + end + + def build + Dir.mktmpdir { |dir| + files.each do |file| + open("#{dir}/#{file.filename}", 'wb') do |tmp_file| + tmp_file << open(file.service_url.to_s).read + end + end + + read_files = Dir.entries("#{dir}/").select { |f| !File.directory? f } + raise StandardError.new "Files not found." unless read_files.any? + yield(dir, read_files) + } + end + + private + + attr_reader :files, :folder_name +end \ No newline at end of file diff --git a/app/views/admin/casting_call_interviews/_casting_call_interview.html.erb b/app/views/admin/casting_call_interviews/_casting_call_interview.html.erb new file mode 100644 index 0000000..697743b --- /dev/null +++ b/app/views/admin/casting_call_interviews/_casting_call_interview.html.erb @@ -0,0 +1,26 @@ + + + <%= casting_call_interview.casting_call.project.account.name.titleize %> + + + <%= casting_call_interview.casting_call.title.titleize %> + + + <%= casting_call_interview.performer_name %> + + + <%= casting_call_interview.interview_date %> + + +
+ <%= button_tag "Manage", class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
+ + diff --git a/app/views/admin/casting_call_interviews/_form.html.erb b/app/views/admin/casting_call_interviews/_form.html.erb new file mode 100644 index 0000000..ffa30fe --- /dev/null +++ b/app/views/admin/casting_call_interviews/_form.html.erb @@ -0,0 +1,15 @@ +<%= errors_summary_for casting_call_interview %> + +<%= bootstrap_form_with model: model, local: true do |form| %> + <%= form.text_field :performer_name, required: true %> + <%= form.grouped_collection_select(:casting_call_id, @accounts, :casting_calls, :name, :id, :title, { prompt: "Select a Casting Call", required: true, class: "form-control custom-select" }) %> + <%= form.text_field :interview_date, class: "datepicker-control" %> + <%= form.text_field :zoom_meeting_url %> + +
+ <%= link_to t("shared.cancel"), [:admin, :casting_call_interviews], class: "col-3 text-reset" %> +
+ <%= form.submit class: class_string("btn btn-block", ["btn-success", "btn-primary"] => casting_call_interview.new_record?), data: { disable_with: t("shared.disable_with") } %> +
+
+<% end %> diff --git a/app/views/admin/casting_call_interviews/edit.html.erb b/app/views/admin/casting_call_interviews/edit.html.erb new file mode 100644 index 0000000..bc03af7 --- /dev/null +++ b/app/views/admin/casting_call_interviews/edit.html.erb @@ -0,0 +1,6 @@ +
+ <%= card_header text: "Edit Casting Call Interview", close_action_path: [:admin, :casting_call_interviews] %> +
+ <%= render "form", model: [:admin, @casting_call_interview], casting_call_interview: @casting_call_interview %> +
+
\ No newline at end of file diff --git a/app/views/admin/casting_call_interviews/index.html.erb b/app/views/admin/casting_call_interviews/index.html.erb new file mode 100644 index 0000000..84d9d98 --- /dev/null +++ b/app/views/admin/casting_call_interviews/index.html.erb @@ -0,0 +1,32 @@ +
+ <% if policy(CastingCall).new? %> + <%= link_to fa_icon("plus", text: t(".actions.new")), [:new, :admin, :casting_call_interview], class: "btn btn-primary mb-3" %> + <% end %> +
+ +
+ + + + + + + + + + + + <% if @casting_call_interviews.any? %> + <%= render @casting_call_interviews %> + <% else %> + + + + <% end %> + +
Account NameCasting Call RequestPerfomer's NameInterview Date
<%= t(".empty") %>
+
+ +
+ <%= will_paginate @casting_call_interviews %> +
diff --git a/app/views/admin/casting_call_interviews/new.html.erb b/app/views/admin/casting_call_interviews/new.html.erb new file mode 100644 index 0000000..053d0ac --- /dev/null +++ b/app/views/admin/casting_call_interviews/new.html.erb @@ -0,0 +1,6 @@ +
+ <%= card_header text: t(".heading"), close_action_path: [:admin, :casting_call_interviews] %> +
+ <%= render "form", model: [:admin, @casting_call_interview], casting_call_interview: @casting_call_interview, casting_calls: @casting_calls %> +
+
diff --git a/app/views/casting_call_interviews/_casting_call_interview.html.erb b/app/views/casting_call_interviews/_casting_call_interview.html.erb new file mode 100644 index 0000000..ee1ff58 --- /dev/null +++ b/app/views/casting_call_interviews/_casting_call_interview.html.erb @@ -0,0 +1,27 @@ + + + <%= casting_call_interview.casting_call.project.account.name.titleize %> + + + <%= casting_call_interview.casting_call.title&.titleize %> + + + <%= casting_call_interview.performer_name %> + + + <%= casting_call_interview.interviewed_at %> + + +
+ <%= button_tag "Manage", class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
+ + diff --git a/app/views/casting_call_interviews/_file.html.erb b/app/views/casting_call_interviews/_file.html.erb new file mode 100644 index 0000000..85fe6bb --- /dev/null +++ b/app/views/casting_call_interviews/_file.html.erb @@ -0,0 +1,6 @@ + + <%= file.filename %> + + <%= link_to fa_icon("download"), file, target: "_blank" %> + + diff --git a/app/views/casting_call_interviews/index.html.erb b/app/views/casting_call_interviews/index.html.erb new file mode 100644 index 0000000..ef64b1e --- /dev/null +++ b/app/views/casting_call_interviews/index.html.erb @@ -0,0 +1,26 @@ +
+ + + + + + + + + + + + <% if @casting_call_interviews.any? %> + <%= render @casting_call_interviews %> + <% else %> + + + + <% end %> + +
Account NameCasting Call RequestPerfomer's NameInterviewed At
<%= t(".empty") %>
+
+ +
+ <%= will_paginate @casting_call_interviews %> +
diff --git a/app/views/casting_call_interviews/show.html.erb b/app/views/casting_call_interviews/show.html.erb new file mode 100644 index 0000000..727d285 --- /dev/null +++ b/app/views/casting_call_interviews/show.html.erb @@ -0,0 +1,25 @@ +
+

Files:

+
+ + + + + + + + + <% if @files.any? %> + <%= render partial: "file", collection: @files %> + <% else %> + + + + <% end %> + +
Filename
<%= t(".empty") %>
+
+ <%= will_paginate @files %> +
+
+
\ No newline at end of file diff --git a/app/views/casting_calls/_casting_call.html.erb b/app/views/casting_calls/_casting_call.html.erb new file mode 100644 index 0000000..d152b28 --- /dev/null +++ b/app/views/casting_calls/_casting_call.html.erb @@ -0,0 +1,28 @@ + + + <%= casting_call.created_at.strftime('%D') %> + + + <%= casting_call.title %> + + + <%= casting_call.status %> + + +
+ <%= button_tag t(".actions.manage"), class: "btn btn-light btn-sm dropdown-toggle border", data: { toggle: "dropdown", boundary: "window" }, aria: { haspopup: true, expanded: false } %> + +
+ + diff --git a/app/views/casting_calls/_form.html.erb b/app/views/casting_calls/_form.html.erb new file mode 100644 index 0000000..b5434dc --- /dev/null +++ b/app/views/casting_calls/_form.html.erb @@ -0,0 +1,22 @@ +<%= errors_summary_for casting_call %> + +<%= bootstrap_form_with model: model, url: [@project, @casting_call, show_chat: true], local: true do |form| %> +
+ <%= fa_icon "info-circle" %> + <%= t '.info_message' %> +
+ + <%= form.text_field :title, label: t('.labels.title') %> + <%= form.text_area :description, label: t('.labels.description') %> + <%= form.text_area :project_description, label: t('.labels.project_description') %> + <%= form.text_area :interview_instructions, label: t('.labels.interview_instructions') %> + <%= form.text_area :interview_requirements, label: t('.labels.interview_requirements') %> + <%= form.text_area :questions, label: t('.labels.questions') %> + +
+ <%= link_to t("shared.cancel"), [project, :casting_calls], class: "col-3 text-reset" %> +
+ <%= form.submit class: class_string("btn btn-block", ["btn-success", "btn-primary"] => casting_call.new_record?), data: { disable_with: t("shared.disable_with") } %> +
+
+<% end %> \ No newline at end of file diff --git a/app/views/casting_calls/create.html.erb b/app/views/casting_calls/create.html.erb new file mode 100644 index 0000000..9a52e24 --- /dev/null +++ b/app/views/casting_calls/create.html.erb @@ -0,0 +1,2 @@ +<%= render "shared/initiate_hubspot_chat" %> +

<%= t '.success_message' %>

diff --git a/app/views/casting_calls/edit.html.erb b/app/views/casting_calls/edit.html.erb new file mode 100644 index 0000000..d09dc0f --- /dev/null +++ b/app/views/casting_calls/edit.html.erb @@ -0,0 +1,6 @@ +
+ <%= card_header text: t(".heading"), close_action_path: [@project, :casting_calls] %> +
+ <%= render "form", model: [@project, @casting_call], casting_call: @casting_call, project: @project %> +
+
\ No newline at end of file diff --git a/app/views/casting_calls/index.html.erb b/app/views/casting_calls/index.html.erb new file mode 100644 index 0000000..434e2af --- /dev/null +++ b/app/views/casting_calls/index.html.erb @@ -0,0 +1,37 @@ +<%= product_wordmark :cast_me, class: "small mb-3" %> + +
+
+
+ <% if policy(CastingCall).new? %> + <%= link_to fa_icon("plus", text: t(".actions.new")), [:new, @project, :casting_call], class: "btn btn-primary mb-2" %> + <% end %> +
+
+
+ +
+ + + + + + + + + + + <% if @casting_calls.any? %> + <%= render @casting_calls %> + <% else %> + + + + <% end %> + +
<%= t(".table_headers.casting_call_created_on") %><%= t(".table_headers.casting_call_title") %><%= t(".table_headers.casting_call_status") %>
<%= t(".empty") %>
+
+ +
+ <%= will_paginate @casting_calls %> +
\ No newline at end of file diff --git a/app/views/casting_calls/new.html.erb b/app/views/casting_calls/new.html.erb new file mode 100644 index 0000000..d09dc0f --- /dev/null +++ b/app/views/casting_calls/new.html.erb @@ -0,0 +1,6 @@ +
+ <%= card_header text: t(".heading"), close_action_path: [@project, :casting_calls] %> +
+ <%= render "form", model: [@project, @casting_call], casting_call: @casting_call, project: @project %> +
+
\ No newline at end of file diff --git a/app/views/casting_calls/show.html.erb b/app/views/casting_calls/show.html.erb new file mode 100644 index 0000000..a56078c --- /dev/null +++ b/app/views/casting_calls/show.html.erb @@ -0,0 +1,38 @@ +<% content_for :header do %> +
+
+
+ <%= product_wordmark(:cast_me, class: 'navbar-brand') %> +
+
+
+<% end %> + +
+ <%= card_header text: @casting_call.title, close_action_path: [@project, :casting_calls] %> +
+
+
+
+ <%= description_list_pair_for @casting_call, :title, append: ":" %> + <%= description_list_pair_for @casting_call, :description, append: ":" %> + <%= description_list_pair_for @casting_call, :project_description, append: ":" %> + <%= description_list_pair_for @casting_call, :created_at, append: ":" %> +
+
+
+
+ <%= description_list_pair_for @casting_call, :status, append: ":" %> + <%= description_list_pair_for @casting_call, :interview_instructions, append: ":" %> + <%= description_list_pair_for @casting_call, :interview_requirements, append: ":" %> + <%= description_list_pair_for @casting_call, :questions, append: ":" %> +
+
+
+ <% unless @casting_call.cancelled? %> +
+ <%= link_to "Schedule an Audition", ENV["CASTME_AUDITION_BOOKING_URL"], target: "_blank", class: "btn btn-primary" %> +
+ <% end %> +
+
diff --git a/app/views/interview_downloads/_other_pending_downloads.html.erb b/app/views/interview_downloads/_other_pending_downloads.html.erb new file mode 100644 index 0000000..d61bafb --- /dev/null +++ b/app/views/interview_downloads/_other_pending_downloads.html.erb @@ -0,0 +1,17 @@ +

Your <%= release_type.titleize %> files are being prepared for download. You will be notified when it's ready. +

+

The following downloads are also in progress:

+ diff --git a/app/views/public/casting_call_interviews/show.html.erb b/app/views/public/casting_call_interviews/show.html.erb new file mode 100644 index 0000000..a0baedf --- /dev/null +++ b/app/views/public/casting_call_interviews/show.html.erb @@ -0,0 +1,51 @@ +
+ <%= card_header text: "Casting call interview details" %> +
+
+
+
+ <%= description_list_pair_for @casting_call_interview, :performer_name, append: ":" %> + <%= description_list_pair_for @casting_call_interview, :interview_date, append: ":" %> +
+
+
+
INTERVIEW FILES:
+
+
+
+
+ <%= card_header text: t(".heading") %> +
+ <%= errors_summary_for @casting_call_interview %> + <%= bootstrap_form_with model: @casting_call_interview, url: casting_call_interview_path(token: @casting_call_interview.token), local: true do |form| %> +
+ <%= form.label :files %> + <%= form.file_field :files, disable: true, direct_upload: true, multiple: true, id: "casting_call_interivew_files", hide_label: true %> + <% @casting_call_interview.files.each do |file| %> + <% unless file.persisted? %> + <%= hidden_field_tag "#{@casting_call_interview.model_name.param_key}[files][]", file.signed_id %> + <% end %> + <% end %> +
+ +
+ +
+ <%= form.button t(".update"), class: "btn btn-block btn-lg btn-success", data: { disable_with: t("shared.disable_with") } %> +
+ <% end %> +
+
+
+
+
+ <%= link_to "Start Interview", @casting_call_interview.join_zoom_meeting_url, target: "_blank", class: "btn btn-primary" %> +
+
+
diff --git a/app/views/public/casting_calls/show.html.erb b/app/views/public/casting_calls/show.html.erb new file mode 100644 index 0000000..c9ec824 --- /dev/null +++ b/app/views/public/casting_calls/show.html.erb @@ -0,0 +1,29 @@ +<% content_for :header do %> +
+
+
+ <%= product_wordmark(:cast_me, class: 'navbar-brand') %> +
+
+
+<% end %> + +
+ <%= card_header text: @casting_call.title %> +
+
+
+
+ <%= description_list_pair_for @casting_call, :title, append: ":" %> + <%= description_list_pair_for @casting_call, :description, append: ":" %> + <%= description_list_pair_for @casting_call, :project_description, append: ":" %> +
+
+
+ <% unless @casting_call.cancelled? %> +
+ <%= link_to "Schedule an Audition", ENV["CASTME_AUDITION_BOOKING_URL"], target: "_blank", class: "btn btn-primary" %> +
+ <% end %> +
+
diff --git a/app/views/shared/_initiate_hubspot_chat.html.erb b/app/views/shared/_initiate_hubspot_chat.html.erb new file mode 100644 index 0000000..900ac85 --- /dev/null +++ b/app/views/shared/_initiate_hubspot_chat.html.erb @@ -0,0 +1,15 @@ +<% if params[:show_chat] %> + <%= javascript_include_tag "//js.hs-scripts.com/7344617.js", defer: "defer", async: true, id: "hs-script-loader" %> + <%= javascript_tag nonce: true do %> + function onConversationsAPIReady() { + window.HubSpotConversations.widget.load({ widgetOpen: true }); + window.HubSpotConversations.widget.open(); + } + if (window.HubSpotConversations) { + onConversationsAPIReady(); + } else { + window.hsConversationsOnReady = [onConversationsAPIReady]; + } + <% end %> +<% end %> + diff --git a/db/migrate/20200626044744_create_casting_calls.rb b/db/migrate/20200626044744_create_casting_calls.rb new file mode 100644 index 0000000..0642635 --- /dev/null +++ b/db/migrate/20200626044744_create_casting_calls.rb @@ -0,0 +1,17 @@ +class CreateCastingCalls < ActiveRecord::Migration[6.0] + def change + create_table :casting_calls do |t| + t.references :project + t.string :title + t.string :user_email + t.text :description + t.text :project_description + t.text :interview_instructions + t.text :interview_requirements + t.text :questions + t.datetime :cancelled_at + + t.timestamps + end + end +end diff --git a/db/migrate/20200701121237_create_casting_call_interviews.rb b/db/migrate/20200701121237_create_casting_call_interviews.rb new file mode 100644 index 0000000..fe4f4d7 --- /dev/null +++ b/db/migrate/20200701121237_create_casting_call_interviews.rb @@ -0,0 +1,12 @@ +class CreateCastingCallInterviews < ActiveRecord::Migration[6.0] + def change + create_table :casting_call_interviews do |t| + t.references :casting_call, foreign_key: true + t.string :performer_name + t.string :zoom_meeting_url + t.datetime :interview_date + + t.timestamps + end + end +end diff --git a/db/migrate/20200706193123_add_token_to_casting_calls.rb b/db/migrate/20200706193123_add_token_to_casting_calls.rb new file mode 100644 index 0000000..57e8ea8 --- /dev/null +++ b/db/migrate/20200706193123_add_token_to_casting_calls.rb @@ -0,0 +1,6 @@ +class AddTokenToCastingCalls < ActiveRecord::Migration[6.0] + def change + add_column :casting_calls, :token, :string + add_index :casting_calls, :token, unique: true + end +end diff --git a/db/migrate/20200706230803_add_token_to_casting_call_interviews.rb b/db/migrate/20200706230803_add_token_to_casting_call_interviews.rb new file mode 100644 index 0000000..9c5689c --- /dev/null +++ b/db/migrate/20200706230803_add_token_to_casting_call_interviews.rb @@ -0,0 +1,6 @@ +class AddTokenToCastingCallInterviews < ActiveRecord::Migration[6.0] + def change + add_column :casting_call_interviews, :token, :string + add_index :casting_call_interviews, :token, unique: true + end +end diff --git a/db/migrate/20200707070522_add_interviewed_at_to_casting_call_interview.rb b/db/migrate/20200707070522_add_interviewed_at_to_casting_call_interview.rb new file mode 100644 index 0000000..ab41369 --- /dev/null +++ b/db/migrate/20200707070522_add_interviewed_at_to_casting_call_interview.rb @@ -0,0 +1,5 @@ +class AddInterviewedAtToCastingCallInterview < ActiveRecord::Migration[6.0] + def change + add_column :casting_call_interviews, :interviewed_at, :datetime + end +end diff --git a/spec/controllers/admin/casting_call_interviews_controller_spec.rb b/spec/controllers/admin/casting_call_interviews_controller_spec.rb new file mode 100644 index 0000000..d2ff7b2 --- /dev/null +++ b/spec/controllers/admin/casting_call_interviews_controller_spec.rb @@ -0,0 +1,106 @@ +require "rails_helper" + +RSpec.describe Admin::CastingCallInterviewsController, type: :controller do + + let!(:current_user) { create(:user, :admin) } + + before do + sign_in(current_user) + end + + describe "#index" do + it "returns a successful response" do + get :index + + expect(response).to be_successful + end + end + + describe "#new" do + it "returns a successful response" do + get :new + + expect(response).to be_successful + end + + it "assigns user, accounts" do + get :new + + expect(assigns(:casting_call_interview)).not_to be_nil + expect(assigns(:accounts)).to eq Account.all + end + end + + describe "#create" do + it "does create a new record" do + expect { + post :create, params: { casting_call_interview: casting_call_interview_params } + }.to change(CastingCallInterview, :count) + end + end + + describe "#edit" do + let(:casting_call_interview) { create(:casting_call_interview) } + + it "returns a successful response" do + get :edit, params: { id: casting_call_interview } + + expect(response).to be_successful + end + + it "assigns casting call interview" do + get :edit, params: { id: casting_call_interview } + + expect(assigns(:casting_call_interview)).to eq casting_call_interview + end + end + + describe "#update" do + let(:casting_call_interview) { create(:casting_call_interview) } + + it "redirects to casting call interviews page" do + patch :update, params: { id: casting_call_interview, casting_call_interview: update_params } + + expect(response).to be_redirect + expect(response).to redirect_to admin_casting_call_interviews_path + end + + it "sets a flash notice" do + patch :update, params: { id: casting_call_interview, casting_call_interview: update_params } + + expect(flash.notice).to eq "The casting call interview has been updated" + end + + it "updates casting call interview" do + patch :update, params: { id: casting_call_interview, casting_call_interview: update_params } + + expect(casting_call_interview.reload.zoom_meeting_url).to eq("new_zoom_meeting_url") + end + end + + describe "#complete" do + let(:casting_call_interview) { create(:casting_call_interview) } + + it "sets interviewed_at on casting call interview" do + expect(casting_call_interview.interviewed_at).to be_nil + + post :complete, params: { id: casting_call_interview } + + expect(casting_call_interview.reload.interviewed_at).not_to be_nil + end + end + + private + + def casting_call_interview_params + casting_call = create(:casting_call) + + attributes_for(:casting_call_interview).except(:interviewed_at).merge(casting_call_id: casting_call.id) + end + + def update_params + { + zoom_meeting_url: "new_zoom_meeting_url" + } + end +end diff --git a/spec/controllers/casting_call_interviews_controller_spec.rb b/spec/controllers/casting_call_interviews_controller_spec.rb new file mode 100644 index 0000000..2ac60aa --- /dev/null +++ b/spec/controllers/casting_call_interviews_controller_spec.rb @@ -0,0 +1,43 @@ +require "rails_helper" + +RSpec.describe CastingCallInterviewsController, type: :controller do + render_views + + let(:user) { create(:user) } + let(:account) { user.primary_account } + let(:project) { create(:project, account: user.primary_account) } + let(:casting_call) { create(:casting_call, project: project, title: "My Interview") } + + before do + sign_in(user) + end + + describe "#index" do + it "returns a successful response" do + get :index, params: { project_id: project } + + expect(response).to be_successful + end + + it "only shows completed interviews" do + create(:casting_call_interview, casting_call: casting_call, interviewed_at: Time.zone.now, performer_name: "John Doe") + create(:casting_call_interview, casting_call: casting_call, interviewed_at: nil, performer_name: "Jane Doe") + + get :index, params: { project_id: project } + + expect(response.body).to have_content("John Doe") + expect(response.body).not_to have_content("Jane Doe") + end + end + + describe "#show" do + let!(:casting_call_interview) { create(:casting_call_interview, :with_files, casting_call: casting_call, interviewed_at: Time.zone.now, performer_name: "Jane Doe") } + + it "shows files of casting call interview" do + get :show, params: { project_id: project, id: casting_call_interview.id } + + expect(response.body).to have_content("Filename") + expect(response.body).to have_content("location_photo.png") + end + end +end diff --git a/spec/controllers/casting_calls_controller_spec.rb b/spec/controllers/casting_calls_controller_spec.rb new file mode 100644 index 0000000..3c23bee --- /dev/null +++ b/spec/controllers/casting_calls_controller_spec.rb @@ -0,0 +1,126 @@ +require 'rails_helper' + +RSpec.describe CastingCallsController, type: :controller do + render_views + + let(:user) { create(:user) } + let(:account) { user.primary_account } + let(:project) { create(:project, account: user.primary_account) } + + before do + sign_in user + end + + describe "#index" do + it "responds successfully" do + get :index, params: { project_id: project } + + expect(response).to be_successful + end + + it "renders content" do + create(:casting_call, project: project) + + get :index, params: { project_id: project } + + expect(response.body).to have_link "Create Casting Call" + expect(response.body).to have_content "Active" + end + + context "when there are many records" do + it "paginates the table" do + create_list(:casting_call, 20, project: project) + + get :index, params: { project_id: project } + + expect(response.body).to have_link("2", href: project_casting_calls_path(project, page: 2)) + end + end + end + + describe "#new" do + it "responds successfully" do + get :new, params: { project_id: project } + + expect(response).to be_successful + expect(assigns(:casting_call)).to be_a_new(CastingCall) + expect(response).to render_template(:new) + end + end + + describe "#create" do + it "does create a new record" do + expect { + post :create, params: { project_id: project.id, casting_call: casting_call_params } + }.to change(CastingCall, :count) + end + + it "logs an event" do + expect { + post :create, params: { project_id: project.id, casting_call: casting_call_params } + }.to have_enqueued_job(TrackAnalyticsJob).with(user, account, :track_create_casting_call, user_agent: "Rails Testing", user_ip: "0.0.0.0") + end + + it "submits data to hubspot form" do + expect { + post :create, params: { project_id: project.id, casting_call: casting_call_params } + }.to have_enqueued_job(SubmitHubspotFormJob) + end + end + + describe "#update" do + let!(:casting_call) { create(:casting_call, project: project, description: "My description" ) } + + it "updates casting call request" do + patch :update, params: { project_id: project.id, id: casting_call.id, casting_call: update_params } + + expect(casting_call.reload.description).to eq("This is updated description") + end + end + + describe "#show" do + let!(:casting_call) { create(:casting_call, project: project, description: "Casting Call Request") } + + it "responds successfully" do + get :show, params: { project_id: project.id, id: casting_call.id } + + expect(response).to be_successful + expect(assigns(:casting_call)).to eq(casting_call) + end + + it "renders content" do + get :show, params: { project_id: project.id, id: casting_call.id } + + expect(response.body).to have_content "Casting Call Request" + expect(response.body).to have_content "Active" + end + end + + describe "#cancel" do + let!(:casting_call) { create(:casting_call, project: project, description: "Casting Call to be Cancelled") } + + it "responds with redirect" do + post :cancel, params: { project_id: project.id, id: casting_call.id } + + expect(response).to be_redirect + expect(response).to redirect_to(project_casting_calls_path(project)) + expect(flash.notice).not_to be_nil + end + + it "updates the status to 'Cancelled'" do + expect { + post :cancel, params: { project_id: project.id, id: casting_call.id } + }.to change { casting_call.reload.status }.from("Active").to("Cancelled") + end + end + + private + + def casting_call_params + attributes_for(:casting_call).except(:status, :user_email) + end + + def update_params + { description: "This is updated description" } + end +end \ No newline at end of file diff --git a/spec/controllers/interview_downloads_controller_spec.rb b/spec/controllers/interview_downloads_controller_spec.rb new file mode 100644 index 0000000..119250f --- /dev/null +++ b/spec/controllers/interview_downloads_controller_spec.rb @@ -0,0 +1,58 @@ +require "rails_helper" + +RSpec.describe InterviewDownloadsController, type: :controller do + render_views + + let(:current_user) { create(:user) } + let(:project) { create(:project, :discovery_client, account: current_user.primary_account) } + let(:casting_call) { create(:casting_call, project: project, title: "My Title") } + let(:casting_call_interview) { create(:casting_call_interview, casting_call: casting_call, performer_name: "John Doe") } + + before do + sign_in current_user + end + + describe "#create" do + it "enqueues zip file generation job" do + expect { + post :create, params: { project_id: project.id, casting_call_interview_id: casting_call_interview.id }, format: :js + }.to have_enqueued_job(GenerateInterviewFilesZipJob) + end + + it "creates a download record with 'not_started' status" do + expect { + post :create, params: { project_id: project.id, casting_call_interview_id: casting_call_interview.id }, format: :js + }.to change(Download, :count).by(1) + + expect(Download.last.status).to eq('not_started') + end + + context "When there is no existing job" do + it "shows a notification to user" do + allow(ProjectsChannel).to receive(:broadcast_download_generation_update).with(be_kind_of(Download), I18n.t("interview_downloads.download.pending", release_type: "Casting Call Interview")) + + post :create, params: { project_id: project.id, casting_call_interview_id: casting_call_interview.id }, format: :js + + expect(ProjectsChannel).to have_received(:broadcast_download_generation_update).with(be_kind_of(Download), I18n.t("interview_downloads.download.pending", release_type: "Casting Call Interview")) + end + end + + context "When there are existing jobs" do + let(:appearance_release_download) { create(:download, project_id: project.id, name: "#{project.name.parameterize}_appearance-releases") } + let(:acquired_media_release_download) { create(:download, project_id: project.id, name: "#{project.name.parameterize}_acquired-media-releases", release_type: "AcquiredMediaRelease") } + + before do + allow(Download).to receive_message_chain(:unfinished_desc_order, :offset).and_return([acquired_media_release_download, appearance_release_download]) + allow(ProjectsChannel).to receive(:broadcast_download_generation_update) + end + + it "shows names of other contracts in the notification, which are in progress" do + broadcast_message = "

Your Casting Call Interview files are being prepared for download. You will be notified when it's ready.\n

\n

The following downloads are also in progress:

\n\n" + + post :create, params: { project_id: project.id, casting_call_interview_id: casting_call_interview.id }, format: :js + + expect(ProjectsChannel).to have_received(:broadcast_download_generation_update).with(be_kind_of(Download), broadcast_message) + end + end + end +end diff --git a/spec/controllers/public/casting_call_interviews_controller.rb b/spec/controllers/public/casting_call_interviews_controller.rb new file mode 100644 index 0000000..fb08c74 --- /dev/null +++ b/spec/controllers/public/casting_call_interviews_controller.rb @@ -0,0 +1,44 @@ +require 'rails_helper' + +RSpec.describe Public::CastingCallInterviewsController, type: :controller do + render_views + + describe "#show" do + let(:casting_call_interview) { create(:casting_call_interview) } + + it "responds successfully" do + get :show, params: { token: casting_call_interview.token } + + expect(response).to be_successful + expect(assigns(:casting_call_interview)).to eq(casting_call_interview) + end + + it "shows casting call interview details" do + get :show, params: { token: casting_call_interview.token } + + expect(response.body).to have_content(casting_call_interview.performer_name) + expect(response.body).to have_content(casting_call_interview.interview_date) + expect(response.body).to have_link("Start Interview") + end + end + + describe "#update" do + let(:casting_call_interview) { create(:casting_call_interview) } + + it "responds successfully" do + patch :update, params: { token: casting_call_interview.token, casting_call_interview: casting_call_interview_params } + + expect(response).to redirect_to casting_call_interview_url(token: casting_call_interview.token) + expect(flash.notice).to be_present + end + end + + private + + def casting_call_interview_params + path = Rails.root.join("spec", "fixtures", "files", "contract.pdf") + file = Rack::Test::UploadedFile.new(path, "application/pdf") + + { files: [file]} + end +end diff --git a/spec/controllers/public/casting_calls_controller_spec.rb b/spec/controllers/public/casting_calls_controller_spec.rb new file mode 100644 index 0000000..e7ce2c5 --- /dev/null +++ b/spec/controllers/public/casting_calls_controller_spec.rb @@ -0,0 +1,28 @@ +require 'rails_helper' + +RSpec.describe Public::CastingCallsController, type: :controller do + render_views + + describe "#show" do + let(:casting_call) { create(:casting_call) } + + it "responds successfully" do + get :show, params: { token: casting_call.token } + + expect(response).to be_successful + expect(assigns(:casting_call)).to eq(casting_call) + end + + it "shows casting call details" do + get :show, params: { token: casting_call.token } + + expect(response.body).to have_content(casting_call.title) + expect(response.body).to have_content(casting_call.description) + expect(response.body).to have_content(casting_call.project_description) + expect(response.body).to have_content(casting_call.interview_instructions) + expect(response.body).to have_content(casting_call.interview_requirements) + expect(response.body).to have_content(casting_call.questions) + expect(response.body).to have_link("Schedule an Audition") + end + end +end diff --git a/spec/factories/casting_call_interviews.rb b/spec/factories/casting_call_interviews.rb new file mode 100644 index 0000000..389bd9d --- /dev/null +++ b/spec/factories/casting_call_interviews.rb @@ -0,0 +1,13 @@ +FactoryBot.define do + factory :casting_call_interview do + association :casting_call + performer_name 'John Doe' + zoom_meeting_url 'https://us04web.zoom.us/j/1111111111?pwd=aDZCS1dzZ2lWdDZJcHBhVnNIclB4QT03' + interview_date { 10.days.from_now } + interviewed_at { nil } + + trait :with_files do + files { [Rack::Test::UploadedFile.new('spec/fixtures/files/location_photo.png', 'image/png')] } + end + end +end diff --git a/spec/factories/casting_calls.rb b/spec/factories/casting_calls.rb new file mode 100644 index 0000000..0db776a --- /dev/null +++ b/spec/factories/casting_calls.rb @@ -0,0 +1,15 @@ +FactoryBot.define do + factory :casting_call do + association :project + user_email 'test@email.com' + description "Casting call description" + project_description "Casting call project description" + interview_instructions "Interview instructions" + interview_requirements "Interview requirements" + questions "Questions" + + trait :cancelled do + cancelled_at { Time.zone.now } + end + end +end diff --git a/spec/features/user_managing_casiting_calls_spec.rb b/spec/features/user_managing_casiting_calls_spec.rb new file mode 100644 index 0000000..cc023d2 --- /dev/null +++ b/spec/features/user_managing_casiting_calls_spec.rb @@ -0,0 +1,155 @@ +require "rails_helper" + +feature "User managing casting calls" do + let(:current_user) { create(:user) } + let(:project) { create(:project, account: current_user.primary_account) } + + before :each do + sign_in current_user + end + + scenario "casting calls table is visible" do + visit project_casting_calls_path(project) + + expect(page).to have_content "Created On" + expect(page).to have_content "Title" + expect(page).to have_content "Status" + end + + scenario "sees list of casting calls" do + visit project_casting_calls_path(project) + + expect(page).to have_content no_casting_calls_label + + casting_call = create(:casting_call, project: project) + + visit project_casting_calls_path(project) + + expect(page).not_to have_content no_casting_calls_label + + expect(page).to have_content casting_call.created_at.try(:strftime, '%D') + expect(page).to have_content casting_call.title + expect(page).to have_content casting_call.status + end + + scenario "can create casting call requests" do + visit project_casting_calls_path(project) + + expect(page).to have_content no_casting_calls_label + click_on add_new_casting_call_label + + fill_in title_field, with: "Title" + fill_in description_field, with: "Description" + fill_in project_description_field, with: "Project Description" + fill_in interview_instructions_field, with: "Interview instructions" + fill_in interview_requirements_field, with: "Interview requirements" + fill_in questions_field, with: "Questions" + + click_on "Create Casting call" + + expect(page).to have_content("Your casting call request was successfully submitted. Thank you. A chat window will pop up on the lower right in a few seconds.") + end + + scenario "can update casting call requests" do + create(:casting_call, title: "Title", project: project) + visit project_casting_calls_path(project) + + click_on manage_button + click_on "Edit" + + fill_in title_field, with: "New Title" + + click_on "Update Casting call" + + expect(page).to have_content("The casting call request has been updated") + end + + scenario "can cancel a casting call request" do + create(:casting_call, title: "Title", project: project) + visit project_casting_calls_path(project) + + click_on manage_button + click_on "Cancel" + + expect(page).to have_content("The casting call request has been cancelled") + end + + scenario "can open casting call details" do + cc = create(:casting_call, title: "Dummy title", project: project) + + visit project_casting_calls_path(project) + + click_on manage_button + click_on view_button + + expect(page).to have_content cc.title + expect(page).to have_content cc.description + expect(page).to have_content cc.project_description + expect(page).to have_content cc.created_at + expect(page).to have_content cc.status + expect(page).to have_content cc.interview_instructions + expect(page).to have_content cc.interview_requirements + expect(page).to have_content cc.questions + end + + context "when signed out" do + scenario "user opens public accessible casting call URL" do + cc = create(:casting_call, title: "Dummy title", project: project) + + sign_out + public_url = "/casting_calls/#{cc.token}" + visit public_url + + expect(page).to have_content cc.title + expect(page).to have_content cc.description + expect(page).to have_content cc.project_description + expect(page).not_to have_content cc.created_at + expect(page).not_to have_content cc.status + expect(page).not_to have_content cc.interview_instructions + expect(page).not_to have_content cc.interview_requirements + expect(page).not_to have_content cc.questions + end + end + + private + + def no_casting_calls_label + "Casting calls will appear here" + end + + def manage_button + t "casting_calls.casting_call.actions.manage" + end + + def view_button + 'View' + end + + def add_new_casting_call_label + t "casting_calls.index.actions.new" + end + + def title_field + t "casting_calls.form.labels.title" + end + + def description_field + t "casting_calls.form.labels.description" + end + + def project_description_field + t "casting_calls.form.labels.project_description" + end + + def interview_instructions_field + t "casting_calls.form.labels.interview_instructions" + end + + def interview_requirements_field + t "casting_calls.form.labels.interview_requirements" + end + + def questions_field + t "casting_calls.form.labels.questions" + end +end \ No newline at end of file diff --git a/spec/jobs/generate_interview_files_zip_job_spec.rb b/spec/jobs/generate_interview_files_zip_job_spec.rb new file mode 100644 index 0000000..0cb9eb4 --- /dev/null +++ b/spec/jobs/generate_interview_files_zip_job_spec.rb @@ -0,0 +1,60 @@ +require "rails_helper" + +describe GenerateInterviewFilesZipJob do + let(:project) { create(:project) } + let(:download) { create(:download, project: project, release_type: "CastingCallInterview", name: "my-title_john-doe") } + let(:casting_call) { create(:casting_call, project: project, title: "My Title") } + let(:casting_call_interview) { create(:casting_call_interview, casting_call: casting_call, performer_name: "John Doe") } + + before do + dir = Rails.root.join("spec", "fixtures", "files") + files = ["contract.pdf", "AppearanceRelease.pdf"] + # Attachments in the test environment do not persist to cloud storage + # Therefore we want to stub calls to `open` with a cloud storage URL + allow_any_instance_of(InterviewFilesCollectionService).to receive(:open).and_return(StringIO.new("file data")) + allow_any_instance_of(InterviewFilesCollectionService).to receive(:build).and_yield(dir, files) + end + + describe ".perform_later" do + it "enqueues a background job for generating zip file" do + expect { + GenerateInterviewFilesZipJob.perform_later(project, download, casting_call_interview) + }.to have_enqueued_job + end + end + + describe ".perform_now" do + it "updates a download record and creates attachment for it" do + GenerateInterviewFilesZipJob.perform_now(project, download, casting_call_interview) + + expect(download.project).to eq project + expect(download.release_type).to eq "CastingCallInterview" + expect(download.name).to eq "my-title_john-doe" + expect(download.status).to eq "success" + expect(download.file).to be_attached + end + + context "When there are errors" do + let(:error) { StandardError.new("Casting Call Interview files not found.") } + + before do + allow(ProjectsChannel).to receive(:broadcast_download_generation_update).with(download, I18n.t("interview_downloads.download.failure")) + allow_any_instance_of(InterviewFilesCollectionService).to receive(:build).and_raise(StandardError, "Casting Call Interview files not found.") + end + + it "updates status to 'failure' and sends user a notification" do + GenerateInterviewFilesZipJob.perform_now(project, download, casting_call_interview) + + expect(download.status).to eq "failure" + expect(ProjectsChannel).to have_received(:broadcast_download_generation_update).with(download, I18n.t("interview_downloads.download.failure")) + end + end + end + + after do + # Delete the file created in fixture. + # Or the tests will fail on next run due to already existing files in existing zip. + path = Rails.root.join("spec", "fixtures", "files") + File.delete("#{path}/my-title_john-doe.zip") if File.exists? "#{path}/my-title_john-doe.zip" + end +end diff --git a/spec/models/casting_call_interview_spec.rb b/spec/models/casting_call_interview_spec.rb new file mode 100644 index 0000000..8affc14 --- /dev/null +++ b/spec/models/casting_call_interview_spec.rb @@ -0,0 +1,12 @@ +require 'rails_helper' + +RSpec.describe CastingCallInterview, type: :model do + describe "associations" do + it { is_expected.to belong_to(:casting_call) } + it { is_expected.to have_secure_token(:token) } + end + + describe "validations" do + it { is_expected.to validate_presence_of(:performer_name) } + end +end diff --git a/spec/models/casting_call_spec.rb b/spec/models/casting_call_spec.rb new file mode 100644 index 0000000..c46e97f --- /dev/null +++ b/spec/models/casting_call_spec.rb @@ -0,0 +1,11 @@ +require 'rails_helper' + +RSpec.describe CastingCall, type: :model do + describe "associations" do + it { is_expected.to belong_to(:project) } + end + + describe "validations" do + it { is_expected.to have_secure_token(:token) } + end +end diff --git a/spec/policies/casting_call_policy_spec.rb b/spec/policies/casting_call_policy_spec.rb new file mode 100644 index 0000000..39aac8e --- /dev/null +++ b/spec/policies/casting_call_policy_spec.rb @@ -0,0 +1,91 @@ +require "rails_helper" + +describe CastingCallPolicy do + subject { described_class } + + let(:user_context) { build(:user_context, user: user, account: user.primary_account) } + + context "for an associate" do + let(:user) { create(:user, :associate, admin: false) } + + permissions :index? do + it { is_expected.to permit(user_context, subject) } + end + + permissions :create? do + it { is_expected.to permit(user_context, subject) } + end + + permissions :show? do + it { is_expected.to permit(user_context, subject) } + end + + permissions :destroy? do + it { is_expected.to permit(user_context, subject) } + end + + permissions :update? do + it { is_expected.to permit(user_context, subject) } + end + + permissions :cancel? do + it { is_expected.to permit(user_context, subject) } + end + end + + context "for a project manager" do + let(:user) { create(:user, :manager, admin: false) } + + permissions :index? do + it { is_expected.to permit(user_context, subject) } + end + + permissions :create? do + it { is_expected.to permit(user_context, subject) } + end + + permissions :show? do + it { is_expected.to permit(user_context, subject) } + end + + permissions :destroy? do + it { is_expected.to permit(user_context, subject) } + end + + permissions :update? do + it { is_expected.to permit(user_context, subject) } + end + + permissions :cancel? do + it { is_expected.to permit(user_context, subject) } + end + end + + context "for account managers" do + let(:user) { create(:user, :account_manager, admin: false) } + + permissions :index? do + it { is_expected.to permit(user_context, subject) } + end + + permissions :create? do + it { is_expected.to permit(user_context, subject) } + end + + permissions :show? do + it { is_expected.to permit(user_context, subject) } + end + + permissions :destroy? do + it { is_expected.to permit(user_context, subject) } + end + + permissions :update? do + it { is_expected.to permit(user_context, subject) } + end + + permissions :cancel? do + it { is_expected.to permit(user_context, subject) } + end + end +end