Compare commits

..

10 Commits

Author SHA1 Message Date
Bilal
057bdfe882 fix MR comments 2020-09-09 15:00:26 +03:00
Bilal
545d12f427 fix 2020-09-09 14:34:59 +03:00
Bilal
ac7e67c20e add specs 2020-09-09 14:34:59 +03:00
Bilal
9bafbe36db implement account locking/unlocking 2020-09-09 14:34:59 +03:00
Senad Uka
3db230de9b Upstream sync 2020-09-09 05:33:57 +02:00
Senad Uka
e5ac3e9345 Upstream sync 2020-09-06 21:42:50 +02:00
Senad Uka
f611382e9e Upstream sync 2020-09-01 17:15:16 +02:00
Senad Uka
95a14ab2f6 Upstream sync 2020-08-31 18:19:00 +02:00
Senad Uka
896cec2259 Upstream sync 2020-08-25 05:24:49 +02:00
Senad Uka
a493076f9b Upstream sync 2020-08-24 15:52:23 +02:00
129 changed files with 1646 additions and 1978 deletions

View File

@@ -32,10 +32,3 @@ CUSTOM_API_TOKEN=
# Required for simulcasting to Millicast for director mode
MILLICAST_API_SECRET=
MILLICAST_ACCOUNT_ID=
# Required for Microsoft Azure AD Auth
AZURE_CLIENT_ID = Client App ID
AZURE_CLIENT_SECRET = Client App Secret
AZURE_TENANT_ID = Client App Tenant ID
AZURE_REDIRECT_URI = where microsoft will redirect after login, eg. http://localhost:3000/auth/azure_ad/callback
AZURE_SCOPES = Scopes required for Application, eg. 'openid email profile User.Read offline_access OnlineMeetings.ReadWrite'

View File

@@ -139,11 +139,6 @@ gem 'rack-cors'
# Ruby wrappers for the HubSpot REST API
gem "hubspot-ruby"
# OAuth
gem 'omniauth-oauth2', '~> 1.6'
# OmniAuth CSRF protection
gem 'omniauth-rails_csrf_protection', '~> 0.1.2'
group :development, :test, :review do
# Call "byebug" anywhere in the code to stop execution and get a debugger console
gem "byebug", "~> 11.0.1", platforms: [:mri, :mingw, :x64_mingw]

View File

@@ -220,7 +220,6 @@ GEM
activesupport (>= 4.2.0)
hashdiff (1.0.1)
hashery (2.1.2)
hashie (4.1.0)
hexapdf (0.9.3)
cmdparse (~> 3.0, >= 3.0.3)
geom2d (~> 0.2)
@@ -298,7 +297,6 @@ GEM
money (~> 6.13.2)
railties (>= 3.0)
msgpack (1.3.1)
multi_json (1.15.0)
multi_xml (0.6.0)
multipart-post (2.1.1)
nio4r (2.5.2)
@@ -310,21 +308,6 @@ GEM
warden
oath-generators (1.0.1)
oath (>= 0.0.12)
oauth2 (1.4.4)
faraday (>= 0.8, < 2.0)
jwt (>= 1.0, < 3.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
omniauth (1.9.1)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
omniauth-oauth2 (1.6.0)
oauth2 (~> 1.1)
omniauth (~> 1.9)
omniauth-rails_csrf_protection (0.1.2)
actionpack (>= 4.2)
omniauth (>= 1.3.1)
parallel (1.19.1)
parity (3.2.0)
parser (2.6.5.0)
@@ -569,8 +552,6 @@ DEPENDENCIES
mux_ruby!
oath (~> 1.1.0)
oath-generators (~> 1.0.1)
omniauth-oauth2 (~> 1.6)
omniauth-rails_csrf_protection (~> 0.1.2)
parity (~> 3.2.0)
pdf-reader (~> 2.1.0)
pdfkit (~> 0.8.2)

View File

@@ -24,6 +24,8 @@ $(document).on "turbolinks:load", ->
stream_selected = $("#broadcast_video").data('videoType') == 'stream';
if data.streamer_status == 'recording' && data.status == 'active' && stream_selected
$("#broadcast_video").html data.video_content
$("#live_take").html data.live_take_content
new (Clappr.Player)(
<%= "baseUrl: 'http://cdn.clappr.io/latest'," if Rails.env.test? %>
parentId: '#broadcast_video'
@@ -35,6 +37,7 @@ $(document).on "turbolinks:load", ->
hlsMinimumDvrSize: 1)
if data.streamer_status == "idle" && data.status == "idle"
$("#broadcast_video").html data.video_content
$("#live_take").html data.live_take_content
showBroadcastRecordings: (data) ->
$(".flash-message").html data.flash_content

View File

@@ -0,0 +1,20 @@
$(document).on("click", "#download_releases", function(event) {
event.preventDefault();
const releasable_ids = JSON.parse($("#selected_releases_form").attr('data-releasable-ids'));
const total_entries = $('#total_entries').val();
const input_ids = $('<input>').attr({ type: 'hidden', name: 'release_ids', value: JSON.stringify(releasable_ids) });
const search_query = $('<input>').attr({ type: 'hidden', name: 'search_query', value: $("form input[type='search']").val() });
const type_filter = $('<input>').attr({ type: 'hidden', name: 'type_filter', value: $('#type_filter_value').val() });
const download_count = releasable_ids.length > 0 ? releasable_ids.length : total_entries;
$(this).parent().append(input_ids);
$(this).parent().append(search_query);
$(this).parent().append(type_filter);
if (confirm(`${download_count} release(s) will be downloaded. Is this correct?`)){
Rails.fire($(this).parent()[0], 'submit');
}
});

View File

@@ -1,8 +1,4 @@
$(document).on("click", "[data-behavior=play_recording]", function() {
if ($(this).hasClass('active')){
return false;
}
$("#broadcast_video").data('videoType', 'recording');
var playback_url = $(this).attr("data-playback-url")
@@ -16,13 +12,6 @@ $(document).on("click", "[data-behavior=play_recording]", function() {
height: '100%',
autoPlay: true
});
$(".dropdown-menu").children().removeClass('active');
$(".dropdown-menu").children().children('i').remove();
$(this).siblings().removeClass('active');
$(this).siblings().children("i").remove();
$(this).addClass('active');
$(this).prepend('<i class="fa fa-check">&nbsp;</i>');
});
$(document).on("click", "[data-behavior=play_stream]", function() { $("#broadcast_video").data('videoType', 'stream'); });

View File

@@ -0,0 +1,4 @@
$(document).on("click", "#director_mode_switch", function() {
// JQuery click event trigger was not working.
document.getElementById("director_mode_link").click();
});

View File

@@ -1,4 +1,30 @@
// Do not allow file attachments in rich text content
addEventListener("trix-file-accept", function(event) {
event.preventDefault();
})
});
Trix.config.textAttributes.underline = {
style: { "textDecoration": "underline" },
inheritable: true,
parser: function (element) {
var style = window.getComputedStyle(element);
return style.textDecoration === "underline";
}
}
document.addEventListener('trix-initialize', function (e) {
const trix = e.target;
const toolBar = trix.toolbarElement;
// // Creation of the button
const button = document.createElement("button");
button.setAttribute("type", "button");
button.setAttribute("class", "trix-button trix-button--icon trix-button--icon-underline");
button.setAttribute("data-trix-attribute", "underline");
button.setAttribute("title", "underline");
button.setAttribute("tabindex", "-1");
button.innerText = "U";
// Attachment of the button to the toolBar
toolBar.querySelector('.trix-button-group--text-tools').appendChild(button);
});

View File

@@ -412,6 +412,16 @@ a[data-behavior=seekable-timecode] {
background-color: rgba($black, 0.05);
}
// Black background
.bg-black {
background-color: $black;
}
// White background
.bg-white {
background-color: $white;
}
// Custom width
.w-65 {
width: 65% !important;
@@ -422,8 +432,39 @@ a[data-behavior=seekable-timecode] {
max-height: 30rem;
}
// Max-width 75%
.max-w-75 {
max-width: 75%;
}
// Fix height and width
.fix-h-and-w {
width: 308px;
height:308px;
}
// Dropdown shown state overrride
.override-dropdown-show-state {
color: $white !important;
background-color: $black !important;
border-color: transparent !important;
}
// Override custom switch color
.override-custom-control-label::before {
background-color: #000000;
}
// Override nav tab design
.override-nav-tabs .nav-item.show .nav-link, .nav-tabs .nav-link.active {
background: transparent;
border-color: transparent;
border-bottom: 3px solid #ff0000;
}
//Trix underline style
trix-toolbar {
.trix-button--icon-underline::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z'/%3E%3C/svg%3E");
}
}

View File

@@ -48,6 +48,10 @@ u {
margin-right: -30px;
}
.embed-person-photo {
text-align: right;
}
.do-not-copy-warning {
padding-right: 15px;
}

View File

@@ -11,6 +11,7 @@ class BroadcastsChannel < ApplicationCable::Channel
def self.broadcast_stream_updates(broadcast)
status_content = ApplicationController.render partial: "broadcasts/broadcast_status", locals: { broadcast: broadcast }
video_content = ApplicationController.render partial: "broadcasts/video", locals: { broadcast: broadcast }
live_take = ApplicationController.render partial: "broadcasts/live_take", locals: { broadcast: broadcast }
broadcast_to broadcast, {
event: :broadcast_stream_update,
@@ -18,6 +19,7 @@ class BroadcastsChannel < ApplicationCable::Channel
playback_url: broadcast.stream_playback_url,
full_live_stream_playback_url: broadcast.full_live_stream_playback_url,
video_content: video_content,
live_take_content: live_take,
status_content: status_content,
streamer_status: broadcast.streamer_status
}

View File

@@ -1,4 +1,5 @@
class AccountSessionsController < ApplicationController
skip_before_action :redirect_locked_accounts
def update
authorize :account_session, :update?
session[:active_account] = account_session_params[:account_id]

View File

@@ -0,0 +1,31 @@
class Admin::AccountLocksController < Admin::ApplicationController
before_action :set_account
def create
authorize :account_lock, :create?
@account.update(locked: true)
redirect_to admin_accounts_path, notice: 'Account locked'
end
def destroy
authorize :account_lock, :destroy?
@account.update(locked: false)
redirect_to admin_accounts_path, notice: 'Account unlocked'
end
private
def set_account
if params[:account_id].present?
@account = Account.find_by(slug: params[:account_id])
else
failure_redirect
end
rescue ActiveRecord::RecordNotFound
failure_redirect
end
def failure_redirect
redirect_to admin_accounts_path, alert: 'Failed to find the account'
end
end

View File

