From 3871e0129c223ca62221022959fc04a546f0fed9 Mon Sep 17 00:00:00 2001 From: Zachary Chai Date: Fri, 28 Oct 2016 16:21:44 -0400 Subject: [PATCH 1/4] add recordings api actions --- app/controllers/bbb_controller.rb | 64 ++++++++++++++++++++---- app/helpers/bbb_helper.rb | 45 +++++++++++++++-- app/views/bbb/_bbb.jbuilder | 3 ++ app/views/bbb/delete_recordings.jbuilder | 1 + app/views/bbb/join.jbuilder | 6 +-- app/views/bbb/recordings.jbuilder | 21 ++++++++ app/views/bbb/update_recordings.jbuilder | 1 + app/views/landing/rooms.html.erb | 2 + config/routes.rb | 5 +- 9 files changed, 129 insertions(+), 19 deletions(-) create mode 100644 app/views/bbb/_bbb.jbuilder create mode 100644 app/views/bbb/delete_recordings.jbuilder create mode 100644 app/views/bbb/recordings.jbuilder create mode 100644 app/views/bbb/update_recordings.jbuilder diff --git a/app/controllers/bbb_controller.rb b/app/controllers/bbb_controller.rb index cc35c019..abb5c037 100644 --- a/app/controllers/bbb_controller.rb +++ b/app/controllers/bbb_controller.rb @@ -1,17 +1,18 @@ class BbbController < ApplicationController + before_action :authorize_owner_recording, only: [:update_recordings, :delete_recordings] + # GET /:resource/:id/join def join - if ( params[:id].blank? ) - render_response("missing_parameter", "meeting token was not included", :bad_request) - elsif ( params[:name].blank? ) - render_response("missing_parameter", "user name was not included", :bad_request) + if params[:name].blank? + render_bbb_response("missing_parameter", "user name was not included", :unprocessable_entity) else user = User.find_by username: params[:id] options = if user { wait_for_moderator: true, + meeting_recorded: true, user_is_moderator: current_user == user } else @@ -25,22 +26,63 @@ class BbbController < ApplicationController options ) - if bbb_res[:returncode] && current_user && current_user == user ActionCable.server.broadcast "moderator_#{user.username}_join_channel", moderator: "joined" end - render_response bbb_res[:messageKey], bbb_res[:message], bbb_res[:status], bbb_res[:response] + render_bbb_response bbb_res, bbb_res[:response] end end + # GET /rooms/:id/recordings + def recordings + user = User.find_by username: params[:id] + if !user + render head(:not_found) && return + end + + bbb_res = helpers.bbb_get_recordings user.username + render_bbb_response bbb_res, bbb_res[:recordings] + end + + # PATCH /rooms/:id/recordings/:record_id + def update_recordings + bbb_res = helpers.bbb_update_recordings(params[:record_id], params[:published] == 'true') + render_bbb_response bbb_res, bbb_res[:recordings] + end + + # DELETE /rooms/:id/recordings/:record_id + def delete_recordings + byebug + bbb_res = helpers.bbb_delete_recordings(params[:record_id]) + render_bbb_response bbb_res, bbb_res[:recordings] + end + private - def render_response(messageKey, message, status, response={}) - @messageKey = messageKey - @message = message - @status = status + + def authorize_owner_recording + user = User.find_by username: params[:id] + if !user + render head(:not_found) && return + elsif !current_user || current_user != user + render head(:unauthorized) && return + end + + recordings = helpers.bbb_get_recordings(params[:record_id])[:recordings] + recordings.each do |recording| + if recording[:recordID] == params[:record_id] + return true + end + end + render head(:not_found) && return + end + + def render_bbb_response(bbb_res, response) + @messageKey = bbb_res[:messageKey] + @message = bbb_res[:message] + @status = bbb_res[:status] @response = response - render status: @status + render status: @status && return end end diff --git a/app/helpers/bbb_helper.rb b/app/helpers/bbb_helper.rb index dce6942d..4277145d 100644 --- a/app/helpers/bbb_helper.rb +++ b/app/helpers/bbb_helper.rb @@ -7,6 +7,10 @@ module BbbHelper Rails.application.secrets[:bbb_secret] end + def bbb + @bbb ||= BigBlueButton::BigBlueButtonApi.new(bbb_endpoint + "api", bbb_secret, "0.8", true) + end + def random_password(length) o = [('a'..'z'), ('A'..'Z')].map { |i| i.to_a }.flatten password = (0...length).map { o[rand(o.length)] }.join @@ -19,7 +23,6 @@ module BbbHelper options[:wait_for_moderator] ||= false options[:meeting_logout_url] ||= nil - bbb ||= BigBlueButton::BigBlueButtonApi.new(bbb_endpoint + "api", bbb_secret, "0.8", true) if !bbb return call_invalid_res else @@ -41,8 +44,7 @@ module BbbHelper logout_url = options[:meeting_logout_url] || "#{request.base_url}" moderator_password = random_password(12) viewer_password = random_password(12) - meeting_options = {:record => options[:meeting_recorded].to_s, :logoutURL => logout_url, :moderatorPW => moderator_password, :attendeePW => viewer_password } - + meeting_options = {record: options[:meeting_recorded].to_s, logoutURL: logout_url, moderatorPW: moderator_password, attendeePW: viewer_password} # Create the meeting bbb.create_meeting(meeting_token, meeting_id, meeting_options) @@ -65,6 +67,34 @@ module BbbHelper end end + def bbb_get_recordings(meeting_token) + meeting_id = (Digest::SHA1.hexdigest(Rails.application.secrets[:secret_key_base]+meeting_token)).to_s + bbb_safe_execute :get_recordings, meetingID: meeting_id + end + + def bbb_update_recordings(id, published) + bbb_safe_execute :publish_recordings, id, published + end + + def bbb_delete_recordings(id) + bbb_safe_execute :delete_recordings, id + end + + # method must be a symbol of the method's name + def bbb_safe_execute(method, *args) + if !bbb + return call_invalid_res + else + begin + response_data = bbb.send(method, *args) + response_data[:status] = :ok + rescue BigBlueButton::BigBlueButtonException => exc + response_data = bbb_exception_res exc + end + end + response_data + end + def success_res(join_url) { returncode: true, @@ -94,4 +124,13 @@ module BbbHelper status: :internal_server_error } end + + def bbb_exception_res(exc) + { + returncode: false, + messageKey: 'BBB'+exc.key.capitalize.underscore, + message: exc.message, + status: :internal_server_error + } + end end diff --git a/app/views/bbb/_bbb.jbuilder b/app/views/bbb/_bbb.jbuilder new file mode 100644 index 00000000..8d70ae36 --- /dev/null +++ b/app/views/bbb/_bbb.jbuilder @@ -0,0 +1,3 @@ +json.messageKey messageKey +json.message message +json.status status diff --git a/app/views/bbb/delete_recordings.jbuilder b/app/views/bbb/delete_recordings.jbuilder new file mode 100644 index 00000000..0f12a451 --- /dev/null +++ b/app/views/bbb/delete_recordings.jbuilder @@ -0,0 +1 @@ +json.partial! 'bbb', messageKey: @messageKey, message: @message, status: @status diff --git a/app/views/bbb/join.jbuilder b/app/views/bbb/join.jbuilder index cc60f524..59bbed24 100644 --- a/app/views/bbb/join.jbuilder +++ b/app/views/bbb/join.jbuilder @@ -1,7 +1,5 @@ -json.messageKey @messageKey -json.message @message -json.status @status -if @response +json.partial! 'bbb', messageKey: @messageKey, message: @message, status: @status +unless @response.blank? json.response do json.join_url(@response[:join_url]) if @response[:join_url] end diff --git a/app/views/bbb/recordings.jbuilder b/app/views/bbb/recordings.jbuilder new file mode 100644 index 00000000..0fcd74a2 --- /dev/null +++ b/app/views/bbb/recordings.jbuilder @@ -0,0 +1,21 @@ +json.partial! 'bbb', messageKey: @messageKey, message: @message, status: @status +unless @response.blank? + json.recordings do + unless @response.is_a? Array + @response = [@response] + end + json.array!(@response) do |recording| + json.id recording[:recordID] + json.name recording[:name] + json.start_time recording[:startTime] + json.end_time recording[:endTime] + json.published recording[:published] + json.playbacks do + json.array!(recording[:playback][:format]) do |playback| + json.type playback[:type] + json.url playback[:url] + end + end + end + end +end diff --git a/app/views/bbb/update_recordings.jbuilder b/app/views/bbb/update_recordings.jbuilder new file mode 100644 index 00000000..0f12a451 --- /dev/null +++ b/app/views/bbb/update_recordings.jbuilder @@ -0,0 +1 @@ +json.partial! 'bbb', messageKey: @messageKey, message: @message, status: @status diff --git a/app/views/landing/rooms.html.erb b/app/views/landing/rooms.html.erb index 7900b70f..28e5ce9d 100644 --- a/app/views/landing/rooms.html.erb +++ b/app/views/landing/rooms.html.erb @@ -34,5 +34,7 @@ <% end %> +
+ diff --git a/config/routes.rb b/config/routes.rb index 188c6eb7..30280265 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,8 +11,11 @@ Rails.application.routes.draw do # meetings offer a landing page for NON authenticated users to create and join session in BigBlueButton # rooms offer a customized landing page for authenticated users to create and join session in BigBlueButton get '/:resource/:id', to: 'landing#index', as: :resource - get '/:resource/:id/join', to: 'bbb#join', as: :bbb_join, defaults: { :format => 'json' } + get '/:resource/:id/join', to: 'bbb#join', as: :bbb_join, defaults: {format: 'json'} get '/:resource/:id/wait', to: 'landing#wait_for_moderator' + get '/rooms/:id/recordings', to: 'bbb#recordings', defaults: {format: 'json'} + patch '/rooms/:id/recordings/:record_id', to: 'bbb#update_recordings', defaults: {format: 'json'} + delete '/rooms/:id/recordings/:record_id', to: 'bbb#delete_recordings', defaults: {format: 'json'} root to: 'landing#index', :resource => "meetings" end From db9d06b72fb137e7bf35fca815a803c25db66c94 Mon Sep 17 00:00:00 2001 From: Zachary Chai Date: Fri, 28 Oct 2016 16:39:32 -0400 Subject: [PATCH 2/4] add recordings table --- app/assets/javascripts/application.js | 2 + app/assets/javascripts/landing.js | 97 ++++++++++++++++++++++++++ app/assets/javascripts/shared.js | 6 ++ app/assets/stylesheets/application.css | 2 +- app/assets/stylesheets/landing.scss | 6 ++ app/controllers/bbb_controller.rb | 9 ++- app/views/landing/meetings.html.erb | 2 +- app/views/landing/rooms.html.erb | 8 ++- 8 files changed, 122 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 34532f70..d780648c 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,6 +12,8 @@ // //= require jquery2 //= require jquery-ui +//= require dataTables/jquery.dataTables +//= require dataTables/bootstrap/3/jquery.dataTables.bootstrap //= require bootstrap-sprockets //= require turbolinks //= require_self diff --git a/app/assets/javascripts/landing.js b/app/assets/javascripts/landing.js index 1e9e48e2..9ebfb1cd 100644 --- a/app/assets/javascripts/landing.js +++ b/app/assets/javascripts/landing.js @@ -1,4 +1,6 @@ (function() { + var recordingsTable = null; + var waitForModerator = function(url) { $.get(url + "/wait", function(html) { $(".center-panel-wrapper").html(html); @@ -72,8 +74,103 @@ window.location.hostname + meetingURL.data('path'); meetingURL.val(link); + + // initialize recordings datatable + recordingsTable = $('#recordings').dataTable({ + data: [], + rowId: 'id', + paging: false, + searching: false, + info: false, + ordering: false, + language: { + emptyTable: "Past recordings are shown here." + }, + columns: [ + { title: "Date Recorded", data: "start_time" }, + { title: "Duration", data: "duration" }, + { title: "Views", data: "playbacks" }, + { title: "Actions", data: "id" } + ], + columnDefs: [ + { + targets: 2, + render: function(data, type, row) { + if (type === 'display') { + var str = ""; + for(let i in data) { + str += ''+data[i].type+' '; + } + return str; + } + return data; + } + }, + { + targets: -1, + render: function(data, type, row) { + if (type === 'display') { + var roomName = window.location.pathname.split('/').pop(); + var published = row.published; + var eye = (published) ? 'eye' : 'eye-slash' + return ' ' + + ''; + } + return data; + } + } + ] + }); + + $('#recordings').on('click', '.recording-update', function(event) { + var room = $(this).data('room'); + var id = $(this).data('id'); + var published = $(this).data('published'); + $.ajax({ + method: 'PATCH', + url: '/rooms/'+room+'/recordings/'+id, + data: {published: (!published).toString()} + }).done(function(data) { + $(this).prop("disabled", true); + }); + }); + + $('#recordings').on('click', '.recording-delete', function(event) { + var room = $(this).data('room'); + var id = $(this).data('id'); + $.ajax({ + method: 'DELETE', + url: '/rooms/'+room+'/recordings/'+id + }).done(function() { + $('tr[id="'+id+'"]').remove(); + }); + }); + + refreshRecordings(); }; + var refreshRecordings = function() { + if (!recordingsTable) { + return; + } + table = recordingsTable.api(); + $.get("/rooms/"+window.location.pathname.split('/').pop()+"/recordings", function(data) { + var i; + for (i = 0; i < data.recordings.length; i++) { + var totalMinutes = Math.round((new Date(data.recordings[i].end_time) - new Date(data.recordings[i].start_time)) / 1000 / 60); + data.recordings[i].duration = totalMinutes; + + data.recordings[i].start_time = new Date(data.recordings[i].start_time) + .toLocaleString([], {month: 'long', day: 'numeric', year: 'numeric', hour12: 'true', hour: '2-digit', minute: '2-digit'}); + } + table.clear(); + table.rows.add(data.recordings); + table.columns.adjust().draw(); + }); + } + $(document).on("turbolinks:load", function() { init(); if ($("body[data-controller=landing]").get(0)) { diff --git a/app/assets/javascripts/shared.js b/app/assets/javascripts/shared.js index edf84fc9..a1cc6c17 100644 --- a/app/assets/javascripts/shared.js +++ b/app/assets/javascripts/shared.js @@ -1,3 +1,9 @@ +$.ajaxSetup({ + headers: { + 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content') + } +}); + var meetingInstance = null; class Meeting { constructor(url, name) { diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 6f2b6eff..ca8885fa 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -11,7 +11,7 @@ * It is generally better to create a new file per style scope. * *= require jquery-ui - *= require dataTables/jquery.dataTables + *= require dataTables/bootstrap/3/jquery.dataTables.bootstrap *= require_tree . *= require_self */ diff --git a/app/assets/stylesheets/landing.scss b/app/assets/stylesheets/landing.scss index f33d6f8d..e2d46925 100644 --- a/app/assets/stylesheets/landing.scss +++ b/app/assets/stylesheets/landing.scss @@ -1,3 +1,9 @@ // Place all the styles related to the landing controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ + +.rooms { + .table-wrapper { + padding: 40px 50px 10px 50px; + } +} diff --git a/app/controllers/bbb_controller.rb b/app/controllers/bbb_controller.rb index abb5c037..bb3cb7be 100644 --- a/app/controllers/bbb_controller.rb +++ b/app/controllers/bbb_controller.rb @@ -49,14 +49,13 @@ class BbbController < ApplicationController # PATCH /rooms/:id/recordings/:record_id def update_recordings bbb_res = helpers.bbb_update_recordings(params[:record_id], params[:published] == 'true') - render_bbb_response bbb_res, bbb_res[:recordings] + render_bbb_response bbb_res end # DELETE /rooms/:id/recordings/:record_id def delete_recordings - byebug bbb_res = helpers.bbb_delete_recordings(params[:record_id]) - render_bbb_response bbb_res, bbb_res[:recordings] + render_bbb_response bbb_res end private @@ -69,7 +68,7 @@ class BbbController < ApplicationController render head(:unauthorized) && return end - recordings = helpers.bbb_get_recordings(params[:record_id])[:recordings] + recordings = helpers.bbb_get_recordings(params[:id])[:recordings] recordings.each do |recording| if recording[:recordID] == params[:record_id] return true @@ -78,7 +77,7 @@ class BbbController < ApplicationController render head(:not_found) && return end - def render_bbb_response(bbb_res, response) + def render_bbb_response(bbb_res, response={}) @messageKey = bbb_res[:messageKey] @message = bbb_res[:message] @status = bbb_res[:status] diff --git a/app/views/landing/meetings.html.erb b/app/views/landing/meetings.html.erb index 9bef80de..271c96d9 100644 --- a/app/views/landing/meetings.html.erb +++ b/app/views/landing/meetings.html.erb @@ -15,7 +15,7 @@ <% end %> -
+
<%= render 'shared/title', title: 'Start A New Session' %> diff --git a/app/views/landing/rooms.html.erb b/app/views/landing/rooms.html.erb index 28e5ce9d..810489a4 100644 --- a/app/views/landing/rooms.html.erb +++ b/app/views/landing/rooms.html.erb @@ -16,7 +16,7 @@
<% end %> -
+
<%= render 'shared/title', title: page_title %> @@ -34,7 +34,9 @@ <% end %>
-
- +
+

