');
diff --git a/app/assets/javascripts/recordings.coffee b/app/assets/javascripts/recordings.coffee
index 00abec2f..a4c69396 100644
--- a/app/assets/javascripts/recordings.coffee
+++ b/app/assets/javascripts/recordings.coffee
@@ -37,7 +37,7 @@ class @Recordings
{ data: "previews", orderable: false },
{ data: "duration", orderable: false },
{ data: "playbacks", orderable: false },
- { data: "published", visible: false },
+ { data: "listed", visible: false },
{ data: "id", orderable: false }
],
columnDefs: [
@@ -45,10 +45,13 @@ class @Recordings
targets: 0,
render: (data, type, row) ->
if type == 'display'
- return new Date(data)
+ date = new Date(data)
+ title = date
.toLocaleString($('html').attr('lang'),
{month: 'long', day: 'numeric', year: 'numeric',
hour12: 'true', hour: '2-digit', minute: '2-digit'})
+ timeago = ''
+ return title+'('+timeago+')'
return data
},
{
@@ -78,18 +81,18 @@ class @Recordings
render: (data, type, row) ->
if type == 'display'
roomName = Meeting.getInstance().getId()
- published = row.published
- publishText = if published then 'unpublish' else 'publish'
recordingActions = $('.hidden-elements').find('.recording-actions')
- recordingActions.find('.recording-update > i.default')
- .removeClass(PUBLISHED_CLASSES.join(' '))
- .addClass(getPublishClass(published))
- recordingActions.find('.recording-update > i.hover')
- .removeClass(PUBLISHED_CLASSES.join(' '))
- .addClass(getPublishClass(!published))
- recordingActions.find('.recording-update')
- .attr('data-published', published)
- .attr('title', I18n[publishText+'_recording'])
+ classes = ['recording-unpublished', 'recording-unlisted', 'recording-published']
+ if row.published
+ if row.listed
+ cls = classes[2]
+ else
+ cls = classes[1]
+ else
+ cls = classes[0]
+ trigger = recordingActions.find('.recording-update-trigger')
+ trigger.removeClass(classes.join(' '))
+ trigger.addClass(cls)
return recordingActions.html()
return data
}
@@ -106,17 +109,33 @@ class @Recordings
@getTable().api().clear().draw().destroy()
# enable popovers
+ # can't use trigger:'focus' because it doesn't work will with buttons inside
+ # the popover
options = {
selector: '.has-popover',
html: true,
- trigger: 'focus',
+ trigger: 'click',
title: ->
- return I18n.are_you_sure;
+ return $(this).data("popover-title");
content: ->
- return $(".delete-popover-body").html()
+ bodySelector = $(this).data("popover-body")
+ return $(bodySelector).html()
}
$('#recordings').popover(options)
+ # close popovers manually when clicking outside of them or in buttons
+ # with [data-dismiss="popover"]
+ # careful to hide only the open popover and not all of them, otherwise they won't reopen
+ $('body').on 'click', (e) ->
+ $('.has-popover').each ->
+ if !$(this).is(e.target) and $(this).has(e.target).length == 0 and $('.popover.in').has(e.target).length == 0
+ if $(this).next(".popover.in").length > 0
+ $(this).popover('hide')
+ $(document).on 'click', '[data-dismiss="popover"]', (e) ->
+ $('.has-popover').each ->
+ if $(this).next(".popover.in").length > 0
+ $(this).popover('hide')
+
# Gets the current instance or creates a new one
@getInstance: ->
if _recordingsInstance && Recordings.initialized()
@@ -153,24 +172,32 @@ class @Recordings
# setup click handlers for the action buttons
setupActionHandlers: ->
table_api = this.table.api()
+
@getTable().on 'click', '.recording-update', (event) ->
btn = $(this)
row = table_api.row($(this).closest('tr')).data()
url = $('.meeting-url').val()
id = row.id
- published = btn.data('published')
+
+ published = btn.data('visibility') == "unlisted" ||
+ btn.data('visibility') == "published"
+ listed = btn.data('visibility') == "published"
+
btn.prop('disabled', true)
+
+ data = { published: published.toString() }
+ data["meta_" + GreenLight.META_LISTED] = listed.toString();
$.ajax({
method: 'PATCH',
url: url+'/recordings/'+id,
- data: {published: (!published).toString()}
+ data: data
}).done((data) ->
).fail((data) ->
btn.prop('disabled', false)
)
- this.table.on 'click', '.recording-delete', (event) ->
+ @getTable().on 'click', '.recording-delete', (event) ->
btn = $(this)
row = table_api.row($(this).closest('tr')).data()
url = $('.meeting-url').val()
@@ -185,6 +212,9 @@ class @Recordings
btn.prop('disabled', false)
)
+ @getTable().on 'draw.dt', (event) ->
+ $('time[data-time-ago]').timeago();
+
getTable: ->
@table
diff --git a/app/assets/javascripts/shared.js b/app/assets/javascripts/shared.js.erb
similarity index 71%
rename from app/assets/javascripts/shared.js
rename to app/assets/javascripts/shared.js.erb
index 52553d87..dba6461e 100644
--- a/app/assets/javascripts/shared.js
+++ b/app/assets/javascripts/shared.js.erb
@@ -20,12 +20,6 @@ $.ajaxSetup({
}
});
-var PUBLISHED_CLASSES = ['fa-eye-slash', 'fa-eye']
-
-var getPublishClass = function(published) {
- return PUBLISHED_CLASSES[+published];
-}
-
var loopJoin = function() {
var jqxhr = Meeting.getInstance().getJoinMeetingResponse();
jqxhr.done(function(data) {
@@ -38,8 +32,9 @@ var loopJoin = function() {
jqxhr.fail(function(xhr, status, error) {
console.info("meeting join failed");
});
-}
+};
+var alertTimeout = null;
var showAlert = function(html, timeout_delay) {
if (!html) {
return;
@@ -49,12 +44,34 @@ var showAlert = function(html, timeout_delay) {
$('#alerts').html($('.alert-template').html());
if (timeout_delay) {
- setTimeout(function() {
+ clearTimeout(alertTimeout);
+ alertTimeout = setTimeout(function() {
$('#alerts > .alert').alert('close');
}, timeout_delay);
}
-}
+};
var displayRoomURL = function() {
$('.meeting-url').val(Meeting.getInstance().getURL());
-}
+};
+
+var showNotification = function(title, options) {
+ if (Notification.permission === "granted") {
+ var icon = '<%= asset_path("bbb-logo.png") %>';
+ options = $.extend(options, {
+ icon: icon
+ });
+ var notification = new Notification(title, options);
+ notification.onclick = function() {
+ window.focus();
+ };
+ }
+};
+
+// For now there are notifications only for users signed in and when they
+// are in their room's page
+$(document).on("turbolinks:load", function() {
+ if (GreenLight.user.roomOwner) {
+ Notification.requestPermission();
+ }
+});
diff --git a/app/assets/stylesheets/landing.scss b/app/assets/stylesheets/landing.scss
index b53da801..06900393 100644
--- a/app/assets/stylesheets/landing.scss
+++ b/app/assets/stylesheets/landing.scss
@@ -37,16 +37,11 @@
.dataTables_empty {
text-align: center;
}
- .recording-update:hover > {
- .default {
- display: none;
- }
- .hover {
- display: inline-block;
- }
- }
- .fa.hover {
- display: none;
+ .timeago {
+ margin-left: 5px;
+ font-size: 13px;
+ cursor: pointer;
+ color: #999;
}
}
}
@@ -69,3 +64,34 @@
z-index: 1;
}
}
+
+.meeting-url-group {
+ position: relative;
+
+ .generate-link {
+ position: absolute;
+ right: 12px;
+ top: 10px;
+ z-index: 9;
+ color: #999;
+ cursor: pointer;
+
+ &:hover {
+ color: #000;
+ }
+ }
+}
+
+.recording-update-trigger {
+ &.recording-unpublished {
+ color: red;
+ }
+
+ &.recording-unlisted {
+ color: #e3a91e;
+ }
+
+ &.recording-published {
+ color: green;
+ }
+}
diff --git a/app/assets/stylesheets/shared.scss b/app/assets/stylesheets/shared.scss
index b11c4df6..e6d8d0ca 100644
--- a/app/assets/stylesheets/shared.scss
+++ b/app/assets/stylesheets/shared.scss
@@ -35,6 +35,10 @@ html, body {
background-repeat: no-repeat;
}
+body[data-controller=landing][data-action=rooms].app-background {
+ background-image: asset-url('bg_personal_room');
+}
+
#alerts {
position: absolute;
top: 10px;
@@ -106,3 +110,7 @@ html, body {
text-align: center;
}
}
+
+.popover {
+ max-width: none;
+}
diff --git a/app/controllers/bbb_controller.rb b/app/controllers/bbb_controller.rb
index 84fff310..485402fe 100644
--- a/app/controllers/bbb_controller.rb
+++ b/app/controllers/bbb_controller.rb
@@ -20,6 +20,9 @@ class BbbController < ApplicationController
before_action :authorize_recording_owner!, only: [:update_recordings, :delete_recordings]
before_action :load_and_authorize_room_owner!, only: [:end]
+ skip_before_action :verify_authenticity_token, only: :callback
+ before_action :validate_checksum, only: :callback
+
# GET /:resource/:id/join
def join
if params[:name].blank?
@@ -39,7 +42,9 @@ class BbbController < ApplicationController
user_is_moderator: true
}
end
- options[:meeting_logout_url] = "#{request.base_url}/#{params[:resource]}/#{params[:id]}"
+ base_url = "#{request.base_url}/#{params[:resource]}/#{params[:id]}"
+ options[:meeting_logout_url] = base_url
+ options[:hook_url] = "#{base_url}/callback"
bbb_res = bbb_join_url(
params[:id],
@@ -47,14 +52,33 @@ class BbbController < ApplicationController
options
)
+ # the user can join the meeting
if bbb_res[:returncode] && current_user && current_user == user
JoinMeetingJob.perform_later(user.encrypted_id)
+
+ # user will be waiting for a moderator
+ else
+ NotifyUserWaitingJob.perform_later(user.encrypted_id, params[:name])
end
render_bbb_response bbb_res, bbb_res[:response]
end
end
+ # POST /:resource/:id/callback
+ # Endpoint for webhook calls from BigBlueButton
+ def callback
+ begin
+ data = JSON.parse(read_body(request))
+ treat_callback_event(data["event"])
+ rescue Exception => e
+ logger.error "Error parsing webhook data. Data: #{data}, exception: #{e.inspect}"
+
+ # respond with 200 anyway so BigBlueButton knows the hook call was ok
+ render head(:ok)
+ end
+ end
+
# DELETE /rooms/:id/end
def end
load_and_authorize_room_owner!
@@ -76,9 +100,11 @@ class BbbController < ApplicationController
# PATCH /rooms/:id/recordings/:record_id
def update_recordings
- bbb_res = bbb_update_recordings(params[:record_id], params[:published] == 'true')
+ published = params[:published] == 'true'
+ metadata = params.select{ |k, v| k.match(/^meta_/) }
+ bbb_res = bbb_update_recordings(params[:record_id], published, metadata)
if bbb_res[:returncode]
- RecordingUpdatesJob.perform_later(@user.encrypted_id, params[:record_id], bbb_res[:published])
+ RecordingUpdatesJob.perform_later(@user.encrypted_id, params[:record_id])
end
render_bbb_response bbb_res
end
@@ -128,4 +154,80 @@ class BbbController < ApplicationController
@response = response
render status: @status
end
+
+ def read_body(request)
+ request.body.read.force_encoding("UTF-8")
+ end
+
+ def treat_callback_event(event)
+ eventName = (event.present? && event['header'].present?) ? event['header']['name'] : nil
+
+ # a recording is ready
+ if eventName == "publish_ended"
+ if event['payload'] && event['payload']['metadata'] && event['payload']['meeting_id']
+ token = event['payload']['metadata'][META_TOKEN]
+ record_id = event['payload']['meeting_id']
+
+ # the webhook event doesn't have all the data we need, so we need
+ # to send a getRecordings anyway
+ # TODO: if the webhooks included all data in the event we wouldn't need this
+ rec_info = bbb_get_recordings(token, record_id)
+ rec_info = rec_info[:recordings].first
+ RecordingCreatedJob.perform_later(token, parse_recording_for_view(rec_info))
+
+ # send an email to the owner of this recording, if defined
+ if Rails.configuration.mail_notifications
+ owner = User.find_by(encrypted_id: token)
+ RecordingReadyEmailJob.perform_later(owner) if owner.present?
+ end
+
+ # TODO: remove the webhook now that the meeting and recording are done
+ # remove only if the meeting is not running, otherwise the hook is needed
+ # if Rails.configuration.use_webhooks
+ # webhook_remove("#{base_url}/callback")
+ # end
+ else
+ logger.error "Bad format for event #{event}, won't process"
+ end
+ else
+ logger.info "Callback event will not be treated. Event name: #{eventName}"
+ end
+
+ render head(:ok) && return
+ end
+
+ # Validates the checksum received in a callback call.
+ # If the checksum doesn't match, renders an ok and aborts execution.
+ def validate_checksum
+ secret = ENV['BIGBLUEBUTTON_SECRET']
+ checksum = params["checksum"]
+ data = read_body(request)
+ callback_url = uri_remove_param(request.url, "checksum")
+
+ checksum_str = "#{callback_url}#{data}#{secret}"
+ calculated_checksum = Digest::SHA1.hexdigest(checksum_str)
+
+ if calculated_checksum != checksum
+ logger.error "Checksum did not match. Calculated: #{calculated_checksum}, received: #{checksum}"
+
+ # respond with 200 anyway so BigBlueButton knows the hook call was ok
+ # but abort execution
+ render head(:ok) && return
+ end
+ end
+
+ # Removes parameters from an URI
+ def uri_remove_param(uri, params = nil)
+ return uri unless params
+ params = Array(params)
+ uri_parsed = URI.parse(uri)
+ return uri unless uri_parsed.query
+ new_params = uri_parsed.query.gsub(/&/, '&').split('&').reject { |q| params.include?(q.split('=').first) }
+ uri = uri.split('?').first
+ if new_params.count > 0
+ "#{uri}?#{new_params.join('&')}"
+ else
+ uri
+ end
+ end
end
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 2eba122d..19758ad6 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -15,4 +15,8 @@
# with BigBlueButton; if not, see .
module UsersHelper
+ def is_room_owner
+ token = current_user ? current_user.encrypted_id : nil
+ token.present? && params[:id].present? && token == params[:id]
+ end
end
diff --git a/app/jobs/notify_user_waiting_job.rb b/app/jobs/notify_user_waiting_job.rb
new file mode 100644
index 00000000..0211e42a
--- /dev/null
+++ b/app/jobs/notify_user_waiting_job.rb
@@ -0,0 +1,24 @@
+# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
+#
+# Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
+#
+# This program is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free Software
+# Foundation; either version 3.0 of the License, or (at your option) any later
+# version.
+#
+# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with BigBlueButton; if not, see .
+
+class NotifyUserWaitingJob < ApplicationJob
+ queue_as :default
+
+ def perform(room, user)
+ ActionCable.server.broadcast "#{room}_meeting_updates_channel",
+ { action: 'user_waiting', user: user }
+ end
+end
diff --git a/app/jobs/recording_created_job.rb b/app/jobs/recording_created_job.rb
new file mode 100644
index 00000000..c38370d8
--- /dev/null
+++ b/app/jobs/recording_created_job.rb
@@ -0,0 +1,26 @@
+# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
+#
+# Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
+#
+# This program is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free Software
+# Foundation; either version 3.0 of the License, or (at your option) any later
+# version.
+#
+# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with BigBlueButton; if not, see .
+
+class RecordingCreatedJob < ApplicationJob
+ include BbbApi
+
+ queue_as :default
+
+ def perform(room, recording)
+ ActionCable.server.broadcast "#{room}_recording_updates_channel",
+ { action: 'create' }.merge(recording)
+ end
+end
diff --git a/app/jobs/recording_deletes_job.rb b/app/jobs/recording_deletes_job.rb
index 624ada8f..74df9abd 100644
--- a/app/jobs/recording_deletes_job.rb
+++ b/app/jobs/recording_deletes_job.rb
@@ -28,7 +28,7 @@ class RecordingDeletesJob < ApplicationJob
if !bbb_res[:recordings] || bbb_res[:messageKey] == 'noRecordings'
ActionCable.server.broadcast "#{room}_recording_updates_channel",
action: 'delete',
- record_id: record_id
+ id: record_id
break
end
sleep sleep_time
diff --git a/app/jobs/recording_ready_email_job.rb b/app/jobs/recording_ready_email_job.rb
new file mode 100644
index 00000000..465f0f38
--- /dev/null
+++ b/app/jobs/recording_ready_email_job.rb
@@ -0,0 +1,25 @@
+# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
+#
+# Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
+#
+# This program is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free Software
+# Foundation; either version 3.0 of the License, or (at your option) any later
+# version.
+#
+# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with BigBlueButton; if not, see .
+
+class RecordingReadyEmailJob < ApplicationJob
+ queue_as :default
+
+ def perform(user)
+ if user.email.present?
+ NotificationMailer.recording_ready_email(user).deliver
+ end
+ end
+end
diff --git a/app/jobs/recording_updates_job.rb b/app/jobs/recording_updates_job.rb
index 5b9903ea..9dafbb2f 100644
--- a/app/jobs/recording_updates_job.rb
+++ b/app/jobs/recording_updates_job.rb
@@ -19,22 +19,13 @@ class RecordingUpdatesJob < ApplicationJob
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",
- action: 'update',
- record_id: record_id,
- published: bbb_res[:recordings].first[:published]
- break
- end
- sleep sleep_time
- sleep_time = sleep_time * 2
- tries += 1
- end
+ def perform(room, record_id)
+ bbb_res = bbb_get_recordings(nil, record_id)
+ recording = bbb_res[:recordings].first
+ ActionCable.server.broadcast "#{room}_recording_updates_channel",
+ action: 'update',
+ id: record_id,
+ published: recording[:published],
+ listed: bbb_is_recording_listed(recording)
end
end
diff --git a/app/lib/bbb_api.rb b/app/lib/bbb_api.rb
index 090a0f3d..35f3c450 100644
--- a/app/lib/bbb_api.rb
+++ b/app/lib/bbb_api.rb
@@ -15,6 +15,10 @@
# with BigBlueButton; if not, see .
module BbbApi
+ META_LISTED = "gl-listed"
+ META_TOKEN = "gl-token"
+ META_HOOK_URL = "gl-webhooks-callback-url"
+
def bbb_endpoint
Rails.configuration.bigbluebutton_endpoint || ''
end
@@ -51,7 +55,7 @@ module BbbApi
# See if the meeting is running
begin
- bbb_meeting_info = bbb.get_meeting_info( meeting_id, nil )
+ bbb_meeting_info = bbb.get_meeting_info(meeting_id, nil)
rescue BigBlueButton::BigBlueButtonException => exc
# This means that is not created
@@ -65,7 +69,22 @@ module BbbApi
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,
+ "meta_#{BbbApi::META_LISTED}": false,
+ "meta_#{BbbApi::META_TOKEN}": meeting_token
+ }
+ meeting_options.merge!(
+ { "meta_#{BbbApi::META_HOOK_URL}": options[:hook_url] }
+ ) if options[:hook_url]
+
+ if Rails.configuration.use_webhooks
+ webhook_register(options[:hook_url], meeting_id)
+ end
+
# Create the meeting
bbb.create_meeting(options[:meeting_name], meeting_id, meeting_options)
@@ -101,7 +120,7 @@ module BbbApi
options[:recordID] = record_id
end
if meeting_id
- options[:meetingID] = (Digest::SHA1.hexdigest(Rails.application.secrets[:secret_key_base]+meeting_id)).to_s
+ options[:meetingID] = bbb_meeting_id(meeting_id)
end
res = bbb_safe_execute :get_recordings, options
@@ -148,6 +167,8 @@ module BbbApi
else
[]
end
+
+ recording[:listed] = bbb_is_recording_listed(recording)
end
res
end
@@ -168,8 +189,11 @@ module BbbApi
response_data = bbb_exception_res exc
end
- def bbb_update_recordings(id, published)
+ def bbb_update_recordings(id, published, metadata)
bbb_safe_execute :publish_recordings, id, published
+
+ params = { recordID: id }.merge(metadata)
+ bbb_safe_execute :send_api_request, "updateRecordings", params
end
def bbb_delete_recordings(id)
@@ -191,6 +215,67 @@ module BbbApi
response_data
end
+ def bbb_is_recording_listed(recording)
+ !recording[:metadata].blank? &&
+ recording[:metadata][BbbApi::META_LISTED.to_sym] == "true"
+ end
+
+ # Parses a recording as returned by getRecordings and returns it
+ # as an object as expected by the views.
+ # TODO: this is almost the same done by jbuilder templates (bbb/recordings),
+ # how to reuse them?
+ def parse_recording_for_view(recording)
+ recording[:previews] ||= []
+ previews = recording[:previews].map do |preview|
+ {
+ url: preview[:content],
+ width: preview[:width],
+ height: preview[:height],
+ alt: preview[:alt]
+ }
+ end
+ recording[:playbacks] ||= []
+ playbacks = recording[:playbacks].map do |playback|
+ {
+ type: playback[:type],
+ type_i18n: t(playback[:type]),
+ url: playback[:url],
+ previews: previews
+ }
+ end
+ {
+ id: recording[:recordID],
+ name: recording[:name],
+ published: recording[:published],
+ end_time: recording[:endTime].to_s,
+ start_time: recording[:startTime].to_s,
+ length: recording[:length],
+ listed: recording[:listed],
+ playbacks: playbacks,
+ previews: previews
+ }
+ end
+
+ def webhook_register(url, meeting_id=nil)
+ params = { callbackURL: url }
+ params.merge!({ meetingID: meeting_id }) if meeting_id.present?
+ bbb_safe_execute :send_api_request, "hooks/create", params
+ end
+
+ def webhook_remove(url)
+ res = bbb_safe_execute :send_api_request, "hooks/list"
+ if res && res[:hooks] && res[:hooks][:hook]
+ res[:hooks][:hook] = [res[:hooks][:hook]] unless res[:hooks][:hook].is_a?(Array)
+ hook = res[:hooks][:hook].select{ |h|
+ h[:callbackURL] == url
+ }.first
+ if hook.present?
+ params = { hookID: hook[:hookID] }
+ bbb_safe_execute :send_api_request, "hooks/destroy", params
+ end
+ end
+ end
+
def success_join_res(join_url)
{
returncode: true,
diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb
new file mode 100644
index 00000000..9c73a0bd
--- /dev/null
+++ b/app/mailers/notification_mailer.rb
@@ -0,0 +1,25 @@
+# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
+#
+# Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below).
+#
+# This program is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free Software
+# Foundation; either version 3.0 of the License, or (at your option) any later
+# version.
+#
+# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with BigBlueButton; if not, see .
+
+class NotificationMailer < ActionMailer::Base
+ default from: Rails.configuration.smtp_from
+
+ def recording_ready_email(user)
+ @user = user
+ @room_url = resource_url(resource: "rooms", id: user.encrypted_id)
+ mail(to: user.email, subject: t('.subject'))
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index c1a77b10..01a16ce4 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -21,6 +21,7 @@ class User < ApplicationRecord
def self.from_omniauth(auth_hash)
user = find_or_initialize_by(uid: auth_hash['uid'], provider: auth_hash['provider'])
user.username = self.send("#{auth_hash['provider']}_username", auth_hash) rescue nil
+ user.email = self.send("#{auth_hash['provider']}_email", auth_hash) rescue nil
user.name = auth_hash['info']['name']
user.save!
user
@@ -30,10 +31,18 @@ class User < ApplicationRecord
auth_hash['info']['nickname']
end
+ def self.twitter_email(auth_hash)
+ auth_hash['info']['email']
+ end
+
def self.google_username(auth_hash)
auth_hash['info']['email'].split('@').first
end
+ def self.google_email(auth_hash)
+ auth_hash['info']['email']
+ end
+
def room_url
"/rooms/#{encrypted_id}"
end
diff --git a/app/views/bbb/recordings.jbuilder b/app/views/bbb/recordings.jbuilder
index e1174e8b..b7f7d7cc 100644
--- a/app/views/bbb/recordings.jbuilder
+++ b/app/views/bbb/recordings.jbuilder
@@ -24,6 +24,7 @@ json.recordings do
json.end_time recording[:endTime]
json.published recording[:published]
json.length recording[:length]
+ json.listed recording[:listed]
json.previews do
json.array!(recording[:previews]) do |preview|
json.partial! 'preview', preview: preview
diff --git a/app/views/landing/rooms.html.erb b/app/views/landing/rooms.html.erb
index aa350b17..6edf3f77 100644
--- a/app/views/landing/rooms.html.erb
+++ b/app/views/landing/rooms.html.erb
@@ -43,13 +43,24 @@ with BigBlueButton; if not, see .