@@ -45,13 +45,14 @@ class Api::ReleasesController < Api::ApiController
if model_name == "acquired_media_release"
mapping = {
"#{model_name.camelize}": SerializableAcquiredMediaRelease,
FileInfo: SerializableFileInfo
FileInfo: SerializableFileInfo,
"ActiveStorage::Attachment".to_sym => ActiveStorage::SerializableAttachment,
}
render jsonapi: release,
status: status,
class: mapping,
include: [:file_infos]
include: [:files, :file_infos]
else
mapping = {
"#{model_name.camelize}": show_serializable,

View File

@@ -4,6 +4,7 @@ require './lib/knock_monkeypatch'
class Api::UserTokenController < Knock::AuthTokenController
include Oath::ControllerHelpers
include RememberMe::Controller
skip_before_action :verify_authenticity_token
before_action :sign_in_user

View File

@@ -13,6 +13,7 @@ class ApplicationController < ActionController::Base
include SetCurrentRequestDetails
before_action :redirect_accountless
before_action :redirect_locked_accounts
private
@@ -29,6 +30,12 @@ class ApplicationController < ActionController::Base
end
end
def redirect_locked_accounts
if Current.user && !Current.user.admin? && Current.account.present? && Current.account.locked?
redirect_to locked_account_path
end
end
def signed_in_as_admin?
signed_in? && current_user.admin?
end

View File

@@ -6,9 +6,12 @@ class ApprovalsController < ApplicationController
def create
@releasable.approve_by(current_user)
@releasable.approved_by_user_signature.attach(approved_by_user_signature_params) if signature_data.present?
if @releasable.save
if @releasable.save(context: :approval)
redirect_to [@project, "#{@releasable_param.name.pluralize}"], notice: t('.release_approved', release_type: @releasable.model_name.human)
else
render :new
end
end
@@ -25,4 +28,21 @@ class ApprovalsController < ApplicationController
def set_project
@project = @releasable.project
end
def releasable_params
params.require(releasable_param.name).permit(approved_by_user_signature: :data)
end
def signature_data
releasable_params.dig(:approved_by_user_signature, :data)
end
def approved_by_user_signature_params
{
data: signature_data,
filename: "approved_by_user_signature.png",
content_type: "image/png",
identify: false,
}
end
end

View File

@@ -0,0 +1,34 @@
class BroadcastRecordingStarringsController < ApplicationController
layout "project"
before_action :set_project
before_action :set_broadcast
before_action :set_recording
def create
@recording.toggle_star
set_recordings
end
private
def broadcast_recording_params
params.require(:broadcast_recording).permit(:name, :description)
end
def set_project
@project = policy_scope(Project).find(params[:project_id])
end
def set_broadcast
@broadcast = authorize policy_scope(@project.broadcasts).find(params[:broadcast_id])
end
def set_recording
@recording = authorize policy_scope(@broadcast.broadcast_recordings).find(params[:broadcast_recording_id])
end
def set_recordings
@recordings = @broadcast.broadcast_recordings.visible.order_by_recent.paginate(page: params[:page])
end
end

View File

@@ -5,13 +5,25 @@ class BroadcastRecordingsController < ApplicationController
before_action :set_broadcast
before_action :set_recording
def edit
end
def update
@recording.update(broadcast_recording_params)
set_recordings
end
def destroy
@recording.update(hidden: true)
@recordings = @broadcast.broadcast_recordings.visible.order_by_recent.paginate(page: params[:page])
set_recordings
end
private
def broadcast_recording_params
params.require(:broadcast_recording).permit(:name, :description)
end
def set_project
@project = policy_scope(Project).find(params[:project_id])
end
@@ -23,4 +35,8 @@ class BroadcastRecordingsController < ApplicationController
def set_recording
@recording = authorize policy_scope(@broadcast.broadcast_recordings).find(params[:id])
end
def set_recordings
@recordings = @broadcast.broadcast_recordings.visible.order_by_recent.paginate(page: params[:page])
end
end

View File

@@ -32,6 +32,7 @@ class BroadcastsController < ApplicationController
@conference_url = conference_url_for(@broadcast)
@recordings = @broadcast.broadcast_recordings.visible.order_by_recent.paginate(page: params[:page])
@files = @broadcast.files.order("created_at DESC").paginate(page: params[:files_page])
render layout: 'application'
end
@@ -75,7 +76,7 @@ class BroadcastsController < ApplicationController
end
def broadcast_params
params.require(:broadcast).permit(:name, :shoot_location_time_zone, :conference_option, files: [])
params.require(:broadcast).permit(:name, :shoot_location_time_zone, files: [])
end
def set_project
@@ -113,7 +114,7 @@ class BroadcastsController < ApplicationController
end
def conference_url_for(broadcast)
broadcast.video_conference_url_override || url_for([broadcast.project, broadcast, :conference_meeting])
broadcast.video_conference_url_override.presence || url_for([broadcast.project, broadcast, :zoom_meeting])
end
def log_create_analytics

View File

@@ -1,22 +0,0 @@
class CallbacksController < ApplicationController
skip_before_action :require_login
skip_after_action :verify_authorized, except: :index
skip_after_action :verify_policy_scoped, only: :index
skip_before_action :verify_authenticity_token
def create
uid = request.env['omniauth.auth'][:uid]
token_data = request.env['omniauth.auth'][:credentials]
current_user&.tap do |user|
user.microsoft_user_id = uid
user.microsoft_access_token = token_data.token
user.microsoft_refresh_token = token_data.refresh_token
user.microsoft_token_expires_at = token_data.expires_at # Expiration time is returned in seconds
user.save
end
redirect_to profile_path
end
end

View File

@@ -1,45 +0,0 @@
class ConferenceMeetingsController < ApplicationController
require 'microsoft_graph'
def show
authorize broadcast = Broadcast.find(params[:broadcast_id])
case broadcast.conference_option
when 'zoom'
redirect_to broadcast.zoom_meeting_url
when 'ms_teams'
if broadcast.conference_join_url.nil?
begin
graph_api = MicrosoftGraph.new(
current_user,
ENV['AZURE_CLIENT_ID'],
ENV['AZURE_CLIENT_SECRET'],
ENV['AZURE_TENANT_ID'],
ENV['AZURE_SCOPES']
)
subject = "#{broadcast.name} Online Meeting"
teams_meeting = graph_api.create_teams_meeting(subject)
join_url = teams_meeting['joinUrl']
if join_url.present?
broadcast.conference_join_url = join_url
broadcast.save
else
raise StandardError, 'Failed to read teams meeting join URL'
end
rescue ActionController::InvalidAuthenticityToken => e
Rails.logger.error(e.message)
redirect_to project_broadcast_url(broadcast.project, broadcast), alert: t('.alerts.not_authenticated')
return
rescue StandardError => e
Rails.logger.error(e.message)
redirect_to project_broadcast_url(broadcast.project, broadcast), alert: t('.alerts.failed_to_join')
return
end
end
redirect_to broadcast.conference_join_url
else
redirect_to project_broadcast_url(broadcast.project, broadcast), alert: t('.alerts.unknown_conference_option')
end
end
end

View File

@@ -7,28 +7,41 @@ class ContractDownloadsController < ApplicationController
def create
authorize policy_scope(Download).create
fetch_releases
download = @project.downloads.create!(release_type: params[:release_type])
download = @project.downloads.create!(release_type: release_type)
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: params[:release_type] }, :layout => false
in_progress_downloads_details = render_to_string "_other_pending_downloads", locals: { downloads: other_downloads_in_progress, release_type: release_type }, :layout => false
ProjectsChannel.broadcast_download_generation_update(download, in_progress_downloads_details)
else
ProjectsChannel.broadcast_download_generation_update(download, I18n.t("contract_downloads.download.pending", release_type: params[:release_type].titleize))
ProjectsChannel.broadcast_download_generation_update(download, I18n.t("contract_downloads.download.pending", release_type: release_type.titleize))
end
GenerateContractsZipJob.perform_later(@project, download, params[:release_type], @releases.ids)
GenerateContractsZipJob.perform_later(@project, download, release_type, release_ids, search_query, type_filter)
end
private
def fetch_releases
@releases = policy_scope(@project.public_send(releases))
def release_type
params[:release_type]
end
def releases
params[:release_type].constantize.model_name.plural
def search_query
params[:search_query]
end
def type_filter
params[:type_filter]
end
def release_ids
JSON.parse(params[:release_ids])
rescue StandardError
[]
end
def release_name(release_type)
release_type.constantize.model_name.plural
end
end

View File

@@ -3,7 +3,7 @@ class ContractsController < ApplicationController
respond_to do |format|
format.pdf { send_contract_pdf }
if Rails.env.development?
if Rails.env.development? || Rails.env.test?
format.html { render_sample_html }
end
end

View File

@@ -0,0 +1,10 @@
class LockedAccountsController < ApplicationController
skip_before_action :redirect_locked_accounts
skip_after_action :verify_policy_scoped
def index
unless Current.account.locked?
redirect_to projects_path
end
end
end

View File

@@ -106,7 +106,7 @@ class MaterialReleasesController < ApplicationController
:term_id, :term_text,
:restriction_id, :restriction_text,
:description,
:contract, { photos: [] }
:contract, files: []
)
end

View File

@@ -4,4 +4,16 @@ class PagesController < ApplicationController
skip_after_action :verify_authorized
skip_after_action :verify_policy_scoped
skip_before_action :require_login
layout :layout_for_page
private
def layout_for_page
case params[:id]
when 'nanocosmos_player'
false
else
end
end
end

View File

@@ -44,7 +44,7 @@ class Public::BroadcastsController < Public::BaseController
end
def conference_url_for(broadcast)
broadcast.video_conference_url_override || broadcast_zoom_meeting_url(broadcast.token)
broadcast.video_conference_url_override.presence || broadcast_zoom_meeting_url(broadcast.token)
end
class MultiViewBroadcast

View File

@@ -92,7 +92,7 @@ class Public::MaterialReleasesController < Public::BaseController
params.require(:material_release).permit(person_params, guardian_params, second_guardian_params, :minor,
:name, :address_street1, :address_street2, :address_city, :address_state, :address_zip, :address_country,
:signature_base64,
:locale, :contract_template, :description, photos: []
:locale, :contract_template, :description, files: []
)
end

View File

@@ -0,0 +1,6 @@
class ZoomMeetingsController < ApplicationController
def show
authorize broadcast = Broadcast.find(params[:broadcast_id])
redirect_to broadcast.zoom_meeting_url
end
end

View File

@@ -1,17 +0,0 @@
module BroadcastConferencesHelper
def options_for_conference_select
[
['Zoom', 'zoom'],
['MS Teams', 'ms_teams']
]
end
def conference_option_name_from_key(key)
option = options_for_conference_select.find { |option| option[1] == key }
if option.present?
option.first
else
'Unknown conference option'
end
end
end

View File

@@ -17,6 +17,7 @@ module TagsHelper
disable_with: disabled_content,
},
form: {
id: "selected_releases_form",
data: {
releasable_ids: [],
},

View File

@@ -7,13 +7,14 @@ class GenerateContractsZipJob < ApplicationJob
@project = job.arguments.first
@download = job.arguments.second
@release_type = job.arguments.third
@folder_name = "#{@project.name.parameterize}_#{get_release_name(@release_type).gsub('_', '-')}"
@release_ids = job.arguments.fourth
@search_query = job.arguments.fifth
@type_filter = job.arguments[5]
@folder_name = "#{@project.name.parameterize}_#{release_name.gsub('_', '-')}"
@download.update!(name: @folder_name, status: :pending)
end
def perform(project, download, release_type, release_ids)
releases = project.public_send(get_release_name(release_type)).where(id: release_ids)
def perform(project, download, release_type, release_ids, search_query, type_filter)
::ReleaseContractCollectionService.new(releases, @folder_name).build do |dir, files|
zipfile_name = "#{dir}/#{@folder_name}.zip"
Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile|
@@ -31,7 +32,7 @@ class GenerateContractsZipJob < ApplicationJob
end
rescue StandardError => e
Rails.logger.error("Failed to generate download for project (##{project.id}) with release type #{release_type}\n" + e.message)
@download.failure!
ProjectsChannel.broadcast_download_generation_update(@download, I18n.t("contract_downloads.download.failure"))
end
@@ -61,7 +62,32 @@ class GenerateContractsZipJob < ApplicationJob
end
end
def get_release_name(release_type)
release_type.constantize.model_name.plural
def release_name
@release_type.constantize.model_name.plural
end
def all_releases
@project.public_send(release_name)
end
def releases
if @release_ids.any?
return all_releases.where(id: @release_ids)
end
results = all_releases
if all_releases.respond_to?(:complete, :incomplete)
results = case @type_filter
when 'complete'
all_releases.complete
when 'incomplete'
all_releases.incomplete
else
all_releases
end
end
results = results.search(@search_query) if @search_query.present?
results
end
end

View File

@@ -15,7 +15,8 @@ class AcquiredMediaRelease < ApplicationRecord
include SecondGuardianPhotoable
include GuardianName
include SecondGuardianName
include FilesFilterable
class << self
def custom_csv_exportable_headers
%i[name files_count owner_info]
@@ -106,16 +107,4 @@ class AcquiredMediaRelease < ApplicationRecord
def files_count
files.any? ? files.size : I18n.t('acquired_media_releases.acquired_media_release.no_media')
end
def image_files
files_blobs.where("content_type ILIKE ?", "%image%")
end
def video_files
files_blobs.where("content_type ILIKE ?", "%video%")
end
def other_files
files_blobs.where("NOT content_type ILIKE ANY (array[?])", ["%image%", "%video%"])
end
end

View File

@@ -7,8 +7,10 @@ class BroadcastRecording < ApplicationRecord
scope :visible, -> { where(hidden: false) }
before_save :set_title_and_description
def download_url
"https://stream.mux.com/#{asset_playback_uid}/#{file_name}?download=#{download_file_name}"
"https://stream.mux.com/#{asset_playback_uid}/#{file_name}?download=#{name}"
end
def playback_url
@@ -16,6 +18,21 @@ class BroadcastRecording < ApplicationRecord
end
def download_file_name
"#{broadcast_name}_Date_#{created_at.in_time_zone(broadcast.shoot_location_time_zone).strftime("%Y-%m-%d")}_Time_#{created_at.in_time_zone(broadcast.shoot_location_time_zone).strftime("%T")}".parameterize
"#{broadcast_name}_Date_#{Time.now.in_time_zone(broadcast.shoot_location_time_zone).strftime("%Y-%m-%d")}_Time_#{Time.now.in_time_zone(broadcast.shoot_location_time_zone).strftime("%T")}".parameterize
end
def toggle_star
toggle! :starred
end
def thumbnail_url(width = 300)
"https://image.mux.com/#{asset_playback_uid}/thumbnail.jpg?width=#{width}"
end
private
def set_title_and_description
self.name ||= download_file_name
self.description ||= "No description provided for this recording."
end
end

View File

@@ -1,15 +1,24 @@
module Approvable
extend ActiveSupport::Concern
included do
include ActiveStorageSupport::SupportForBase64
has_one_base64_attached :approved_by_user_signature
# Requires signature when saving in the approval context
with_options on: :approval do
validates :approved_by_user_signature, attached: true
end
def approve_by(user)
return unless approved_at.nil?
self.approved_by_user_name = user.full_name
self.approved_by_user_email = user.email
self.approved_at = Time.zone.now
self.approved_at = BigMediaTime.time_zone_now
end
def approved?
self.approved_at.present?
end

View File

@@ -0,0 +1,17 @@
module FilesFilterable
extend ActiveSupport::Concern
included do
def image_files
files_blobs.where("content_type ILIKE ?", "%image%")
end
def video_files
files_blobs.where("content_type ILIKE ?", "%video%")
end
def other_files
files_blobs.where("NOT content_type ILIKE ANY (array[?])", ["%image%", "%video%"])
end
end
end

View File

@@ -3,7 +3,7 @@ class MaterialRelease < ApplicationRecord
include Contractable
include Exploitable
include Notable
include Photoable
include Photoable # This association needs to be removed after changing the API. Removing it right now will cause failure in API specs.
include Releasable
include Searchable
include Signable
@@ -16,11 +16,11 @@ class MaterialRelease < ApplicationRecord
include SecondGuardianPhotoable
include GuardianName
include SecondGuardianName
include FilesFilterable
class << self
def custom_csv_exportable_headers
%i[name owner_info]
%i[name owner_info files_count]
end
end
@@ -56,6 +56,8 @@ class MaterialRelease < ApplicationRecord
%w[guardian_2_address_zip zip],
%w[guardian_2_address_country country]
]
has_many_attached :files
# We don't care for the argument but method WILL receive option name
# when called from inside with_option block, hence * argument
@@ -92,4 +94,8 @@ class MaterialRelease < ApplicationRecord
def uses_edl?
true
end
def files_count
files.any? ? files.size : I18n.t('material_releases.material_release.no_media')
end
end