Past Recordings

+
+
From e400bf41e89213468ca2f7167df37fbd3fa34ca9 Mon Sep 17 00:00:00 2001 From: Zachary Chai Date: Mon, 31 Oct 2016 17:39:22 -0400 Subject: [PATCH 3/4] recording async update --- .../javascripts/channels/recording_update.js | 26 +++++++++++++++++++ app/assets/javascripts/landing.js | 9 ++++--- app/assets/javascripts/shared.js | 6 +++++ app/channels/recording_updates_channel.rb | 5 ++++ app/controllers/bbb_controller.rb | 15 +++++++---- app/jobs/recording_updates_job.rb | 22 ++++++++++++++++ app/{helpers/bbb_helper.rb => lib/bbb_api.rb} | 14 +++++++--- app/views/bbb/recordings.jbuilder | 3 +++ config/environments/development.rb | 4 ++- config/environments/production.rb | 2 ++ test/jobs/recording_updates_job_test.rb | 7 +++++ 11 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 app/assets/javascripts/channels/recording_update.js create mode 100644 app/channels/recording_updates_channel.rb create mode 100644 app/jobs/recording_updates_job.rb rename app/{helpers/bbb_helper.rb => lib/bbb_api.rb} (91%) create mode 100644 test/jobs/recording_updates_job_test.rb diff --git a/app/assets/javascripts/channels/recording_update.js b/app/assets/javascripts/channels/recording_update.js new file mode 100644 index 00000000..a2cb32e7 --- /dev/null +++ b/app/assets/javascripts/channels/recording_update.js @@ -0,0 +1,26 @@ +(function() { + + var initRooms = function() { + App.messages = App.cable.subscriptions.create({ + channel: 'RecordingUpdatesChannel', + username: window.location.pathname.split('/').pop() + }, + { + received: function(data) { + var btn = $("#recordings").find(".recording-update:disabled"); + btn.data('published', data.published); + btn.find('i').removeClass(getPublishClass(!data.published)); + btn.find('i').addClass(getPublishClass(data.published)); + btn.prop("disabled", false); + } + }); + }; + + $(document).on("turbolinks:load", function() { + if ($("body[data-controller=landing]").get(0)) { + if ($("body[data-action=rooms]").get(0)) { + initRooms(); + } + } + }); +}).call(this); diff --git a/app/assets/javascripts/landing.js b/app/assets/javascripts/landing.js index 9ebfb1cd..ce8ee132 100644 --- a/app/assets/javascripts/landing.js +++ b/app/assets/javascripts/landing.js @@ -112,9 +112,9 @@ if (type === 'display') { var roomName = window.location.pathname.split('/').pop(); var published = row.published; - var eye = (published) ? 'eye' : 'eye-slash' + var eye = getPublishClass(published); return ' ' + + ' ' + ''; } @@ -128,12 +128,15 @@ var room = $(this).data('room'); var id = $(this).data('id'); var published = $(this).data('published'); + $(this).prop("disabled", true); $.ajax({ method: 'PATCH', url: '/rooms/'+room+'/recordings/'+id, data: {published: (!published).toString()} }).done(function(data) { - $(this).prop("disabled", true); + + }).fail(function(data) { + $(this).prop("disabled", false); }); }); diff --git a/app/assets/javascripts/shared.js b/app/assets/javascripts/shared.js index a1cc6c17..a2488f93 100644 --- a/app/assets/javascripts/shared.js +++ b/app/assets/javascripts/shared.js @@ -4,6 +4,12 @@ $.ajaxSetup({ } }); +var PUBLISHED_CLASSES = ['fa-eye-slash', 'fa-eye'] + +var getPublishClass = function(published) { + return PUBLISHED_CLASSES[+published]; +} + var meetingInstance = null; class Meeting { constructor(url, name) { diff --git a/app/channels/recording_updates_channel.rb b/app/channels/recording_updates_channel.rb new file mode 100644 index 00000000..ff814506 --- /dev/null +++ b/app/channels/recording_updates_channel.rb @@ -0,0 +1,5 @@ +class RecordingUpdatesChannel < ApplicationCable::Channel + def subscribed + stream_from "#{params[:username]}_recording_updates_channel" + end +end diff --git a/app/controllers/bbb_controller.rb b/app/controllers/bbb_controller.rb index bb3cb7be..aa140966 100644 --- a/app/controllers/bbb_controller.rb +++ b/app/controllers/bbb_controller.rb @@ -1,4 +1,5 @@ class BbbController < ApplicationController + include BbbApi before_action :authorize_owner_recording, only: [:update_recordings, :delete_recordings] @@ -20,7 +21,7 @@ class BbbController < ApplicationController end options[:meeting_logout_url] = "#{request.base_url}/#{params[:resource]}/#{params[:id]}" - bbb_res = helpers.bbb_join_url( + bbb_res = bbb_join_url( params[:id], params[:name], options @@ -42,19 +43,22 @@ class BbbController < ApplicationController render head(:not_found) && return end - bbb_res = helpers.bbb_get_recordings user.username + bbb_res = bbb_get_recordings user.username render_bbb_response bbb_res, bbb_res[:recordings] end # PATCH /rooms/:id/recordings/:record_id def update_recordings - bbb_res = helpers.bbb_update_recordings(params[:record_id], params[:published] == 'true') + bbb_res = bbb_update_recordings(params[:record_id], params[:published] == 'true') + if bbb_res[:returncode] + RecordingUpdatesJob.perform_later(@user.username, params[:record_id], bbb_res[:published]) + end render_bbb_response bbb_res end # DELETE /rooms/:id/recordings/:record_id def delete_recordings - bbb_res = helpers.bbb_delete_recordings(params[:record_id]) + bbb_res = bbb_delete_recordings(params[:record_id]) render_bbb_response bbb_res end @@ -68,9 +72,10 @@ class BbbController < ApplicationController render head(:unauthorized) && return end - recordings = helpers.bbb_get_recordings(params[:id])[:recordings] + recordings = bbb_get_recordings(params[:id])[:recordings] recordings.each do |recording| if recording[:recordID] == params[:record_id] + @user = user return true end end diff --git a/app/jobs/recording_updates_job.rb b/app/jobs/recording_updates_job.rb new file mode 100644 index 00000000..866e729d --- /dev/null +++ b/app/jobs/recording_updates_job.rb @@ -0,0 +1,22 @@ +class RecordingUpdatesJob < ApplicationJob + include BbbApi + + queue_as :default + + def perform(room, record_id, published) + tries = 0 + sleep_time = 2 + + while tries < 4 + bbb_res = bbb_get_recordings(nil, record_id) + if bbb_res[:recordings].first[:published].to_s == published + ActionCable.server.broadcast "#{room}_recording_updates_channel", + published: bbb_res[:recordings].first[:published] + break + end + sleep sleep_time + sleep_time = sleep_time * 2 + tries += 1 + end + end +end diff --git a/app/helpers/bbb_helper.rb b/app/lib/bbb_api.rb similarity index 91% rename from app/helpers/bbb_helper.rb rename to app/lib/bbb_api.rb index 4277145d..61aaa257 100644 --- a/app/helpers/bbb_helper.rb +++ b/app/lib/bbb_api.rb @@ -1,4 +1,4 @@ -module BbbHelper +module BbbApi def bbb_endpoint Rails.application.secrets[:bbb_endpoint] end @@ -67,9 +67,15 @@ module BbbHelper end end - def bbb_get_recordings(meeting_token) - meeting_id = (Digest::SHA1.hexdigest(Rails.application.secrets[:secret_key_base]+meeting_token)).to_s - bbb_safe_execute :get_recordings, meetingID: meeting_id + def bbb_get_recordings(meeting_id, record_id=nil) + options={} + if record_id + options[:recordID] = record_id + end + if meeting_id + options[:meetingID] = (Digest::SHA1.hexdigest(Rails.application.secrets[:secret_key_base]+meeting_id)).to_s + end + bbb_safe_execute :get_recordings, options end def bbb_update_recordings(id, published) diff --git a/app/views/bbb/recordings.jbuilder b/app/views/bbb/recordings.jbuilder index 0fcd74a2..63d617ca 100644 --- a/app/views/bbb/recordings.jbuilder +++ b/app/views/bbb/recordings.jbuilder @@ -11,6 +11,9 @@ unless @response.blank? json.end_time recording[:endTime] json.published recording[:published] json.playbacks do + unless recording[:playback][:format].is_a? Array + recording[:playback][:format] = [recording[:playback][:format]] + end json.array!(recording[:playback][:format]) do |playback| json.type playback[:type] json.url playback[:url] diff --git a/config/environments/development.rb b/config/environments/development.rb index b91c71aa..9616a76b 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -31,11 +31,13 @@ Rails.application.configure do config.action_mailer.perform_caching = false + config.active_job.queue_adapter = :async + # action cable socket URI config.action_cable.url = "ws://localhost/cable" # allowed action cable origins - Rails.application.config.action_cable.allowed_request_origins = ['http://localhost'] + config.action_cable.allowed_request_origins = ['http://localhost'] # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log diff --git a/config/environments/production.rb b/config/environments/production.rb index 6956dec8..6f2ca454 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -55,6 +55,8 @@ Rails.application.configure do # Use a real queuing backend for Active Job (and separate queues per environment) # config.active_job.queue_adapter = :resque # config.active_job.queue_name_prefix = "greenlight_#{Rails.env}" + config.active_job.queue_adapter = :async + config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. diff --git a/test/jobs/recording_updates_job_test.rb b/test/jobs/recording_updates_job_test.rb new file mode 100644 index 00000000..62dd0969 --- /dev/null +++ b/test/jobs/recording_updates_job_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class RecordingUpdatesJobTest < ActiveJob::TestCase + # test "the truth" do + # assert true + # end +end From 15411d76fa472daca9543c05e6c94f231ad6ef69 Mon Sep 17 00:00:00 2001 From: Zachary Chai Date: Tue, 1 Nov 2016 12:03:00 -0400 Subject: [PATCH 4/4] only allow room owner to perform recording actions --- .../javascripts/channels/recording_update.js | 9 ++++---- app/assets/javascripts/landing.js | 22 ++++++++++++------- app/controllers/bbb_controller.rb | 6 ++--- app/jobs/recording_updates_job.rb | 1 + app/views/bbb/recordings.jbuilder | 1 + 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/channels/recording_update.js b/app/assets/javascripts/channels/recording_update.js index a2cb32e7..aae65063 100644 --- a/app/assets/javascripts/channels/recording_update.js +++ b/app/assets/javascripts/channels/recording_update.js @@ -7,11 +7,10 @@ }, { received: function(data) { - var btn = $("#recordings").find(".recording-update:disabled"); - btn.data('published', data.published); - btn.find('i').removeClass(getPublishClass(!data.published)); - btn.find('i').addClass(getPublishClass(data.published)); - btn.prop("disabled", false); + var table = $("#recordings").DataTable(); + var rowData = table.row("#"+data.record_id).data(); + rowData.published = data.published + table.row("#"+data.record_id).data(rowData).draw(); } }); }; diff --git a/app/assets/javascripts/landing.js b/app/assets/javascripts/landing.js index ce8ee132..86bab1b1 100644 --- a/app/assets/javascripts/landing.js +++ b/app/assets/javascripts/landing.js @@ -98,8 +98,10 @@ render: function(data, type, row) { if (type === 'display') { var str = ""; - for(let i in data) { - str += ''+data[i].type+' '; + if (row.published) { + for(let i in data) { + str += ''+data[i].type+' '; + } } return str; } @@ -125,10 +127,11 @@ }); $('#recordings').on('click', '.recording-update', function(event) { - var room = $(this).data('room'); - var id = $(this).data('id'); - var published = $(this).data('published'); - $(this).prop("disabled", true); + var btn = $(this); + var room = btn.data('room'); + var id = btn.data('id'); + var published = btn.data('published'); + btn.prop("disabled", true); $.ajax({ method: 'PATCH', url: '/rooms/'+room+'/recordings/'+id, @@ -136,7 +139,7 @@ }).done(function(data) { }).fail(function(data) { - $(this).prop("disabled", false); + btn.prop("disabled", false); }); }); @@ -147,7 +150,7 @@ method: 'DELETE', url: '/rooms/'+room+'/recordings/'+id }).done(function() { - $('tr[id="'+id+'"]').remove(); + recordingsTable.api().row("#"+id).remove().draw(); }); }); @@ -160,6 +163,9 @@ } table = recordingsTable.api(); $.get("/rooms/"+window.location.pathname.split('/').pop()+"/recordings", function(data) { + if (!data.is_owner) { + table.column(-1).visible( false ); + } var i; for (i = 0; i < data.recordings.length; i++) { var totalMinutes = Math.round((new Date(data.recordings[i].end_time) - new Date(data.recordings[i].start_time)) / 1000 / 60); diff --git a/app/controllers/bbb_controller.rb b/app/controllers/bbb_controller.rb index aa140966..bc65542e 100644 --- a/app/controllers/bbb_controller.rb +++ b/app/controllers/bbb_controller.rb @@ -38,12 +38,12 @@ class BbbController < ApplicationController # GET /rooms/:id/recordings def recordings - user = User.find_by username: params[:id] - if !user + @user = User.find_by username: params[:id] + if !@user render head(:not_found) && return end - bbb_res = bbb_get_recordings user.username + bbb_res = bbb_get_recordings @user.username render_bbb_response bbb_res, bbb_res[:recordings] end diff --git a/app/jobs/recording_updates_job.rb b/app/jobs/recording_updates_job.rb index 866e729d..7d884756 100644 --- a/app/jobs/recording_updates_job.rb +++ b/app/jobs/recording_updates_job.rb @@ -11,6 +11,7 @@ class RecordingUpdatesJob < ApplicationJob bbb_res = bbb_get_recordings(nil, record_id) if bbb_res[:recordings].first[:published].to_s == published ActionCable.server.broadcast "#{room}_recording_updates_channel", + record_id: record_id, published: bbb_res[:recordings].first[:published] break end diff --git a/app/views/bbb/recordings.jbuilder b/app/views/bbb/recordings.jbuilder index 63d617ca..23781b77 100644 --- a/app/views/bbb/recordings.jbuilder +++ b/app/views/bbb/recordings.jbuilder @@ -1,5 +1,6 @@ json.partial! 'bbb', messageKey: @messageKey, message: @message, status: @status unless @response.blank? + json.is_owner current_user == @user json.recordings do unless @response.is_a? Array @response = [@response]