View File

@@ -0,0 +1,9 @@
class AccountLockPolicy < ApplicationPolicy
def create?
user.admin?
end
def destroy?
user.admin?
end
end

View File

@@ -1,9 +1,21 @@
class BroadcastRecordingPolicy < ApplicationPolicy
def create?
true
end
def destroy?
if user.nil? || user.user.nil?
return false
end
user.manager? || user.account_manager?
end
def edit?
true
end
def update?
true
end
end

View File

@@ -19,12 +19,16 @@ class MaterialReleasePolicy < ReleasePolicy
user.manager? || user.account_manager?
end
def edit_photos?
def edit_files?
true
end
def update_files?
edit_files?
end
def update_photos?
edit_photos?
edit_files?
end
def tag_multiple?

View File

@@ -7,7 +7,7 @@ class SerializableAcquiredMediaRelease < JSONAPI::Serializable::Resource
:person_title, :person_company, :created_at, :updated_at, :collection_uid, :territory_old, :term_old,
:applicable_medium_id, :applicable_medium_text, :territory_id, :territory_text, :term_id, :term_text,
:restriction_id, :restriction_text, :categories, :description, :tag_list
has_many :file_infos do
data do
@object.file_infos
@@ -17,4 +17,14 @@ class SerializableAcquiredMediaRelease < JSONAPI::Serializable::Resource
{ count: @object.file_infos.size }
end
end
has_many :files do
data do
@object.files
end
meta do
{ count: @object.files.size }
end
end
end

View File

@@ -1,5 +1,9 @@
class SerializableUser < JSONAPI::Serializable::Resource
type "user"
attributes :email
attributes :email, :full_name
attribute :company_name do
@object.primary_account.name
end
end

View File

@@ -24,7 +24,7 @@ class ReleaseContractCollectionService
end
files = Dir.entries("#{dir}/").select { |f| !File.directory? f }
raise StandardError.new "Contracts or Contract Templates not found." unless files.any?
# raise StandardError.new "Contracts or Contract Templates not found." unless files.any?
yield(dir, files)
}
end

View File

@@ -30,6 +30,11 @@
<%= link_to fa_icon("arrow-right", text: "Overview"), admin_account_path(account), class: "dropdown-item" %>
<%= link_to fa_icon("pencil", text: "Edit"), edit_admin_account_path(account), class: "dropdown-item" %>
<%= link_to fa_icon("arrow-right", text: "Account Managers"), account_auths_path({ account_id: account.id}), class: "dropdown-item" %>
<% if account.locked? %>
<%= link_to fa_icon("unlock", text: "Unlock Account"), [:admin, account, :lock], method: :delete, class: "dropdown-item" %>
<% else %>
<%= link_to fa_icon("lock", text: "Lock Account"), [:admin, account, :lock], method: :post, class: "dropdown-item" %>
<% end %>
</div>
</div>
</td>

View File

@@ -1,5 +1,6 @@
<div class="row">
<div class="col-md-12">
<input id="total_entries" type=hidden value=<%= @appearance_releases.total_entries %> />
<div id="upload-progress-container" class="mb-1"></div>
<div class="d-md-flex d-sm-flex flex-sm-column flex-md-row flex-md-wrap mb-2">
<% if policy(AppearanceRelease).new? %>
@@ -16,7 +17,7 @@
<% end %>
<% if @appearance_releases.any? && policy(AppearanceRelease).download_multiple? %>
<%= link_to "Download All", [@project, :contract_downloads, release_type: @appearance_releases.name], method: :post, remote: true, class: "btn btn-light border mr-2 mb-2", data: { disable_with: "Please wait..." } %>
<%= button_to "Download", [@project, :contract_downloads, release_type: @appearance_releases.name], id: "download_releases", method: :post, remote: true, class: "btn btn-light border mr-2 mb-2", data: { disable_with: "Please wait..." } %>
<% end %>
</div>
</div>

View File

@@ -3,3 +3,5 @@ $("form input[type='search']").val("<%= params[:query] %>");
$("#type_filter_actions").html("<%= j render 'type_filter_actions' %>");
$("#appearance_releases_pagination").html("<%= j will_paginate(@appearance_releases) %>");
$('#type_filter_value').val("<%= params[:type_filter] %>");
$("#selected_releases_form").attr('data-releasable-ids', JSON.stringify([]));
$("#total_entries").val(<%= @appearance_releases.total_entries %>);

View File

@@ -1,9 +1,13 @@
<div class="card shadow-sm">
<%= card_header text: t(".heading", release_type: @releasable_param.name.titleize), close_action_path: [@project, "#{@releasable_param.name.pluralize}"] %>
<div class="card-body">
<embed class="embeded-contract-preview" type="application/pdf" src="<%= url_for [@releasable, :contracts, format: "pdf"] %>" width="90%" height="1200" />
<embed class="embeded-contract-preview mb-3" type="application/pdf" src="<%= url_for [@releasable, :contracts, format: "pdf"] %>" width="90%" height="1200" />
<%= errors_summary_for @releasable %>
<%= bootstrap_form_with model: @releasable, method: :post, url: public_send("#{@releasable_param.name}_approvals_path", @releasable), local: true do |form| %>
<%= card_field_set_tag 'Signature' do %>
<%= render "shared/signature_fields", form: form, signature_field: 'approved_by_user_signature[data]' %>
<% end %>
<div class="row align-items-center text-center mt-4">
<%= link_to t("shared.cancel"), [@releasable.project, "#{@releasable_param.name.pluralize}"], class: "col-3 text-reset" %>
<div class="col-9">
@@ -12,4 +16,4 @@
</div>
<% end %>
</div>
</div>
</div>

View File

@@ -0,0 +1 @@
$("#broadcast_recordings").html("<%= j render(partial: 'broadcasts/broadcast_recordings', locals: { recordings: @recordings, broadcast: @broadcast }) %>");

View File

@@ -0,0 +1,24 @@
<%= content_tag :div, class: "modal modal-right", id: "edit_broadcast_recording_modal", aria: { labelledby: "modalLabel", hidden: true }, role: "dialog", tabindex: -1 do %>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalLabel">Edit Broadcast Recording</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<%= bootstrap_form_with model: [broadcast.project, broadcast, recording], layout: :horizontal, label_col: "col-3", control_col: "col-9" do |form| %>
<div class="modal-body">
<div id="broadcast_recording_fields">
<%= form.text_field :name %>
<%= form.text_area :description %>
</div>
</div>
<div class="modal-footer">
<%= form.button "Close", class: "btn btn-secondary", data: { dismiss: "modal" } %>
<%= form.submit "Update Broadcast Recording", class: "btn btn-primary", data: { disable_with: t("shared.disable_with") } %>
</div>
<% end %>
</div>
</div>
<% end %>

View File

@@ -0,0 +1,11 @@
$('[data-id="<%= dom_id(@recording) %>"]').remove();
<% if @recordings.empty? %>
$("#broadcast_recordings_nav").append('<p class="dropdown-item text-muted">Recordings will appear here</p>')
<% end %>
$("#broadcast_recordings").html("<%= j render(partial: 'broadcasts/broadcast_recordings', locals: { recordings: @recordings, broadcast: @broadcast }) %>");
// Close and remove the modal
$("#edit_broadcast_recording_modal").on("hidden.bs.modal", function (e) {
$("#edit_broadcast_recording_modal").remove();
});
$("#edit_broadcast_recording_modal").modal("hide");

View File

@@ -1,6 +1 @@
var dom_id = "<%= dom_id(@recording) %>"
$('[data-id="' + dom_id + '"]').remove();
<% if @recordings.empty? %>
$("#broadcast_recordings_nav").append('<p class="dropdown-item text-muted">Recordings will appear here</p>')
<% end %>
$("#broadcast_recordings").html("<%= j render(partial: 'broadcasts/broadcast_recordings', locals: { recordings: @recordings, broadcast: @broadcast }) %>");
<%= render("broadcast_recordings/refresh_recordings_list") %>

View File

@@ -0,0 +1,6 @@
<% # Remove the modal if it already exists %>
$("#edit_broadcast_recording_modal").remove();
<% # Create and show the modal %>
$("body").append("<%= j render(partial: 'edit_broadcast_recording_modal', locals: { project: @project, broadcast: @broadcast, recording: @recording }) %>");
$("#edit_broadcast_recording_modal").modal("toggle");

View File

@@ -0,0 +1 @@
<%= render("broadcast_recordings/refresh_recordings_list") %>

View File

@@ -1,3 +1 @@
<%= link_to broadcast_recording.download_file_name, "javascript:void(0);", class: "dropdown-item", data: { behavior: "play_recording", playback_url: broadcast_recording.playback_url, id: dom_id(broadcast_recording) } %>
<%= link_to broadcast_recording.name, "javascript:void(0);", class: "dropdown-item", data: { behavior: "play_recording", playback_url: broadcast_recording.playback_url, id: dom_id(broadcast_recording) } %>

View File

@@ -1,18 +1,29 @@
<% if recordings.present? %>
<p>Click below to download the recordings of the live stream.</p>
<ul class="mt-2">
<div class="list-group">
<% recordings.each do |recording| %>
<li>
<%= link_to(recording.download_file_name, recording.download_url, target: "_blank") %>
<% if (controller.class.module_parent.to_s != "Public" && policy(BroadcastRecording).destroy?) %>
<%= link_to "Hide", [broadcast.project, broadcast, recording], class: "btn-sm btn-primary ml-1 text-decoration-none", remote: true, method: :delete, data: { confirm: t('.confirm_hide') } %>
<% end %>
</li>
<div class="list-group-item list-group-item-action">
<div class="d-flex align-items-start">
<% if policy(BroadcastRecording).update? %>
<%= link_to fa_icon("#{recording.starred ? 'star' : 'star-o'} fw"), [broadcast.project, broadcast, recording, :broadcast_recording_starrings], method: :post, class: "text-warning mr-3", remote: true %>
<% end %>
<%= image_tag(recording.thumbnail_url, class: 'img-thumbnail img-fluid max-w-75', data: { behavior: "play_recording", playback_url: recording.playback_url, id: dom_id(recording) }) %>
<div class="ml-auto">
<% if policy(BroadcastRecording).edit? %>
<%= link_to fa_icon('edit'), [:edit, broadcast.project, broadcast, recording], remote: true %>
<% end %>
<%= link_to(fa_icon('download'), recording.download_url, target: "_blank") %>
</div>
</div>
<div class="d-flex flex-column align-items-start justify-content-start p-4">
<h5><%= recording.name %></h5>
<p><%= recording.description %></p>
</div>
</div>
<% end %>
</ul>
</div>
<div id="recordings_pagination" class="row mt-5 justify-content-center">
<%= will_paginate(recordings, params: {controller: "broadcasts", action: "show", project_id: broadcast.project_id, id: broadcast.id, page: params[:page], active_tab: 'recordings'}) %>
</div>
<% else %>
<p>Recording of the live stream will appear here.</p>
<p class="mt-4">Recording of the live stream will appear here.</p>
<% end %>

View File

@@ -1,21 +1,13 @@
<% if broadcast.streamer_connected? || (broadcast.streamer_recording? && !broadcast.active?) %>
<div class="alert alert-info text-center text-md-left">
<%= fa_icon "info-circle" %>
<strong>Live stream has connected successfully and will be available soon.</strong>
</div>
<p class="mb-1">Live stream has connected successfully and will be available soon.</p>
<div class="badge badge-pill badge-success mb-2 text-uppercase">Connected</div>
<% elsif broadcast.streamer_recording? && broadcast.active? %>
<div class="alert alert-success text-center text-md-left">
<%= fa_icon "success" %>
<strong>Live stream has begun, click play to watch it.</strong>
</div>
<p class="mb-1">Live stream has begun, click play to watch it.</p>
<div class="badge badge-pill badge-danger mb-2 text-uppercase">Live</div>
<% elsif broadcast.streamer_disconnected? %>
<div class="alert alert-warning text-center text-md-left">
<%= fa_icon "warning" %>
<strong>Live stream got disconnected.</strong>
</div>
<p class="mb-1">Live stream got disconnected.</p>
<div class="badge badge-pill badge-warning mb-2 text-uppercase">Disconnected</div>
<% elsif (broadcast.idle? && broadcast.streamer_idle?) || (broadcast.created? && broadcast.streamer_idle?) %>
<div class="alert alert-info text-center text-md-left">
<%= fa_icon "info-circle" %>
<strong>Live stream is waiting to begin.</strong>
</div>
<% end %>
<p class="mb-1">Live stream is waiting to begin.</p>
<div class="badge badge-pill badge-primary mb-2 text-uppercase">Idle</div>
<% end %>

View File

@@ -2,7 +2,6 @@
<%= bootstrap_form_with model: model, local: true do |form| %>
<%= form.text_field :name %>
<%= form.select :conference_option, options_for_conference_select, { label: t('.labels.conference_option') }, class: "form-control custom-select" %>
<%= form.time_zone_select(:shoot_location_time_zone, nil, label: "Time zone of shoot location") %>
<div class="row align-items-center text-center mt-4">

View File

@@ -0,0 +1,12 @@
<div class="list-group">
<div class="list-group-item list-group-item-action flex-column align-items-start">
<div class="d-flex w-100 justify-content-between mb-1">
<h5 class="mb-1"><%= broadcast.name %></h5>
<small>Created - <%= time_ago_in_words(broadcast.created_at) + " ago" %></small>
</div>
<div id="broadcast_updates">
<%= render partial: 'broadcasts/broadcast_status', locals: { broadcast: broadcast } %>
</div>
</div>
</div>

View File

@@ -9,7 +9,7 @@
<table class="w-100 h-100 bg-secondary">
<tbody>
<tr>
<td class="text-center align-middle text-white">
<td class="text-center align-middle text-white bg-black">
Video player will appear here when the stream becomes available.
</td>
</tr>

View File

@@ -14,159 +14,155 @@
<% end %>
<% content_for :header do %>
<header class="container-fluid py-3 border-bottom sticky-top bg-light">
<div class="row align-items-center justify-content-center">
<div class="col-4 text-center">
<%= product_wordmark(:direct_me, class: 'navbar-brand') %>
</div>
</div>
</header>
<header></header>
<% end %>
<div class="row">
<div class="col-lg-8 col-md-12 mb-3">
<div class="card shadow-sm">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h1 class="h3 m-0"><%= @broadcast.name %></h1>
<div class="dropdown">
<%= link_to "Switch View", "#", class: "btn btn-light border dropdown-toggle", role: "button", id: "dropdownMenuLink", data: { toggle: "dropdown" }, aria: { haspopup: "true", expanded: "false" } %>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<h5 class="dropdown-header">Live Streams</h5>
<%= link_to fa_icon("check", text: @broadcast.name.titleize), "#", data: { behavior: "play_stream"}, class: "dropdown-item active" %>
<% @multi_view_broadcasts.each do |broadcast| %>
<% if broadcast.id != @broadcast.id %>
<%= link_to broadcast.name.titleize, broadcast.url, data: { behavior: "play_stream"}, class: class_string("dropdown-item", "active" => @broadcast.id == broadcast.id) %>
<div class="row no-gutters m-n3">
<div class="col-lg-8 col-md-12 bg-black">
<div class="d-flex justify-content-start align-items-center flex-row p-3 mb-5 bg-dark">
<%= product_wordmark(:direct_me, class: 'navbar-brand text-white') %>
<div class="ml-4 dropdown">
<% if @multi_view_broadcasts.present? %>
<%= link_to "#", class: "btn btn-light dropdown-toggle text-white bg-black border-0 override-dropdown-show-state", role: "button", id: "dropdownMenuLink", data: { toggle: "dropdown" }, aria: { haspopup: "true", expanded: "false" } do %>
<i class="fa fa-video-camera text-primary" aria-hidden="true"></i><span class="ml-2" id="selected_stream"><%= @broadcast.name %></span>
<% end %>
<% else %>
<%= link_to "javascript:void(0);", class: "btn btn-light text-white bg-black border-0 override-dropdown-show-state", role: "button", id: "dropdownMenuLink", aria: { haspopup: "true", expanded: "false" } do %>
<i class="fa fa-video-camera text-primary" aria-hidden="true"></i><span class="ml-2" id="selected_stream"><%= @broadcast.name %></span>
<% end %>
<% end %>
<div class="dropdown-menu bg-black" aria-labelledby="dropdownMenuLink">
<%= link_to fa_icon("check", text: @broadcast.name.titleize), "#", data: { behavior: "play_stream"}, class: "dropdown-item active" %>
<% @multi_view_broadcasts.each do |broadcast| %>
<% if broadcast.id != @broadcast.id %>
<% if params[:director_mode] %>
<% if controller.class.module_parent.to_s == "Public" %>
<%= link_to broadcast.name.titleize, url_for(params.permit!.merge(director_mode: true, token: broadcast.token)), data: { behavior: "play_stream"}, class: class_string("dropdown-item", "active" => @broadcast.id == broadcast.id) %>
<% else %>
<%= link_to broadcast.name.titleize, url_for(params.permit!.merge(director_mode: true, id: broadcast.id)), data: { behavior: "play_stream"}, class: class_string("dropdown-item", "active" => @broadcast.id == broadcast.id) %>
<% end %>
<% else %>
<% if controller.class.module_parent.to_s == "Public" %>
<%= link_to broadcast.name.titleize, url_for(params.permit!.merge(token: broadcast.token).except(:director_mode)), data: { behavior: "play_stream"}, class: class_string("dropdown-item", "active" => @broadcast.id == broadcast.id) %>
<% else %>
<%= link_to broadcast.name.titleize, url_for(params.permit!.merge(id: broadcast.id).except(:director_mode)), data: { behavior: "play_stream"}, class: class_string("dropdown-item", "active" => @broadcast.id == broadcast.id) %>
<% end %>
<% end %>
<% if @broadcast.director_mode_video_embed.present? %>
<h5 class="dropdown-header">Director Mode</h5>
<% unless params[:director_mode] %>
<%= link_to "Enable Director Mode", url_for(params.permit!.merge(director_mode: true)), class: "dropdown-item" %>
<% else %>
<%= link_to "Disable Director Mode", url_for(params.permit!.except(:director_mode)), class: "dropdown-item" %>
<% end %>
<% end %>
<h5 class="dropdown-header">Previous Sessions</h5>
<div id="broadcast_recordings_nav">
<% if @recordings.any? %>
<%= render partial: "broadcasts/broadcast_recording_nav", collection: @recordings, as: :broadcast_recording %>
<% else %>
<p class="dropdown-item text-muted">Recordings will appear here</p>
<% end %>
</div>
</div>
</div>
</div>
</div>
<div class="card-body p-0">
<div class="embed-responsive embed-responsive-16by9">
<%= render partial: 'broadcasts/video', locals: { broadcast: @broadcast } %>
<% if @broadcast.streamer_recording? && @broadcast.active? %>
<%= javascript_tag nonce: true do %>
new Clappr.Player({
parentId: '#broadcast_video',
source: "<%= @broadcast.stream_playback_url %>",
width: '100%',
height: '100%',
mute: true,
autoPlay: true,
hlsMinimumDvrSize: 1
});
<% end %>
<% end %>
</div>
</div>
<% if @broadcast.director_mode_video_embed.present? %>
<% if params[:director_mode] %>
<div class="custom-control custom-switch ml-auto">
<input type="checkbox" name="director_mode" value="true" class="custom-control-input" id="director_mode_switch" checked="checked" />
<label class="custom-control-label text-white override-custom-control-label" for="director_mode_switch">Director Mode</label>
</div>
<%= link_to "Disable Director Mode", url_for(params.permit!.except(:director_mode)), class: "d-none", id: "director_mode_link" %>
<% else %>
<div class="custom-control custom-switch ml-auto">
<input type="checkbox" name="director_mode" value="true" class="custom-control-input" id="director_mode_switch" />
<label class="custom-control-label text-white override-custom-control-label" for="director_mode_switch">Director Mode</label>
</div>
<%= link_to "Enable Director Mode", url_for(params.permit!.merge(director_mode: true)), class: "d-none", id: "director_mode_link" %>
<% end %>
<% end %>
</div>
<div class="embed-responsive embed-responsive-16by9" id="video_content">
<%= render partial: 'broadcasts/video', locals: { broadcast: @broadcast } %>
<% if @broadcast.streamer_recording? && @broadcast.active? %>
<%= javascript_tag nonce: true do %>
new Clappr.Player({
parentId: '#broadcast_video',
source: "<%= @broadcast.full_live_stream_playback_url %>",
width: '100%',
height: '100%',
mute: true,
autoPlay: true,
hlsMinimumDvrSize: 1
});
<% end %>
<% end %>
</div>
</div>
<div class="col-lg-4 col-md-12 mb-3">
<div class="card shadow-sm mb-3">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item">
<%= link_to "Home", "#home", class: class_string("nav-link", "active" => !params[:active_tab].present?), data: { toggle: "tab" } %>
</li>
<li class="nav-item">
<%= link_to "Previous Sessions", "#recordings", class: class_string("nav-link", "active" => params[:active_tab] == "recordings"), data: { toggle: "tab" } %>
</li>
</ul>
</div>
<div class="card-body p-3">
<div class="tab-content">
<div class="<%= class_string("tab-pane fade show", "active" => !params[:active_tab].present?) %>" id="home">
<div id="broadcast_updates">
<%= render partial: 'broadcasts/broadcast_status', locals: { broadcast: @broadcast } %>
</div>
<% unless controller.class.module_parent.to_s == "Public" %>
<hr>
<div class="form-group">
<label for="broadcast_share_url">To share the stream, copy the URL below. Anyone with the link can view the stream.</label>
<div class="input-group">
<% if @multi_view_broadcasts.present? %>
<% tokens = @multi_view_broadcasts.map(&:token) %>
<input type="text" class="form-control" value="<%= broadcast_url(@broadcast.token, multi_view_tokens: tokens) %>" readonly>
<div class="input-group-append">
<button type="button" id="broadcast_share_url" class="btn btn-success" data-behavior="clipboard" href="<%= broadcast_url(@broadcast.token, multi_view_tokens: tokens) %>">
<i class="fa fa-clipboard"></i>
Copy URL
</button>
<%= link_to t('.actions.reset_url'), [@project, @broadcast], method: :patch, class: "btn btn-danger" %>
</div>
<% else %>
<input type="text" class="form-control" value="<%= broadcast_url(@broadcast.token) %>" readonly>
<div class="input-group-append">
<button type="button" id="broadcast_share_url" class="btn btn-success" data-behavior="clipboard" href="<%= broadcast_url(@broadcast.token) %>">
<i class="fa fa-clipboard"></i>
Copy URL
</button>
<%= link_to t('.actions.reset_url'), [@project, @broadcast], method: :patch, class: "btn btn-danger" %>
</div>
<% end %>
</div>
</div>
<hr>
<% end %>
<p class="card-text"><%= "If you want to join the #{conference_option_name_from_key(@broadcast.conference_option)} meeting dedicated to this broadcast, follow the link below." %></p>
<%= link_to 'Video Conference', @conference_url, class: "btn btn-primary btn-block", target: '_blank' %>
</div>
<div class="<%= class_string("tab-pane fade show", "active" => params[:active_tab] == 'recordings') %>" id="recordings">
<div id="broadcast_recordings">
<%= render partial: 'broadcasts/broadcast_recordings', locals: { recordings: @recordings, broadcast: @broadcast } %>
</div>
<div class="col-lg-4 col-md-12 bg-white p-3 min-vh-100 overflow-auto">
<% unless controller.class.module_parent.to_s == "Public" %>
<% if @multi_view_broadcasts.present? %>
<% tokens = @multi_view_broadcasts.map(&:token) %>
<div class="btn-group">
<button type="button" class="btn btn-primary" id="broadcast_share_url" data-behavior="clipboard" href="<%= broadcast_url(@broadcast.token, multi_view_tokens: tokens) %>">Share URL</button>
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
<%= link_to t('.actions.reset_url'), [@project, @broadcast], method: :patch, class: "dropdown-item" %>
</div>
</div>
<% else %>
<div class="btn-group">
<button type="button" class="btn btn-primary" id="broadcast_share_url" data-behavior="clipboard" href="<%= broadcast_url(@broadcast.token) %>">Share URL</button>
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
<%= link_to t('.actions.reset_url'), [@project, @broadcast], method: :patch, class: "dropdown-item" %>
</div>
</div>
<% end %>
<% end %>
<%= link_to 'Video Conference', @conference_url, class: 'btn btn-primary', target: '_blank' %>
<hr/>
<ul class="nav nav-tabs override-nav-tabs mb-3">
<li class="nav-item">
<%= link_to "Takes", "#recordings", class: class_string("nav-link", "active" => !params[:active_tab].present?), data: { toggle: "tab" } %>
</li>
<li class="nav-item">
<%= link_to "Files", "#files", class: class_string("nav-link", "active" => params[:active_tab] == "files"), data: { toggle: "tab" } %>
</li>
</ul>
<div class="tab-content">
<div class="<%= class_string("tab-pane fade show", "active" => !params[:active_tab].present?) %>" id="recordings">
<div id="live_take">
<%= render partial: 'broadcasts/live_take', locals: { broadcast: @broadcast } %>
</div>
<% if params[:director_mode] %>
<% if controller.class.module_parent.to_s == "Public" %>
<%= link_to "Play #{@broadcast.name.titleize}", url_for(params.permit!.merge(director_mode: true, token: @broadcast.token)), data: { behavior: "play_stream"}, class: "mt-2 btn btn-primary" %>
<% else %>
<%= link_to "Play #{@broadcast.name.titleize}", url_for(params.permit!.merge(director_mode: true, id: @broadcast.id)), data: { behavior: "play_stream"}, class: "mt-2 btn btn-primary" %>
<% end %>
<hr/>
<% else %>
<% if controller.class.module_parent.to_s == "Public" %>
<%= link_to "Play #{@broadcast.name.titleize}", url_for(params.permit!.merge(token: @broadcast.token).except(:director_mode)), data: { behavior: "play_stream"}, class: "mt-2 btn btn-primary" %>
<% else %>
<%= link_to "Play #{@broadcast.name.titleize}", url_for(params.permit!.merge(id: @broadcast.id).except(:director_mode)), data: { behavior: "play_stream"}, class: "mt-2 btn btn-primary" %>
<% end %>
<hr/>
<% end %>
<div id="broadcast_recordings">
<%= render partial: 'broadcasts/broadcast_recordings', locals: { recordings: @recordings, broadcast: @broadcast } %>
</div>
</div>
</div>
<!-- files section -->
<div id="files" class="card shadow-sm mb-3">
<div class="card-header">
<h2 class="h5">Files</h2>
<% if @multi_view_broadcasts %>
<ul class="nav nav-tabs card-header-tabs">
<div class="<%= class_string("tab-pane fade show", "active" => params[:active_tab] == "files") %>" id="files">
<% if @multi_view_broadcasts.present? %>
<ul class="nav nav-tabs">
<% @multi_view_broadcasts.each_with_index do |mvb, index| %>
<li class="nav-item">
<%= link_to mvb.name, "#files_broadcast_#{mvb.token}", class: class_string("nav-link", "active" => (params[:active_files_tab] == mvb.token || (params[:active_files_tab].nil? && index == 0))), data: { toggle: "tab" } %>
</li>
<% end %>
</ul>
<% end %>
</div>
<div class="card-body p-3">
<div class="tab-content">
<% if @multi_view_broadcasts.present? %>
<div class="tab-content pt-4">
<% @multi_view_broadcasts.each_with_index do |mvb, index| %>
<div class="<%= class_string("tab-pane fade show", "active" => (params[:active_files_tab] == mvb.token || (params[:active_files_tab].nil? && index == 0))) %>" id="files_broadcast_<%= mvb.token %>">
<%= render partial: 'broadcasts/files_section', locals: { broadcast: mvb, files: mvb.files } %>
</div>
<% end %>
<% else %>
<div class="tab-pane fade show active" id="files_broadcast_<%= @broadcast.id %>">
<%= render partial: 'broadcasts/files_section', locals: { broadcast: @broadcast, files: @files } %>
</div>
<% end %>
</div>
<% end %>
</div>
<% else %>
<%= render partial: 'broadcasts/files_section', locals: { broadcast: @broadcast, files: @files } %>
<% end %>
</div>
</div>
</div>

View File

@@ -10,4 +10,10 @@
<%= description_list_pair t('.description_labels.issued_to'), releasable.name %>
<%= description_list_pair t('.description_labels.issued_by'), releasable.approved_by_user_name %>
<%= description_list_pair t('.description_labels.date_issued'), releasable.approved_at %>
</dl>
</dl>
<% if preview %>
<%= image_tag dummy_signature %>
<% elsif releasable.approved_by_user_signature.attached? %>
<%= image_tag releasable.approved_by_user_signature.variant(auto_orient: true, resize: "200x200") %>
<% end %>

View File

@@ -4,23 +4,34 @@
</div>
<hr>
<% end %>
<% if preview %>
<h1>PREVIEW ONLY</h1>
<% end %>
<% if contract_template.body.present? %>
<%= contract_template.body %>
<br/>
<% end %>
<% if releasable.minor? && contract_template.guardian_clause.present? %>
<p class="text-left"><strong>Guardian Clause</strong></p>
<%= contract_template.guardian_clause %>
<% end %>
<%# if releasable.model_name.in? %w(MedicalRelease MiscRelease AppearanceRelease) %>
<% if releasable.respond_to?(:question_1_answer) %>
<div class="page">
<% if preview %>
<h1>PREVIEW ONLY</h1>
<% end %>
<% if releasable.model_name == "AppearanceRelease" && releasable.person_photo.attached? %>
<div class="embed-person-photo">
<%= image_tag releasable.photos.first.variant(auto_orient: true, resize: "200x200"), id: "top-person-photo" %>
</div>
<hr>
<% end %>
<% if contract_template.body.present? %>
<%= contract_template.body %>
<br/>
<% end %>
<% if releasable.minor? && contract_template.guardian_clause.present? %>
<p class="text-left"><strong>Guardian Clause</strong></p>
<%= contract_template.guardian_clause %>
<% end %>
</div>
<% if contract_template.present? && contract_template.has_questionnaire? %>
<div class="page">
<%= render "contracts/questionnaire", releasable: releasable, contract_template: contract_template, preview: preview %>
</div>
</div>
<% end %>
<div class="page">
@@ -47,14 +58,13 @@
<% end %>
<% end %>
<% if releasable.respond_to?(:approved?) && releasable.approved? %>
<% if releasable.try(:approved?) %>
<div class="page">
<%= render "contracts/for_office_use_only", releasable: releasable, preview: preview %>
</div>
<% end %>
<% if releasable.class == AcquiredMediaRelease %>
<% if releasable.respond_to?(:image_files) %>
<div class="page">
<%= render "contracts/files", release: releasable, preview: preview %>
</div>

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="<%= I18n.locale %>">
<head>
<title>MESuite.ai App</title>
<title>ME Suite</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="<%= I18n.locale %>">
<head>
<title>MESuite.ai App</title>
<title>ME Suite</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>

View File

@@ -0,0 +1 @@
<p><%= t '.account_locked_message' %></p>

View File

@@ -57,8 +57,8 @@
<hr>
<%= field_set_tag content_tag(:span, t(".photos.heading"), class: "h6 text-muted text-uppercase") do %>
<%= render "shared/photos_dropzone_fields", form: form, release: material_release %>
<%= field_set_tag content_tag(:span, t(".files.heading"), class: "h6 text-muted text-uppercase") do %>
<%= render "shared/releasable_files_dropzone", form: form, releasable: material_release %>
<div class="<%= class_string("collapse" => !material_release.minor?) %>" data-ujs-target="guardian-fields">
<br>

View File

@@ -9,10 +9,10 @@
<% end %>
</td>
<td>
<% if material_release.photo.attached? %>
<%= image_tag medium_variant(material_release.photo), class: "img-fluid" %>
<% if material_release.files.any? %>
<%= material_release.files.size %>
<% else %>
<%= fa_icon("warning", text: t(".no_photos"), class: "text-danger") %>
<%= fa_icon("warning", text: t(".no_media"), class: "text-danger") %>
<% end %>
</td>
<td>
@@ -41,8 +41,8 @@
<% if policy(material_release.tags).new? %>
<%= link_to fa_icon("tags fw", text: "Tags"), [:new, material_release, :acts_as_taggable_on_tag], class: "dropdown-item", remote: true %>
<% end %>
<% if policy(material_release).edit_photos? %>
<%= link_to fa_icon("picture-o fw", text: "Photos"), [:edit, material_release, :photos], class: "dropdown-item" %>
<% if policy(material_release).edit_files? %>
<%= link_to fa_icon("file-o fw", text: "Add Media"), [:edit, material_release, :files], class: "dropdown-item" %>
<% end %>
<% if policy(Contract).show? && (material_release.contract.attached? || material_release.contract_template.present?) %>
<%= link_to fa_icon("download fw", text: "Download"), [material_release, :contracts, format: "pdf"], class: "dropdown-item", target: "_blank" %>

View File

@@ -27,7 +27,7 @@
<tr>
<th data-behavior="all-selectable"><%= check_box_tag "material_release_ids[]", false, false %></th>
<th><%= t '.table_headers.approved'%></th>
<th></th>
<th><%= t(".table_headers.files_count") %></th>
<th><%= MaterialRelease.human_attribute_name(:name) %></th>
<th><%= t(".table_headers.owner_info") %>
<th><%= t(".table_headers.notes") %></th>

View File

@@ -0,0 +1,45 @@
<div>
<div id="playerDiv"></div>
<script type="text/javascript" src="https://demo.nanocosmos.de/nanoplayer/api/release/nanoplayer.4.min.js"></script>
<%= javascript_tag nonce: true do %>
var urlParams = new URLSearchParams(window.location.search);
var streamName = urlParams.get('stream_name');
console.log("streamName", streamName);
var player;
var config = {
"source": {
"entries": [
{
"h5live": {
// your rtmp stream
"rtmp": {
"url": "rtmp://bintu-play.nanocosmos.de/play",
"streamname": streamName,
},
"server": {
"websocket": "wss://bintu-h5live.nanocosmos.de:443/h5live/stream.mp4",
"hls": "https://bintu-h5live.nanocosmos.de:443/h5live/http/playlist.m3u8",
"progressive": "https://bintu-h5live.nanocosmos.de:443/h5live/http/stream.mp4"
}
}
}
]
},
"style": {
"width": "auto",
}
};
function initPlayer() {
player = new NanoPlayer('playerDiv');
player.setup(config).then(function (config) {
console.log('setup ok with config: ' + JSON.stringify(config));
}, function (error) {
console.log(error);
});
}
// load player from playerDiv
document.addEventListener('DOMContentLoaded', function () {
initPlayer();
});
<% end %>
</div>

View File

@@ -17,7 +17,6 @@
<%= @user.role_for(Current.account).to_s.titleize %>
<% end %>
</p>
<%= link_to 'Auth to Microsoft', '/auth/azure_ad', method: :post, class: "btn btn-primary" %>
</div>
<div class="mt-3">

View File

@@ -45,8 +45,8 @@
<%= render "shared/address_fields", form: form, subject: "person", required: true %>
<% end %>
<%= card_field_set_tag t(".photo.heading") do %>
<%= render "shared/photos_dropzone_fields", form: form, release: @material_release %>
<%= card_field_set_tag t(".files.heading") do %>
<%= render "shared/releasable_files_dropzone", form: form, releasable: @material_release %>
<% end %>
<hr>

View File

@@ -9,12 +9,12 @@ Rails.application.config.content_security_policy do |policy|
policy.font_src :self, :https, :data
policy.img_src :self, :https, :data
policy.object_src :self
policy.script_src :self, :https, AppHost.new.domain_with_port, "https://stream.mux.com", :blob, :unsafe_eval
policy.script_src :self, :https, AppHost.new.domain_with_port, "https://stream.mux.com", "https://demo.nanocosmos.de", :blob, :unsafe_eval
policy.media_src :self, :https, AppHost.new.domain_with_port, "https://stream.mux.com", :data, :blob
# policy.style_src :self, :https, :unsafe_inline
# If you are using webpack-dev-server then specify webpack-dev-server host
# policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development?
policy.connect_src :self, :https, "ws://#{AppHost.new.domain_with_port}", "wss://#{AppHost.new.domain_with_port}"
policy.connect_src :self, :https, "ws://#{AppHost.new.domain_with_port}", "wss://#{AppHost.new.domain_with_port}", "wss://bintu-h5live.nanocosmos.de"
# Specify URI for violation reports
# policy.report_uri "/csp-violation-report-endpoint"

View File

@@ -1,13 +0,0 @@
require 'azure_ad'
Rails.application.config.middleware.use OmniAuth::Builder do
provider :azure_ad,
client_id: ENV['AZURE_CLIENT_ID'],
client_secret: ENV['AZURE_CLIENT_SECRET'],
redirect_uri: ENV['AZURE_REDIRECT_URI'],
client_options: {
token_url: "#{ENV['AZURE_TENANT_ID']}/oauth2/v2.0/token",
authorize_url: "#{ENV['AZURE_TENANT_ID']}/oauth2/v2.0/authorize"
},
scope: ENV['AZURE_SCOPES']
end

View File

@@ -0,0 +1 @@
Rails.application.config.session_store :cookie_store, key: '_easy_release_session', expire_after: 1.month

View File

@@ -224,6 +224,9 @@ en:
manage: Manage
empty_bookmarks:
empty: Notes will appear here
broadcast_recordings:
edit:
heading: Edit Broadcast Recording
broadcasts:
broadcast:
actions:
@@ -275,9 +278,6 @@ en:
stream_multiple_cameras: Stream multiple cameras at one time
update:
reset_notice: The Share URL has been reset, and the previous URL will no longer work. Please click "Copy URL" and share it again with those who you want to have access to this live stream
form:
labels:
conference_option: Conference Option
bulk_taggings:
new_bulk_tag_modal:
submit: Add
@@ -904,6 +904,8 @@ en:
form:
contract_and_rights:
heading: 3 of 4 Contract & Exploitable Rights
files:
heading: Files
guardian_2_info:
heading: Second Guardian Information (if company requires)
guardian_info:
@@ -911,7 +913,7 @@ en:
material_details:
heading: 1 of 3 Material Details
photos:
dropzone_label: Tap to take a photo of Licensed Material (optional)
dropzone_label: "To Add Files to the release:<br>Drag & Drop Files<br>or<br>Click or Tap here to browse files"
guardian_2_photo:
heading: Second Guardian Photo
guardian_photo:
@@ -926,6 +928,7 @@ en:
empty: Material Releases will appear here
table_headers:
approved: Approved
files_count: No. of Files
name: Name
notes: Notes
owner_info: Owner Info
@@ -937,6 +940,7 @@ en:
review: Review
messages:
approved_tooltip: Approved by %{user} on %{timestamp}
no_media: No Media
no_photos: Needs Photo
new:
heading: Import Material Release (Products / Logos)
@@ -1255,6 +1259,8 @@ en:
cancel: Cancel
contact_info:
heading: Licensor/Owner Contact Information
files:
heading: Files
guardian_2_info:
heading: Second Guardian Information (if company requires)
guardian_2_photo:
@@ -1645,9 +1651,6 @@ en:
edit: Edit
report: Report
generating: Generating...
conference_meetings:
show:
alerts:
not_authenticated: You are not authenticated via Microsoft, please authenticate and try again
failed_to_join: Failed to join conference
unknown_conference_option: Unknown conference option
locked_accounts:
index:
account_locked_message: This account is locked. Please contact a BIG admin.

View File

@@ -131,9 +131,6 @@ es:
stream_multiple_cameras: Stream multiple cameras at one time
update:
reset_notice: The Share URL has been reset, and the previous URL will no longer work. Please click "Copy URL" and share it again with those who you want to have access to this live stream
form:
labels:
conference_option: Conference Option (ES)
contract_templates:
blank_contracts:
create:
@@ -708,9 +705,6 @@ es:
production_elements_logs: Production Elements Logs, and more (ES)
reduces_labor_cost: Reduces labor costs (ES)
simplifies_cue_sheets: Simplifies Music Cue Sheets, Graphic Cue Sheets (ES)
conference_meetings:
show:
alerts:
not_authenticated: You are not authenticated via Microsoft, please authenticate and try again (ES)
failed_to_join: Failed to join conference (ES)
unknown_conference_option: Unknown conference option (ES)
locked_accounts:
index:
account_locked_message: This account is locked. Please contact a BIG admin. (ES)

View File

@@ -4,8 +4,6 @@ require 'sidekiq/web'
Rails.application.routes.draw do
AVAILABLE_LOCALES_REGEX = /#{I18n.available_locales.join("|")}/.freeze
get 'auth/azure_ad/callback', to: 'callbacks#create'
concern :confirmable do
resources :video_release_confirmations, only: [:new, :create, :destroy]
end
@@ -32,7 +30,9 @@ Rails.application.routes.draw do
namespace :admin do
mount Sidekiq::Web => '/background_queue', as: :background_queue
resources :accounts, only: [:index, :new, :create, :edit, :update, :show]
resources :accounts, only: [:index, :new, :create, :edit, :update, :show] do
resource :account_lock, path: :lock, as: :lock, only: [:create, :destroy]
end
resources :users, only: [:index, :new, :create, :edit, :update, :destroy] do
resource :masquerade, only: :create
end
@@ -50,14 +50,16 @@ Rails.application.routes.draw do
scope "(:locale)", locale: AVAILABLE_LOCALES_REGEX do
resource :account_session, only: [:update]
resource :session, only: [:destroy]
resource :account, only: [:new, :create, :update]
resource :account, only: [:new, :create, :update] do
get 'locked' => 'locked_accounts#index'
end
resources :account_auths, only: [:index, :create, :update, :destroy]
resources :projects, shallow: true do
resources :acquired_media_releases, except: [:show], concerns: [:contractable, :notable, :file_uploadable]
resources :appearance_releases, except: [:show], concerns: [:contractable, :notable]
resources :appearance_release_imports, only: [:create]
resources :location_releases, except: [:show], concerns: [:contractable, :notable, :photoable]
resources :material_releases, except: [:show], concerns: [:contractable, :notable, :photoable]
resources :material_releases, except: [:show], concerns: [:contractable, :notable, :file_uploadable]
resources :music_releases, except: [:show], concerns: [:contractable, :notable]
resources :talent_releases, except: [:show], concerns: [:contractable, :notable, :photoable]
resources :medical_releases, except: [:show], concerns: [:contractable, :notable, :photoable]
@@ -102,8 +104,10 @@ Rails.application.routes.draw do
member do
delete :destroy_file
end
resource :conference_meeting, only: [:show]
resources :broadcast_recordings, only: :destroy
resource :zoom_meeting, only: [:show]
resources :broadcast_recordings, only: [:destroy, :edit, :update] do
resources :broadcast_recording_starrings, only: :create
end
end
resources :directories, except: [:index] do
member do
@@ -200,6 +204,7 @@ Rails.application.routes.draw do
get "cookies_disabled" => 'pages#show', id: "cookies_disabled", as: :cookies_disabled
get "accountless_user" => 'pages#show', id: "accountless_user", as: :accountless_user
get "nanocosmos_player" => 'pages#show', id: "nanocosmos_player", as: :nanocosmos_player
resource :session, only: [:new, :create]
resources :password_resets, only: [:new, :create, :edit, :update]

View File

@@ -0,0 +1,9 @@
class MigrateMaterialPhotosToFiles < ActiveRecord::DataMigration
def up
photos = ActiveStorage::Attachment.where(name: "photos", record_type: "MaterialRelease")
photos.each do |photo|
photo.update(name: "files")
end
end
end

View File

@@ -1,8 +0,0 @@
class AddMicrosoftInfoToUsers < ActiveRecord::Migration[6.0]
def change
add_column :users, :microsoft_user_id, :string
add_column :users, :microsoft_access_token, :string
add_column :users, :microsoft_refresh_token, :string
add_column :users, :microsoft_token_expires_at, :integer
end
end

View File

@@ -1,6 +0,0 @@
class AddConferenceDetailsToBroadcasts < ActiveRecord::Migration[6.0]
def change
add_column :broadcasts, :conference_option, :string
add_column :broadcasts, :conference_join_url, :string
end
end

View File

@@ -0,0 +1,6 @@
class AddNameAndDescriptionToBroadcastRecordings < ActiveRecord::Migration[6.0]
def change
add_column :broadcast_recordings, :name, :string
add_column :broadcast_recordings, :description, :text
end
end

View File

@@ -0,0 +1,5 @@
class AddStarToBroadcastRecordings < ActiveRecord::Migration[6.0]
def change
add_column :broadcast_recordings, :starred, :boolean, default: false
end
end

View File

@@ -0,0 +1,5 @@
class AddLockedToAccounts < ActiveRecord::Migration[6.0]
def change
add_column :accounts, :locked, :boolean, default: false
end
end

View File

@@ -95,7 +95,8 @@ CREATE TABLE public.accounts (
slug character varying,
plan_uid character varying,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
updated_at timestamp without time zone NOT NULL,
locked boolean DEFAULT false
);
@@ -541,7 +542,10 @@ CREATE TABLE public.broadcast_recordings (
created_at timestamp(6) without time zone NOT NULL,
updated_at timestamp(6) without time zone NOT NULL,
duration double precision,
hidden boolean DEFAULT false
hidden boolean DEFAULT false,
starred boolean DEFAULT false,
name character varying,
description text
);
@@ -586,9 +590,7 @@ CREATE TABLE public.broadcasts (
stream_key_override character varying,
director_mode_video_embed text,
simulcast_uid character varying,
video_conference_url_override character varying,
conference_option character varying,
conference_join_url character varying
video_conference_url_override character varying
);
@@ -1491,7 +1493,6 @@ CREATE TABLE public.settings (
--
CREATE SEQUENCE public.settings_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
@@ -1527,7 +1528,6 @@ CREATE TABLE public.taggings (
--
CREATE SEQUENCE public.taggings_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
@@ -1558,7 +1558,6 @@ CREATE TABLE public.tags (
--
CREATE SEQUENCE public.tags_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
@@ -1806,11 +1805,7 @@ CREATE TABLE public.users (
remember_created_at timestamp without time zone,
first_name character varying,
last_name character varying,
time_zone character varying DEFAULT 'UTC'::character varying NOT NULL,
microsoft_user_id character varying,
microsoft_access_token character varying,
microsoft_refresh_token character varying,
microsoft_token_expires_at integer
time_zone character varying DEFAULT 'UTC'::character varying NOT NULL
);
@@ -4032,7 +4027,8 @@ INSERT INTO "schema_migrations" (version) VALUES
('20200811102720'),
('20200812060406'),
('20200819070738'),
('20200820081251'),
('20200820081524');
('20200820082501'),
('20200824171649'),
('20200908085319');

View File

@@ -1,124 +0,0 @@
require 'omniauth-oauth2'
# This file is from omniauth-microsoft_graph lib (not installed)
# It is modified to make auth work
module OmniAuth
module Strategies
class AzureAd < OmniAuth::Strategies::OAuth2
BASE_SCOPE_URL = 'https://graph.microsoft.com/'
BASE_SCOPES = %w[offline_access openid email profile].freeze
DEFAULT_SCOPE = 'offline_access openid email profile User.Read'.freeze
option :name, :azure_ad
option :client_options,
site: 'https://login.microsoftonline.com/'
option :authorize_options, %i[state callback_url scope response_mode]
option :token_params, {}
option :scope, DEFAULT_SCOPE
option :authorized_client_ids, []
uid { raw_info["id"] }
info do
{
# 'email' => raw_info["mail"],
# 'first_name' => raw_info["givenName"],
# 'last_name' => raw_info["surname"],
# 'name' => [raw_info["givenName"], raw_info["surname"]].join(' '),
# 'nickname' => raw_info["displayName"],
}
end
extra do
{
# 'raw_info' => raw_info,
# 'params' => access_token.params,
# 'aud' => options.client_id
}
end
def authorize_params
super.tap do |params|
options[:authorize_options].each do |k|
params[k] = request.params[k.to_s] unless [nil, ''].include?(request.params[k.to_s])
end
params[:scope] = get_scope(params)
session['omniauth.state'] = params[:state] if params[:state]
end
end
def raw_info
@raw_info ||= access_token.get('https://graph.microsoft.com/v1.0/me').parsed
end
def callback_url
options[:callback_url] || full_host + script_name + callback_path
end
def custom_build_access_token
token_response = get_access_token(request)
session[:microsoft_graph_api_token] = token_response.token
token_response
end
alias build_access_token custom_build_access_token
private
def get_access_token(request)
verifier = request.params['code']
redirect_uri = request.params['redirect_uri'] || request.params['callback_url']
if verifier && request.xhr?
client_get_token(verifier, redirect_uri || '/auth/azure_ad/callback')
elsif verifier
client_get_token(verifier, redirect_uri || callback_url)
elsif verify_token(request.params['access_token'])
::OAuth2::AccessToken.from_hash(client, request.params.dup)
elsif request.content_type =~ /json/i
begin
body = JSON.parse(request.body.read)
request.body.rewind # rewind request body for downstream middlewares
verifier = body && body['code']
client_get_token(verifier, '/auth/azure_ad/callback') if verifier
rescue JSON::ParserError => e
warn "[omniauth google-oauth2] JSON parse error=#{e}"
end
end
end
def client_get_token(verifier, redirect_uri)
client.auth_code.get_token(verifier, get_token_options(redirect_uri), get_token_params)
end
def get_token_params
deep_symbolize(options.auth_token_params || {})
end
def get_token_options(redirect_uri = '')
{ redirect_uri: redirect_uri }.merge(token_params.to_hash(symbolize_keys: true))
end
def get_scope(params)
raw_scope = params[:scope] || DEFAULT_SCOPE
scope_list = raw_scope.split(' ').map { |item| item.split(',') }.flatten
scope_list.map! { |s| s =~ %r{^https?://} || BASE_SCOPES.include?(s) ? s : "#{BASE_SCOPE_URL}#{s}" }
scope_list.join(' ')
end
def verify_token(access_token)
return false unless access_token
# access_token.get('https://graph.microsoft.com/v1.0/me').parsed
raw_response = client.request(:get, 'https://graph.microsoft.com/v1.0/me',
params: { access_token: access_token }).parsed
(raw_response['aud'] == options.client_id) || options.authorized_client_ids.include?(raw_response['aud'])
end
end
end
end

View File

@@ -1,107 +0,0 @@
require 'httparty'
class MicrosoftGraph
BASE_URL = 'https://graph.microsoft.com/v1.0'.freeze
def initialize(current_user, client_id, client_secret, tenant_id, scopes)
@current_user = current_user
@uid = current_user.microsoft_user_id
@token = current_user.microsoft_access_token
@refresh_token = current_user.microsoft_refresh_token
@token_expires_at = current_user.microsoft_token_expires_at
@client_id = client_id
@client_secret = client_secret
@tenant_id = tenant_id
@scopes = scopes
end
def create_teams_meeting(subject)
if @refresh_token.nil? || @token_expires_at.nil?
raise ActionController::InvalidAuthenticityToken, 'Missing refresh token / token expiration'
return
end
# Obtain new token if token is expired or will expire in less than 5 minutes
if 5.minutes.from_now.to_i > @token_expires_at.seconds
refresh_access_token
end
if @token.nil?
raise ActionController::InvalidAuthenticityToken, 'Missing access token'
return
end
response = HTTParty.post(
"#{BASE_URL}/me/onlineMeetings",
body: {
subject: subject,
participants: {
organizer: {
identity: {
user: {
id: @uid
}
}
}
}
}.to_json,
headers: {
Authorization: "Bearer #{@token}",
'Content-Type': 'application/json'
}
)
raise StandardError, 'Authenticated user does not have a permission to create Teams Online Meeting' if response.code == 403
if response.code != 201
Rails.logger.error('[Microsoft Graph Error]')
Rails.logger.error(response.inspect)
raise StandardError, "Failed to create teams meeting [#{response.code}]"
else
JSON.parse(response.body)
end
end
private
def refresh_token_url
"https://login.microsoftonline.com/#{@tenant_id}/oauth2/v2.0/token"
end
def refresh_access_token
response = HTTParty.post(refresh_token_url,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: {
client_id: @client_id,
client_secret: @client_secret,
refresh_token: @refresh_token,
grant_type: 'refresh_token',
scope: @scopes
})
if response.code != 200
Rails.logger.error '[Microsoft Graph Error] Failed to obtain new access token using refresh token'
Rails.logger.error(response.inspect)
raise StandardError, 'Failed to obtain new access token'
end
parsed_response = JSON.parse(response.body)
new_access_token = parsed_response['access_token']
new_refresh_token = parsed_response['refresh_token']
token_expires_in = parsed_response['expires_in'] # For how long access token is valid (in seconds)
token_new_expiration_time = Time.now.to_i + token_expires_in
@current_user.microsoft_access_token = new_access_token
@current_user.microsoft_refresh_token = new_refresh_token
@current_user.microsoft_token_expires_at = token_new_expiration_time
@current_user.save!
@token = new_access_token
@refresh_token = new_refresh_token
@token_expires_at = token_new_expiration_time
end
end

View File

@@ -17,6 +17,7 @@ RSpec.describe BroadcastsChannel, type: :channel do
it "broadcasts to the channel" do
status_content = ApplicationController.render partial: "broadcasts/broadcast_status", locals: { broadcast: broadcast }
video_content = ApplicationController.render partial: "broadcasts/video", locals: { broadcast: broadcast }
live_take = ApplicationController.render partial: "broadcasts/live_take", locals: { broadcast: broadcast }
expect {
BroadcastsChannel.broadcast_stream_updates(broadcast)
@@ -27,6 +28,7 @@ RSpec.describe BroadcastsChannel, type: :channel do
full_live_stream_playback_url: broadcast.full_live_stream_playback_url,
status_content: status_content,
video_content: video_content,
live_take_content: live_take,
streamer_status: broadcast.streamer_status
})
end

View File

@@ -30,6 +30,16 @@ RSpec.describe Api::AcquiredMediaReleasesController, type: :controller do
expect(response).to be_successful
end
it 'contains files attachment data' do
tested_release = create(:acquired_media_release, name: 'ct1', project_id: project.id)
sign_in_to_api(current_user)
get :show, params: { id: tested_release.id }
expect(response.body).to match /file_infos/
expect(response.body).to match /files/
end
end
describe '#create' do

View File

@@ -14,6 +14,8 @@ RSpec.describe Api::ProfilesController, type: :controller do
expect(response).to be_successful
expect(response_body_data).to include('id' => current_user.to_param, 'type' => 'user')
expect(response_body_data_attributes).to include('email' => current_user.email)
expect(response_body_data_attributes).to include('full_name' => current_user.full_name)
expect(response_body_data_attributes).to include('company_name' => current_user.primary_account.name)
end
end

View File

@@ -27,10 +27,17 @@ RSpec.describe ApprovalsController, type: :controller do
expect(MedicalRelease.last.approved?).to eq false
post :create, params: { medical_release_id: medical_release }
post :create, params: { medical_release_id: medical_release, medical_release: approvable_params }
expect(response).to redirect_to [project, :medical_releases]
expect(MedicalRelease.last.approved?).to eq true
end
end
private
def approvable_params
signature_base64 ||= Base64Image.from_image(file_fixture('signature.png')).data_uri
{ approved_by_user_signature: { data: signature_base64 } }
end
end

View File

@@ -0,0 +1,48 @@
require 'rails_helper'
RSpec.describe BroadcastRecordingStarringsController, 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
stub_mux_live_stream
end
describe "#create" do
let(:broadcast) { create(:broadcast, project: project, name: "New Broadcast") }
let(:recordings) { create_list(:broadcast_recording, 5, :with_random_asset_uid, broadcast: broadcast) }
let(:starred_recordings) { create_list(:broadcast_recording, 5, :with_random_asset_uid, broadcast: broadcast, starred: true) }
it "sets star property to true when recording is starred" do
recordings.each do |recording|
expect(recording.starred).to be_falsey
end
post :create, params: { project_id: project, broadcast_id: broadcast, broadcast_recording_id: recordings.first.id }, xhr: true
expect(recordings.first.reload.starred).to eq true
recordings[1..5].each do |recording|
expect(recording.reload.starred).to be_falsey
end
end
it "sets star property to false when recording is unstarred" do
starred_recordings.each do |recording|
expect(recording.starred).to be_truthy
end
post :create, params: { project_id: project, broadcast_id: broadcast, broadcast_recording_id: starred_recordings.first.id }, xhr: true
expect(starred_recordings.first.reload.starred).to eq false
starred_recordings[1..5].each do |recording|
expect(recording.reload.starred).to eq true
end
end
end
end

View File

@@ -9,9 +9,10 @@ RSpec.describe BroadcastRecordingsController, type: :controller do
before do
sign_in user
stub_mux_live_stream
end
describe "#destroy" do
describe "#edit" do
let(:broadcast) { create(:broadcast, project: project, name: "New Broadcast") }
let(:recording) { create(:broadcast_recording, broadcast: broadcast) }
@@ -19,6 +20,55 @@ RSpec.describe BroadcastRecordingsController, type: :controller do
stub_mux_live_stream
end
it "assigns project, broadcast, broadcast recording" do
get :edit, params: { project_id: project, broadcast_id: broadcast, id: recording }, xhr: true
expect(assigns(:project)).to have_attributes({
id: project.id,
name: project.name,
account_id: project.account_id
})
expect(assigns(:broadcast)).to have_attributes({
id: broadcast.id,
name: broadcast.name,
project_id: project.id
})
expect(assigns(:recording)).to have_attributes({
id: recording.id,
broadcast_id: broadcast.id,
file_name: "high.mp4"
})
end
end
describe "#update" do
let(:broadcast) { create(:broadcast, project: project, name: "New Broadcast") }
let(:recording) { create(:broadcast_recording, broadcast: broadcast) }
let(:recordings) { create_list(:broadcast_recording, 5, :with_random_asset_uid, broadcast: broadcast) }
let(:starred_recordings) { create_list(:broadcast_recording, 5, :with_random_asset_uid, broadcast: broadcast, starred: true) }
before do
stub_mux_live_stream
end
it "updates the recording's name and description" do
expect(recording.name).to eq(recording.download_file_name)
expect(recording.description).to eq("No description provided for this recording.")
patch :update, params: { project_id: project, broadcast_id: broadcast, id: recording, broadcast_recording: { name: "Just for fun", description: "I had fun while making this stream." } }, xhr: true
recording.reload
expect(recording.name).to eq("Just for fun")
expect(recording.description).to eq("I had fun while making this stream.")
end
end
describe "#destroy" do
let(:broadcast) { create(:broadcast, project: project, name: "New Broadcast") }
let(:recording) { create(:broadcast_recording, broadcast: broadcast) }
it "hides the broadcast recording" do
expect(recording.hidden).to be false

View File

@@ -150,31 +150,16 @@ RSpec.describe BroadcastsController, type: :controller do
expect(assigns(:broadcast)).to eq(broadcast)
end
it "renders readonly share url" do
get :show, params: { project_id: project.id, id: broadcast.id }
expect(response.body).to have_button "Copy URL"
expect(response.body).to have_xpath "//input[@readonly][@value='#{broadcast_url(broadcast.token)}']"
end
it "displays zoom meeting button for zoom conference option" do
it "displays zoom meeting button" do
get :show, params: { project_id: project.id, id: broadcast.id }
expect(response.body).to have_link("Video Conference", href: project_broadcast_conference_meeting_url(project, broadcast))
end
it "displays microsoft teams meeting button for MS Teams conference option" do
ms_teams_broadcast = create(:broadcast, :ms_teams_conference, project: project )
get :show, params: { project_id: project.id, id: ms_teams_broadcast.id }
expect(response.body).to have_content 'MS Teams'
expect(response.body).to have_link 'Video Conference', href: project_broadcast_conference_meeting_url(project, ms_teams_broadcast)
expect(response.body).to have_link("Video Conference", href: project_broadcast_zoom_meeting_url(project, broadcast))
end
it "assigns required variables" do
get :show, params: { project_id: project.id, id: broadcast.id }
expect(assigns(:conference_url)).to eq project_broadcast_conference_meeting_url(project, broadcast)
expect(assigns(:conference_url)).to eq project_broadcast_zoom_meeting_url(project, broadcast)
expect(assigns(:broadcast)).to eq broadcast
end
@@ -182,7 +167,7 @@ RSpec.describe BroadcastsController, type: :controller do
it "renders the view dropdown with just the current broadcast" do
get :show, params: { project_id: project, id: broadcast }
expect(response.body).to have_content "Switch View"
expect(response.body).to have_content broadcast.name
expect(response.body).to have_selector(".dropdown-menu .dropdown-item.active", text: "Another Broadcast")
end
end
@@ -193,32 +178,12 @@ RSpec.describe BroadcastsController, type: :controller do
get :show, params: { project_id: project, id: broadcast, multi_view_ids: [broadcast.id, other_broadcast.id] }
expect(response.body).to have_content "Switch View"
expect(response.body).to have_content broadcast.name
expect(response.body).to have_selector(".dropdown-menu .dropdown-item.active", text: "Another Broadcast")
expect(response.body).to have_selector(".dropdown-menu a.dropdown-item", text: "Some Other Broadcast")
end
end
context "when there are no recordings for the current broadcast" do
it "renders the view dropdown with a message" do
get :show, params: { project_id: project, id: broadcast }
expect(response.body).to have_content "Switch View"
expect(response.body).to have_selector(".dropdown-menu .dropdown-item", text: "Recordings will appear here")
end
end
context "when there are recordings available" do
it "renders the view dropdown with the recordings" do
recording = create(:broadcast_recording, broadcast: broadcast)
get :show, params: { project_id: project, id: broadcast }
expect(response.body).to have_content "Switch View"
expect(response.body).to have_selector(".dropdown-menu a.dropdown-item", text: recording.download_file_name)
end
end
context "when virtual director video embed is available" do
let(:broadcast) { create(:broadcast, project: project, name: "Another Broadcast",
director_mode_video_embed: "<iframe>video player</iframe>") }
@@ -226,9 +191,8 @@ RSpec.describe BroadcastsController, type: :controller do
it "renders the view dropdown with a director mode enable option" do
get :show, params: { project_id: project, id: broadcast }
expect(response.body).to have_content "Switch View"
expect(response.body).to have_selector(".dropdown-menu h5.dropdown-header", text: "Director Mode")
expect(response.body).to have_selector(".dropdown-menu a.dropdown-item", text: "Enable Director Mode")
expect(response.body).to have_content broadcast.name
expect(response.body).to have_selector(".custom-control-label", text: "Director Mode")
end
context "when director mode is enabled" do
@@ -241,9 +205,8 @@ RSpec.describe BroadcastsController, type: :controller do
it "renders the view dropdown with a director mode disable option" do
get :show, params: { project_id: project, id: broadcast, director_mode: true }
expect(response.body).to have_content "Switch View"
expect(response.body).to have_selector(".dropdown-menu h5.dropdown-header", text: "Director Mode")
expect(response.body).to have_selector(".dropdown-menu a.dropdown-item", text: "Disable Director Mode")
expect(response.body).to have_content broadcast.name
expect(response.body).to have_selector(".custom-control-label", text: "Director Mode")
end
end
end

View File

@@ -1,75 +0,0 @@
require 'rails_helper'
RSpec.describe ConferenceMeetingsController, type: :controller do
let(:user) { create(:user) }
let(:account) { user.primary_account }
let(:project) { create(:project, account: user.primary_account) }
let(:broadcast) { create(:broadcast, name: "Broadcast", project: project) }
let(:ms_teams_broadcast) { create(:broadcast, :ms_teams_conference, project: project) }
let(:unknown_option_broadcast) { create(:broadcast, project: project, conference_option: 'google') }
let(:meeting_start_url) { "http://meeting_start_url" }
let(:meeting_hash) { HashWithIndifferentAccess.new(start_url: meeting_start_url) }
let(:user_create_response) { {"id" => "new_host_id"} }
let(:roles_assign_response) { {"ids" => ["new_host_id"]} }
let(:roles_list_response) { {"roles" => [{"name" => "directme-host"}]} }
before :each do
allow_any_instance_of(ZoomGateway).to receive(:find_meeting).and_return(meeting_hash)
allow_any_instance_of(ZoomGateway).to receive(:create_meeting).and_return("meeting_id")
allow_any_instance_of(ZoomGateway).to receive(:create_host).and_return("host_id")
stub_mux_live_stream
end
describe "#show" do
before { sign_in user }
it "redirects to meeting start url with Zoom conference option" do
get :show, params: { project_id: project.id, broadcast_id: broadcast.id }
expect(response).to redirect_to(meeting_start_url)
end
it "redirects to the broadcast show page with alert if user is not authenticated via microsoft and tries to create MS Teams meeting" do
get :show, params: { project_id: project.id, broadcast_id: ms_teams_broadcast.id }
expect(response).to redirect_to project_broadcast_path(project, ms_teams_broadcast)
expect(flash.alert).to eq not_authenticated_alert
end
it "redirects to the broadcast show page with alert if user is authenticated via microsoft and tries to create MS Teams meeting but Graph API fails to create meeting" do
allow_any_instance_of(MicrosoftGraph).to receive(:create_teams_meeting).and_return(nil)
get :show, params: { project_id: project.id, broadcast_id: ms_teams_broadcast.id }
expect(response).to redirect_to project_broadcast_path(project, ms_teams_broadcast)
expect(flash.alert).to eq failed_to_join_alert
end
it "redirects to the broadcast show page with alert if conference option is not reckognized" do
get :show, params: { project_id: project.id, broadcast_id: unknown_option_broadcast.id }
expect(response).to redirect_to project_broadcast_path(project, unknown_option_broadcast)
expect(flash.alert).to eq unknown_conference_option_alert
end
it "redirects to meeting start url with MS Teams conference option" do
new_ms_teams_meeting = JSON.parse({ joinUrl: meeting_start_url }.to_json)
allow_any_instance_of(MicrosoftGraph).to receive(:create_teams_meeting).and_return(new_ms_teams_meeting)
get :show, params: { project_id: project.id, broadcast_id: ms_teams_broadcast.id }
expect(response).to redirect_to(meeting_start_url)
end
end
private
def not_authenticated_alert
t 'conference_meetings.show.alerts.not_authenticated'
end
def failed_to_join_alert
t 'conference_meetings.show.alerts.failed_to_join'
end
def unknown_conference_option_alert
t 'conference_meetings.show.alerts.unknown_conference_option'
end
end

View File

@@ -68,13 +68,7 @@ RSpec.describe PhotosController, type: :controller do
it_behaves_like "a photoable releases controller"
end
context "for material releases" do
subject { create(:material_release, project: project) }
it_behaves_like "a photoable releases controller"
end
private
def release_params

View File

@@ -45,7 +45,7 @@ RSpec.describe Public::BroadcastsController, type: :controller do
it "renders the view dropdown with just the current broadcast" do
get :show, params: { token: broadcast.token }
expect(response.body).to have_content "Switch View"
expect(response.body).to have_content broadcast.name
expect(response.body).to have_selector(".dropdown-menu .dropdown-item.active", text: "Broadcast")
end
end
@@ -56,7 +56,7 @@ RSpec.describe Public::BroadcastsController, type: :controller do
get :show, params: { token: broadcast.token, multi_view_tokens: [broadcast.token, other_broadcast.token] }
expect(response.body).to have_content "Switch View"
expect(response.body).to have_content broadcast.name
expect(response.body).to have_selector(".dropdown-menu .dropdown-item.active", text: "Broadcast")
expect(response.body).to have_selector(".dropdown-menu a.dropdown-item", text: "Some Other Broadcast")
end
@@ -66,8 +66,7 @@ RSpec.describe Public::BroadcastsController, type: :controller do
it "renders the view dropdown with a message" do
get :show, params: { token: broadcast.token }
expect(response.body).to have_content "Switch View"
expect(response.body).to have_selector(".dropdown-menu .dropdown-item", text: "Recordings will appear here")
expect(response.body).to have_content broadcast.name
end
end
@@ -77,8 +76,7 @@ RSpec.describe Public::BroadcastsController, type: :controller do
get :show, params: { token: broadcast.token }
expect(response.body).to have_content "Switch View"
expect(response.body).to have_selector(".dropdown-menu a.dropdown-item", text: recording.download_file_name)
expect(response.body).to have_content broadcast.name
end
end

View File

@@ -10,10 +10,10 @@ describe Public::MaterialReleasesController do
it "allows photos param" do
contract_template = create(:contract_template, project: project)
post :create, params: { account_id: user.primary_account.to_param, project_id: project, contract_template_id: contract_template, material_release: material_release_params_with_photos }
post :create, params: { account_id: user.primary_account.to_param, project_id: project, contract_template_id: contract_template, material_release: material_release_params_with_files }
expect(response).to be_successful
expect(MaterialRelease.last.photos.attached?).to eq true
expect(MaterialRelease.last.files.attached?).to eq true
end
it "displays validation errors" do
@@ -63,8 +63,8 @@ describe Public::MaterialReleasesController do
attributes_for(:material_release, :native).except(:signature).merge(signature_param)
end
def material_release_params_with_photos
attributes_for(:material_release, :native, :with_photo).except(:signature).merge(signature_param)
def material_release_params_with_files
attributes_for(:material_release, :native, :with_file).except(:signature).merge(signature_param)
end

View File

@@ -0,0 +1,30 @@
require 'rails_helper'
RSpec.describe ZoomMeetingsController, type: :controller do
let(:user) { create(:user) }
let(:account) { user.primary_account }
let(:project) { create(:project, account: user.primary_account) }
let(:broadcast) { create(:broadcast, name: "Broadcast", project: project) }
let(:meeting_start_url) { "http://meeting_start_url" }
let(:meeting_hash) { HashWithIndifferentAccess.new(start_url: meeting_start_url) }
let(:user_create_response) { {"id" => "new_host_id"} }
let(:roles_assign_response) { {"ids" => ["new_host_id"]} }
let(:roles_list_response) { {"roles" => [{"name" => "directme-host"}]} }
before :each do
allow_any_instance_of(ZoomGateway).to receive(:find_meeting).and_return(meeting_hash)
allow_any_instance_of(ZoomGateway).to receive(:create_meeting).and_return("meeting_id")
allow_any_instance_of(ZoomGateway).to receive(:create_host).and_return("host_id")
stub_mux_live_stream
end
describe "#show" do
before { sign_in user }
it "redirects to meeting start url" do
get :show, params: { project_id: project.id, broadcast_id: broadcast.id }
expect(response).to redirect_to(meeting_start_url)
end
end
end

View File

@@ -5,5 +5,9 @@ FactoryBot.define do
asset_uid "asset_uid"
asset_playback_uid "asset_playback_uid"
hidden { false }
trait :with_random_asset_uid do
sequence(:asset_uid, 'a')
end
end
end

Some files were not shown because too many files have changed in this diff Show More