diff --git a/.gitignore b/.gitignore
index 19a4b038a..19f07d121 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ HTML
.DS_Store
coverage
dump.rdb
+working.png
diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb
index 34ccf78a7..9adf8b587 100644
--- a/ruby/lib/jam_ruby/models/recording.rb
+++ b/ruby/lib/jam_ruby/models/recording.rb
@@ -72,6 +72,17 @@ module JamRuby
quick_mixes.find{|quick_mix| quick_mix.completed && !quick_mix.cleaned }
end
+ def mix_state
+ mix.state if mix
+ end
+
+ def mix_error
+ mix.error if mix
+ end
+
+ def stream_mix
+ quick_mixes.find{|quick_mix| quick_mix.completed && !quick_mix.cleaned }
+ end
# this can probably be done more efficiently, but David needs this asap for a video
def grouped_tracks
@@ -117,8 +128,6 @@ module JamRuby
unless claimed_recordings.length > 0
destroy
end
-
-
end
def not_still_finalizing_previous
@@ -448,7 +457,6 @@ module JamRuby
# Further joining and criteria for the unioned object:
-
arel = arel.joins("INNER JOIN recordings ON recordings.id=recorded_items_all.recording_id") \
.where('recorded_items_all.user_id' => user.id) \
.where('recorded_items_all.fully_uploaded = ?', false) \
diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb
index 5216063db..a9c278566 100644
--- a/ruby/lib/jam_ruby/models/user.rb
+++ b/ruby/lib/jam_ruby/models/user.rb
@@ -306,7 +306,7 @@ module JamRuby
end
def session_count
- self.music_sessions.size
+ MusicSession.where("user_id = ? AND started_at IS NOT NULL", self.id).size
end
# count up any session you are RSVP'ed to
diff --git a/ruby/lib/jam_ruby/models/user_authorization.rb b/ruby/lib/jam_ruby/models/user_authorization.rb
index fe1a3e5f5..e83bb8db4 100644
--- a/ruby/lib/jam_ruby/models/user_authorization.rb
+++ b/ruby/lib/jam_ruby/models/user_authorization.rb
@@ -12,7 +12,7 @@ module JamRuby
validates_uniqueness_of :uid, scope: :provider
# token, secret, token_expiration can be missing
- def self.goog_auth(user)
+ def self.google_auth(user)
self
.where(:user_id => user.id)
.where(:provider => 'google_login')
diff --git a/web/Gemfile b/web/Gemfile
index fccbcc672..e2b8e8a44 100644
--- a/web/Gemfile
+++ b/web/Gemfile
@@ -37,13 +37,16 @@ gem 'compass-rails', '1.1.3' # 1.1.4 throws an exception on startup about !init
gem 'rabl', '0.11.0' # for JSON API development
gem 'gon', '~>4.1.0' # for passthrough of Ruby variables to Javascript variables
gem 'eventmachine', '1.0.3'
+gem 'faraday', '~>0.9.0'
gem 'amqp', '0.9.8'
gem 'logging-rails', :require => 'logging/rails'
gem 'omniauth', '1.1.1'
gem 'omniauth-facebook', '1.4.1'
gem 'omniauth-twitter'
gem 'omniauth-google-oauth2', '0.2.1'
-gem 'google-api-client'
+gem 'google-api-client', '0.7.1'
+gem 'google-api-omniauth', '0.1.1'
+gem 'signet', '0.5.0'
gem 'twitter'
gem 'fb_graph', '2.5.9'
gem 'sendgrid', '1.2.0'
@@ -107,7 +110,7 @@ end
group :test, :cucumber do
gem 'simplecov', '~> 0.7.1'
gem 'simplecov-rcov'
- gem 'capybara'
+ gem 'capybara', '2.4.4'
#if ENV['JAMWEB_QT5'] == '1'
# # necessary on platforms such as arch linux, where pacman -S qt5-webkit is your easiet option
# gem "capybara-webkit", :git => 'git://github.com/thoughtbot/capybara-webkit.git'
@@ -115,15 +118,17 @@ group :test, :cucumber do
gem "capybara-webkit"
#end
gem 'capybara-screenshot', '0.3.22' # 1.0.0 broke compat with rspec. maybe we need newer rspec
+ gem 'selenium-webdriver'
gem 'cucumber-rails', :require => false #, '1.3.0', :require => false
gem 'guard-spork', '0.3.2'
gem 'spork', '0.9.0'
- gem 'launchy', '2.1.0'
+ gem 'launchy', '2.1.1'
gem 'rack-test'
# gem 'rb-fsevent', '0.9.1', :require => false
# gem 'growl', '1.0.3'
gem 'poltergeist'
gem 'resque_spec'
+ #gem 'thin'
end
diff --git a/web/app/assets/images/google_signin.png b/web/app/assets/images/google_signin.png
new file mode 100644
index 000000000..408190f1f
Binary files /dev/null and b/web/app/assets/images/google_signin.png differ
diff --git a/web/app/assets/javascripts/accounts_session_detail.js b/web/app/assets/javascripts/accounts_session_detail.js
index 53eb195f7..a3debe85e 100644
--- a/web/app/assets/javascripts/accounts_session_detail.js
+++ b/web/app/assets/javascripts/accounts_session_detail.js
@@ -92,7 +92,9 @@
.done(function(openSlots) {
if (openSlots) {
if (openSlots.length === 0) {
- ui.launchRsvpCreateSlotDialog(sessionData.id, instrumentIds.split('|'), userName);
+ ui.launchRsvpCreateSlotDialog(sessionData.id, instrumentIds.split('|'), userName, function() {
+ approve(rsvpId, params);
+ });
}
else {
var arrInstrumentIds = instrumentIds.split('|');
@@ -122,7 +124,9 @@
}
}
else {
- ui.launchRsvpCreateSlotDialog(sessionData.id, instrumentIds.split('|'), userName);
+ ui.launchRsvpCreateSlotDialog(sessionData.id, instrumentIds.split('|'), userName, function() {
+ approve(rsvpId, params);
+ });
}
});
}
diff --git a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js
index 5863d54e8..f0d24a27d 100644
--- a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js
+++ b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js
@@ -122,13 +122,17 @@
var description = $('#recording-finished-dialog form textarea[name=description]').val();
var genre = $('#recording-finished-dialog form select[name=genre]').val();
var is_public = $('#recording-finished-dialog form input[name=is_public]').is(':checked')
+ var save_video = $('#recording-finished-dialog form input[name=save_video]').is(':checked')
+ var upload_to_youtube = $('#recording-finished-dialog form input[name=upload_to_youtube]').is(':checked')
rest.claimRecording({
id: recording.id,
name: name,
description: description,
genre: genre,
- is_public: is_public
+ is_public: is_public,
+ save_video: save_video,
+ upload_to_youtube: upload_to_youtube
})
.done(function () {
$dialog.data('result', {keep:true});
@@ -151,6 +155,12 @@
var $is_public_errors = context.JK.format_errors('is_public', errors);
if ($is_public_errors) $('#recording-finished-dialog form input[name=is_public]').closest('div.field').addClass('error').end().after($is_public_errors);
+ var $save_video_errors = context.JK.format_errors('save_video', errors);
+ if ($save_video_errors) $('#recording-finished-dialog form input[name=save_video]').closest('div.field').addClass('error').end().after($save_video_errors);
+
+ var $upload_to_youtube_errors = context.JK.format_errors('upload_to_youtube', errors);
+ if ($upload_to_youtube_errors) $('#recording-finished-dialog form input[name=upload_to_youtube]').closest('div.field').addClass('error').end().after($upload_to_youtube_errors);
+
var recording_error = context.JK.get_first_error('recording_id', errors);
if (recording_error) context.JK.showErrorDialog(app, "Unable to claim recording.", recording_error);
@@ -223,8 +233,9 @@
function initializeButtons() {
var isPublic = $('#recording-finished-dialog input[name="is_public"]');
-
context.JK.checkbox(isPublic);
+ context.JK.checkbox($('#recording-finished-dialog input[name="save_video"]'));
+ context.JK.checkbox($('#recording-finished-dialog input[name="upload_to_youtube"]'));
}
function initialize() {
diff --git a/web/app/assets/javascripts/hoverMusician.js b/web/app/assets/javascripts/hoverMusician.js
index 20b44cbfc..7cac6517e 100644
--- a/web/app/assets/javascripts/hoverMusician.js
+++ b/web/app/assets/javascripts/hoverMusician.js
@@ -76,7 +76,7 @@
if (response.internet_score && response.internet_score.length > 0) {
if (response.internet_score[0].score && !isNaN(response.internet_score[0].score)) {
var internetScore = parseInt(response.internet_score[0].score);
- fullScore = (response.internet_score + calculateAudioLatency(response.my_audio_latency) + calculateAudioLatency(response.last_jam_audio_latency)) / 2;
+ fullScore = (internetScore + calculateAudioLatency(response.my_audio_latency) + calculateAudioLatency(response.last_jam_audio_latency)) / 2;
}
}
diff --git a/web/app/assets/javascripts/hoverRecording.js b/web/app/assets/javascripts/hoverRecording.js
index d58e72be5..5321df114 100644
--- a/web/app/assets/javascripts/hoverRecording.js
+++ b/web/app/assets/javascripts/hoverRecording.js
@@ -58,7 +58,7 @@
instrumentHtml = '
';
$.each(val.instrument_ids, function(index, val) {
- instrumentHtml += '  + ') ';
+ instrumentHtml += '  + ') ';
});
instrumentHtml += ' | ';
diff --git a/web/app/assets/javascripts/hoverSession.js b/web/app/assets/javascripts/hoverSession.js
index 5ba742219..d64dfee71 100644
--- a/web/app/assets/javascripts/hoverSession.js
+++ b/web/app/assets/javascripts/hoverSession.js
@@ -25,7 +25,7 @@
instrumentHtml = '';
var instruments = val.instruments.split("|");
$.each(instruments, function(index, instrument) {
- instrumentHtml += '  + ') ';
+ instrumentHtml += '  + ') ';
});
instrumentHtml += ' | ';
diff --git a/web/app/assets/javascripts/sessionList.js b/web/app/assets/javascripts/sessionList.js
index a54ba7cec..2dae2c083 100644
--- a/web/app/assets/javascripts/sessionList.js
+++ b/web/app/assets/javascripts/sessionList.js
@@ -135,8 +135,7 @@
});
if (showJoinLink) {
- // wire up the Join Link to the T&Cs dialog
-
+ // wire up the Join Link to the T&Cs dialog
$('.join-link', $parentRow).click(function(evt) {
if(!context.JK.guardAgainstBrowser(app)) {
return false;
diff --git a/web/app/assets/javascripts/session_utils.js b/web/app/assets/javascripts/session_utils.js
index 0c22b0108..2fd34dd94 100644
--- a/web/app/assets/javascripts/session_utils.js
+++ b/web/app/assets/javascripts/session_utils.js
@@ -138,23 +138,9 @@
return;
}
- if ("invitations" in session) {
- var invitation;
- // user has invitations for this session
- for (var i=0; i < session.invitations.length; i++) {
- invitation = session.invitations[i];
- // session contains an invitation for this user
- if (invitation.receiver_id === context.JK.currentUserId) {
- hasInvitation = true;
- break;
- }
- }
- }
-
if (session) {
- // if user has an invitation, always open terms and allow joining regardless of settings
- if (hasInvitation) {
- logger.debug("Found invitation for user " + context.JK.currentUserId + ", session " + sessionId);
+ if (session.can_join) {
+ logger.debug("Found invitation or approved RSVP for user " + context.JK.currentUserId + ", session " + sessionId);
openJoinSessionTerms(sessionId);
}
else {
diff --git a/web/app/assets/stylesheets/dialogs/recordingFinishedDialog.css.scss b/web/app/assets/stylesheets/dialogs/recordingFinishedDialog.css.scss
index a738fa57b..b8f27aede 100644
--- a/web/app/assets/stylesheets/dialogs/recordingFinishedDialog.css.scss
+++ b/web/app/assets/stylesheets/dialogs/recordingFinishedDialog.css.scss
@@ -1,8 +1,9 @@
#recording-finished-dialog {
width:1000px;
height:auto;
- div[purpose=description], div[purpose=is_public] {
+ div[purpose=description] {
margin-top:20px;
+ margin-bottom: 10px;
}
label[for=is_public], label[for=playback-mode-preview-all], label[for=playback-mode-preview-me] {
@@ -23,11 +24,29 @@
margin-top:20px;
}
- div[purpose=is_public] .icheckbox_minimal {
- display:inline-block;
- position:relative;
- top:3px;
- margin-right:3px;
+ div[purpose=is_public], div[purpose=upload_to_youtube], div[purpose=save_video] {
+ .icheckbox_minimal {
+ display:inline-block;
+ position:relative;
+ top:1px;
+ margin-top:3px;
+ margin-bottom:1px;
+ margin-right:2px;
+ }
+ label {
+ display: inline-block;
+ margin-bottom:4px;
+ margin-right:2px;
+ }
+ clear: left;
+ }
+
+ .google_login_button {
+ vertical-align: middle;
+ }
+
+ .signed_in_to_google {
+ color: yellow;
}
}
diff --git a/web/app/controllers/api_recordings_controller.rb b/web/app/controllers/api_recordings_controller.rb
index f08789905..b286bc8a2 100644
--- a/web/app/controllers/api_recordings_controller.rb
+++ b/web/app/controllers/api_recordings_controller.rb
@@ -1,8 +1,9 @@
class ApiRecordingsController < ApiController
-
before_filter :api_signed_in_user, :except => [ :add_like ]
- before_filter :look_up_recording, :only => [ :show, :stop, :claim, :discard, :keep, :delete_claim ]
- before_filter :parse_filename, :only => [ :download, :upload_next_part, :upload_sign, :upload_part_complete, :upload_complete ]
+
+ before_filter :lookup_recording, :only => [ :show, :stop, :claim, :discard, :keep, :delete_claim ]
+ before_filter :lookup_recorded_track, :only => [ :download, :upload_next_part, :upload_sign, :upload_part_complete, :upload_complete ]
+ before_filter :lookup_recorded_video, :only => [ :video_upload_sign, :video_upload_start, :video_upload_complete ]
before_filter :lookup_stream_mix, :only => [ :upload_next_part_stream_mix, :upload_sign_stream_mix, :upload_part_complete_stream_mix, :upload_complete_stream_mix ]
respond_to :json
@@ -224,7 +225,26 @@ class ApiRecordingsController < ApiController
end
end
+ # POST /api/recordings/:id/videos/:video_id/upload_sign
+ def video_upload_sign
+ length = params[:length]
+ @youtube_client.upload_sign(current_user, @recorded_video.url, length)
+ end
+ # POST /api/recordings/:id/videos/:video_id/upload_complete
+ def video_upload_start
+ length = params[:length]
+ @youtube_client.get_upload_status(current_user, @recorded_video.url, length)
+ end
+
+ # POST /api/recordings/:id/videos/:video_id/upload_complete
+ def video_upload_complete
+ if @youtube_client.complete_upload(@recorded_video)
+ render :status => 200
+ else
+ render :status => 422
+ end
+ end
def upload_next_part_stream_mix
length = params[:length]
@@ -281,7 +301,13 @@ class ApiRecordingsController < ApiController
end
private
- def parse_filename
+
+ def lookup_recording
+ @recording = Recording.find(params[:id])
+ raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recording.has_access?(current_user)
+ end
+
+ def lookup_recorded_track
@recorded_track = RecordedTrack.find_by_recording_id_and_client_track_id!(params[:id], params[:track_id])
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_track.recording.has_access?(current_user)
end
@@ -291,9 +317,9 @@ class ApiRecordingsController < ApiController
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @quick_mix.recording.has_access?(current_user)
end
- def look_up_recording
- @recording = Recording.find(params[:id])
- raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recording.has_access?(current_user)
+ def lookup_recorded_video
+ @recorded_video = RecordedVideo.find_by_recording_id_and_client_video_source_id!(params[:id], params[:video_id])
+ raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_video.recording.has_access?(current_user)
end
-end
+end # class
diff --git a/web/app/controllers/gmail_controller.rb b/web/app/controllers/gmail_controller.rb
index e4f1e1906..16c18cad9 100644
--- a/web/app/controllers/gmail_controller.rb
+++ b/web/app/controllers/gmail_controller.rb
@@ -6,7 +6,7 @@ class GmailController < ApplicationController
render :nothing => true, :status => 404
return
end
- authorization = UserAuthorization.goog_auth(current_user)
+ authorization = UserAuthorization.google_auth(current_user)
if authorization.empty?
render :nothing => true, :status => 404
return
diff --git a/web/app/controllers/sessions_controller.rb b/web/app/controllers/sessions_controller.rb
index 7a2bb8ca7..6c772cd4a 100644
--- a/web/app/controllers/sessions_controller.rb
+++ b/web/app/controllers/sessions_controller.rb
@@ -150,6 +150,10 @@ class SessionsController < ApplicationController
render 'oauth_complete', :layout => "landing"
end
+ def has_google_auth
+ render :json => {has_google_auth: (!!current_user && !!UserAuthorization.google_auth(current_user).first)}
+ end
+
def redirect_after_signin(default)
redirect_to(params['redirect-to'].blank? ? default : params['redirect-to'])
end
diff --git a/web/app/views/api_music_sessions/show.rabl b/web/app/views/api_music_sessions/show.rabl
index 98f642a7f..e4c264a7c 100644
--- a/web/app/views/api_music_sessions/show.rabl
+++ b/web/app/views/api_music_sessions/show.rabl
@@ -15,6 +15,10 @@ else
attributes :id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter, :max_score
+ node :can_join do |session|
+ session.can_join?(current_user, true)
+ end
+
node :genres do |item|
[item.genre.description] # XXX: need to return single genre; not array
end
diff --git a/web/app/views/api_music_sessions/show_history.rabl b/web/app/views/api_music_sessions/show_history.rabl
index e027c0874..c86e6e04f 100644
--- a/web/app/views/api_music_sessions/show_history.rabl
+++ b/web/app/views/api_music_sessions/show_history.rabl
@@ -21,6 +21,10 @@ else
:language, :recurring_mode, :language_description, :scheduled_start_date, :access_description, :timezone, :timezone_id, :timezone_description,
:musician_access_description, :fan_access_description, :session_removed_at, :legal_policy, :open_rsvps, :is_unstructured_rsvp?
+ node :can_join do |session|
+ session.can_join?(current_user, true)
+ end
+
node :share_url do |history|
unless history.share_token.nil?
share_token_url(history.share_token.token)
diff --git a/web/app/views/dialogs/_recordingFinishedDialog.html.erb b/web/app/views/dialogs/_recordingFinishedDialog.html.erb
deleted file mode 100644
index 989e7243f..000000000
--- a/web/app/views/dialogs/_recordingFinishedDialog.html.erb
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-
- <%= image_tag "content/recordbutton-off.png", {:height => 20, :width => 20, :class => 'content-icon'} %>
-
recording finished
-
-
-
- Fill out the fields below and click the "SAVE" button to save this recording to your library. If you do not want to
- keep the recording, click the "DISCARD" button.
-
-
-
-
-
-
- Preview Recording:
-
- <%= render "clients/play_controls" %>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/web/app/views/dialogs/_recordingFinishedDialog.html.haml b/web/app/views/dialogs/_recordingFinishedDialog.html.haml
new file mode 100644
index 000000000..fc3d53eff
--- /dev/null
+++ b/web/app/views/dialogs/_recordingFinishedDialog.html.haml
@@ -0,0 +1,52 @@
+/ Invitation Dialog
+#recording-finished-dialog.dialog.recordingFinished-overlay.ftue-overlay.tall{:layout => "dialog", "layout-id" => "recordingFinished"}
+ .content-head
+ = image_tag "content/recordbutton-off.png", {:height => 20, :width => 20, :class => 'content-icon'}
+ %h1 recording finished
+ .dialog-inner
+ Fill out the fields below and click the "SAVE" button to save this recording to your library. If you do not want to
+ keep the recording, click the "DISCARD" button.
+ %br/
+ %br/
+ %form.left.w40.mr20
+ .left.w50.mr20
+ .field.w100
+ %label{:for => "name"} Recording name:
+ %br/
+ %input#claim-recording-name.w100{:name => "name", :type => "text"}/
+ .right.w40.genre-selector
+ .field
+ / genre box
+ %label{:for => "genre"} Genre:
+ %br/
+ %select{:name => "genre"}
+ .field.w100.left{:purpose => "description"}
+ %label{:for => "description"} Description:
+ %textarea#claim-recording-description.w100{:name => "description"}
+ / TODO VRFS-1849: Uncomment this when rest of feature developed:
+ / .field.left{:purpose => "save_video"}
+ / %input{:checked => "checked", :name => "save_video", :type => "checkbox"}/
+ / %label{:for => "save_video"} Save Video to Computer
+ / .field.left{:purpose => "upload_to_youtube"}
+ / %span
+ / %input{:checked => "checked", :name => "upload_to_youtube", :type => "checkbox"}/
+ / %label{:for => "upload_to_youtube"} Upload Video to YouTube
+ / %span
+ / = render(:partial => "shared/google_login")
+ .field.left{:purpose => "is_public"}
+ %input{:checked => "checked", :name => "is_public", :type => "checkbox"}/
+ %label{:for => "is_public"} Public Recording
+ / <
+ .left.w50.ml30
+ Preview Recording:
+
+ \#{render "clients/play_controls"}
+ %br/
+ %br/
+ %br{:clear => "left"}/
+ %br/
+ .right
+ %a#discard-session-recording.button-grey{:href => "#"}> DISCARD
+ \
+ %a#keep-session-recording.button-orange{:href => "#"} SAVE
+ %br{:clear => "all"}/
\ No newline at end of file
diff --git a/web/app/views/layouts/client.html.erb b/web/app/views/layouts/client.html.erb
index 2565a4ec7..2c4238803 100644
--- a/web/app/views/layouts/client.html.erb
+++ b/web/app/views/layouts/client.html.erb
@@ -30,6 +30,7 @@
<% else %>
<%= render "layouts/social_meta" %>
<% end %>
+ <%= yield(:extra_js) %>
<%= yield %>
diff --git a/web/app/views/shared/_google_login.html.slim b/web/app/views/shared/_google_login.html.slim
new file mode 100644
index 000000000..a190c6366
--- /dev/null
+++ b/web/app/views/shared/_google_login.html.slim
@@ -0,0 +1,26 @@
+-content_for :extra_js do
+ javascript:
+ // Check for google authorization using AJAX and show/hide the
+ // google login button / "signed in" label as appropriate:
+ $(window).on('focus', function() {
+ $.ajax({
+ type: "GET",
+ dataType: "json",
+ url: "/auth/has_google_auth"
+ }).success(function(data) {
+ if(data.has_google_auth) {
+ $("input.google_login_button").addClass("hidden")
+ $("span.signed_in_to_google").removeClass("hidden")
+ if (window._oauth_win) {
+ window._oauth_win.close()
+ }
+ } else {
+ $("span.signed_in_to_google").addClass("hidden")
+ $("input.google_login_button").removeClass("hidden")
+ }
+ })
+ });
+
+-google_auth = (current_user.nil?) ? nil : !!JamRuby::UserAuthorization.google_auth(current_user).first
+span.signed_in_to_google class=((!google_auth) ? "hidden" : "") ="(Signed in)"
+input.google_login_button class=((google_auth) ? "hidden" : "") type='image' onclick='window._oauth_win = window.open("/auth/google_login", "_blank", "height=500,width=500,menubar=no,resizable=no,status=no");' src="/assets/google_signin.png" height="30px"
diff --git a/web/config/application.rb b/web/config/application.rb
index 76b4242a0..ba330052b 100644
--- a/web/config/application.rb
+++ b/web/config/application.rb
@@ -143,6 +143,7 @@ if defined?(Bundler)
# google api keys
config.google_client_id = '785931784279-gd0g8on6sc0tuesj7cu763pitaiv2la8.apps.googleusercontent.com'
config.google_secret = 'UwzIcvtErv9c2-GIsNfIo7bA'
+ config.google_email = '785931784279-gd0g8on6sc0tuesj7cu763pitaiv2la8@developer.gserviceaccount.com'
if Rails.env == 'production'
config.desk_url = 'https://jamkazam.desk.com'
diff --git a/web/config/routes.rb b/web/config/routes.rb
index cea8bdf29..0454b051e 100644
--- a/web/config/routes.rb
+++ b/web/config/routes.rb
@@ -30,6 +30,7 @@ SampleApp::Application.routes.draw do
# oauth
match '/auth/:provider/callback', :to => 'sessions#oauth_callback'
match '/auth/failure', :to => 'sessions#failure'
+ match '/auth/has_google_auth', :to => 'sessions#has_google_auth'
# session info page
match '/sessions/:id/details' => 'music_sessions#session_info', :via => :get, :as => 'music_scheduled_session_detail'
@@ -403,6 +404,8 @@ SampleApp::Application.routes.draw do
match '/recordings/:id/comments' => 'api_recordings#add_comment', :via => :post, :as => 'api_recordings_add_comment'
match '/recordings/:id/likes' => 'api_recordings#add_like', :via => :post, :as => 'api_recordings_add_like'
match '/recordings/:id/discard' => 'api_recordings#discard', :via => :post, :as => 'api_recordings_discard'
+
+ # Recordings - recorded_tracks
match '/recordings/:id/tracks/:track_id' => 'api_recordings#show_recorded_track', :via => :get, :as => 'api_recordings_show_recorded_track'
match '/recordings/:id/tracks/:track_id/download' => 'api_recordings#download', :via => :get, :as => 'api_recordings_download'
match '/recordings/:id/tracks/:track_id/upload_next_part' => 'api_recordings#upload_next_part', :via => :get
@@ -410,9 +413,16 @@ SampleApp::Application.routes.draw do
match '/recordings/:id/tracks/:track_id/upload_part_complete' => 'api_recordings#upload_part_complete', :via => :post
match '/recordings/:id/tracks/:track_id/upload_complete' => 'api_recordings#upload_complete', :via => :post
match '/recordings/:id/stream_mix/upload_next_part' => 'api_recordings#upload_next_part_stream_mix', :via => :get
+
+ # Recordings - stream_mix
match '/recordings/:id/stream_mix/upload_sign' => 'api_recordings#upload_sign_stream_mix', :via => :get
match '/recordings/:id/stream_mix/upload_part_complete' => 'api_recordings#upload_part_complete_stream_mix', :via => :post
match '/recordings/:id/stream_mix/upload_complete' => 'api_recordings#upload_complete_stream_mix', :via => :post
+
+ # Recordings - recorded_videos
+ match '/recordings/:id/tracks/:video_id/upload_sign' => 'api_recordings#video_upload_sign', :via => :get
+ match '/recordings/:id/videos/:video_id/upload_start' => 'api_recordings#video_upload_start', :via => :post
+ match '/recordings/:id/videos/:video_id/upload_complete' => 'api_recordings#video_upload_complete', :via => :post
# Claimed Recordings
match '/claimed_recordings' => 'api_claimed_recordings#index', :via => :get
diff --git a/web/lib/youtube_client.rb b/web/lib/youtube_client.rb
new file mode 100644
index 000000000..635aa1a63
--- /dev/null
+++ b/web/lib/youtube_client.rb
@@ -0,0 +1,387 @@
+require 'faraday'
+#require 'thin'
+require 'launchy'
+require 'cgi'
+require 'json'
+require 'google/api_client'
+require 'google/api_client/client_secrets'
+require 'google/api_client/auth/installed_app'
+require 'socket' # Provides TCPServer and TCPSocket classes
+# require 'youtube_client'; c = YouTubeClient.new
+# Youtube API functionality:
+module JamRuby
+ class YouTubeClient
+ attr_accessor :client
+ attr_accessor :api
+ attr_accessor :request
+ attr_accessor :server
+ attr_accessor :socket
+ attr_accessor :config
+ attr_accessor :redirect_uri
+
+ def initialize()
+ Rails.logger.info("Initializing client...")
+ self.config = Rails.application.config
+ self.redirect_uri='http://localhost:2112/auth/google_login/callback'
+ self.client = Google::APIClient.new(
+ :application_name => 'JamKazam',
+ :application_version => '1.0.0'
+ )
+
+ youtube = client.discovered_api('youtube', 'v3')
+ # client.authorization = nil
+ # result = client.execute
+ # :key => config.youtube_developer_key,
+ # :api_method => youtube.videos.list,
+ # :parameters => {:id => '', :part => 'snippet'}
+ # result = JSON.parse(result.data.to_json)
+ end
+
+ # Return a login URL that will show a web page with
+ def get_login_url(username=nil)
+ uri = "https://accounts.google.com/o/oauth2/auth"
+ uri << "?scope=#{CGI.escape('https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtube.upload https://gdata.youtube.com email profile ')}" # # https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtube.upload
+ uri << "&client_id=#{CGI.escape(self.config.google_email)}"
+ #uri << "&client_secret=#{CGI.escape(self.config.google_secret)}"
+ uri << "&response_type=code"
+ uri << "&access_type=online"
+ uri << "&prompt=consent"
+ uri << "&state=4242"
+ uri << "&redirect_uri=#{redirect_uri}"
+ if username.present?
+ uri << "&login_hint=#{(username)}"
+ end
+ uri
+ end
+
+ # Contacts youtube and prepares an upload to youtube. This
+ # process is somewhat painful, even in ruby, so we do the preparation
+ # and the client does the actual upload using the URL returned:
+
+ # https://developers.google.com/youtube/v3/docs/videos/insert
+ # https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol
+ def upload_sign(user, filename, length)
+ raise ArgumentError, "Length is required and should be > 0" if length.to_i.zero?
+ # Something like this:
+ # POST /upload/youtube/v3/videos?uploadType=resumable&part=snippet,status,contentDetails HTTP/1.1
+ # Host: www.googleapis.com
+ # Authorization: Bearer AUTH_TOKEN
+ # Content-Length: 278
+ # Content-Type: application/json; charset=UTF-8
+ # X-Upload-Content-Length: 3000000
+ # X-Upload-Content-Type: video/*
+
+ # {
+ # "snippet": {
+ # "title": "My video title",
+ # "description": "This is a description of my video",
+ # "tags": ["cool", "video", "more keywords"],
+ # "categoryId": 22
+ # },
+ # "status": {
+ # "privacyStatus": "public",
+ # "embeddable": True,
+ # "license": "youtube"
+ # }
+ # }
+ auth = UserAuthorization.google_auth(user).first
+ if auth.nil? || auth.token.nil?
+ raise SecurityError, "No current google token found for user #{user}"
+ end
+
+ video_data = {
+ "snippet"=> {
+ "title"=> filename,
+ "description"=> filename,
+ "tags"=> ["cool", "video", "more keywords"],
+ "categoryId"=>1
+ },
+ "status"=> {
+ "privacyStatus"=> "public",
+ "embeddable"=> true,
+ "license"=> "youtube"
+ }
+ }
+
+ conn = Faraday.new(:url =>"https://www.googleapis.com",:ssl => {:verify => false}) do |faraday|
+ faraday.request :url_encoded
+ faraday.response :logger
+ faraday.adapter Faraday.default_adapter
+ end
+
+ video_json=video_data.to_json
+ # result = conn.post do |req|
+ # req.url('/upload/youtube/v3/videos?uploadType=resumable&part=snippet,status')
+ # req.headers['authorization']="bearer #{(auth.token)}"
+ # req.headers['content-type']='application/json;charset=utf-8'
+ # req.headers['x-Upload-Content-Length']="#{length}"
+ # req.headers['x-upload-content-type']="video/*"
+ # req.body = video_json
+ # end
+ # access_token=#{CGI.escape(auth.token)}
+ result = conn.post("/upload/youtube/v3/videos?access_token=#{CGI.escape(auth.token)}&uploadType=resumable&part=snippet,status,contentDetails",
+ video_json,
+ {
+ # 'client_id'=>"#{(self.config.google_email)}",
+ # 'client_secret'=>config.google_secret,
+ #'Authorization'=>"bearer #{(auth.token)}",
+ 'content-type'=>'application/json;charset=utf-8',
+ 'x-Upload-Content-Length'=>"#{length}",
+ 'x-upload-content-type'=>"video/*"
+ }
+ )
+
+ #puts result.inspect
+ # Response should something look like:
+ # HTTP/1.1 200 OK
+ # Location: https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&upload_id=xa298sd_f&part=snippet,status,contentDetails
+ # Content-Length: 0
+
+ if (result.nil? || result.status!=200 || result.headers['location'].blank?)
+ msg = "Failed signing with status=#{result.status} #{result.inspect}: "
+ if result.body.present? && result.body.length > 2
+ msg << result.body.inspect# JSON.parse(result.body).inspect
+ end
+
+ # TODO: how to test for this:
+ # If reason is "youtubeSignupRequired"
+ # If the user's youtube account is unlinked, they'll have to go here.
+ # http://m.youtube.com/create_channel. With v3, there is no automated way to do this.
+ raise msg
+ else
+ # This has everything one needs to start the upload to youtube:
+ {
+ "method" => "PUT",
+ "url" => result.headers['location'],
+ "Authorization" => "Bearer #{auth.token}",
+ "Content-Length" => length,
+ "Content-Type" => "video/*"
+ }
+ end
+
+ # This has everything one needs to start the upload to youtube:
+ end
+
+ # https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol#Check_Upload_Status
+ def get_upload_status(user, upload_url, length)
+ auth = UserAuthorization.google_auth(user).first
+ if auth.nil? || auth.token.nil?
+ raise SecurityError, "No current google token found for user #{user}"
+ end
+
+ # PUT UPLOAD_URL HTTP/1.1
+ # Authorization: Bearer AUTH_TOKEN
+ # Content-Length: 0
+ # Content-Range: bytes */CONTENT_LENGTH
+ RestClient.put(upload_url, nil, {
+ 'Authorization' => "Bearer #{auth.token}",
+ 'Content-Length'=> "0",
+ 'Content-Range' => "bytes */#{length}"
+ }) do |response, request, result|
+ # puts "response: #{response.class}: #{response.code} / #{response.headers} / #{response.body}"
+ # Result looks like this:
+ # 308 Resume Incomplete
+ # Content-Length: 0
+ # Range: bytes=0-999999
+ case(response.code)
+ when 200..207
+ result_hash = {
+ "offset" => 0,
+ "length" => length,
+ "status" => response.code
+ }
+ when 308
+ range_str = response.headers['Range']
+ if range_str.nil?
+ range = 0..length
+ else
+ range = range_str.split("-")
+ end
+ result_hash = {
+ "offset" => range.first.to_i,
+ "length" => range.last.to_i,
+ "status" => response.code
+ }
+ else
+ raise "Unexpected status from youtube: [#{response.code}] with headers: #{response.headers.inspect}"
+ end
+
+ result_hash
+ end
+ end
+
+ # @return true if file specified by URL uploaded, false otherwise
+ def verify_upload(user, upload_url, length)
+ status_hash=get_upload_status(user, upload_url, length)
+ (status_hash['status']>=200 && status_hash['status']<300)
+ end
+
+ def complete_upload(recorded_video)
+ if (verify_upload(recorded_video.user, recorded_video.url, recorded_video.length))
+ recorded_video.update_attribute(:fully_uploaded, true)
+ else
+ false
+ end
+ end
+
+ # This will also sign in and prompt for login as necessary;
+ # currently requires the server to be running at localhost:3000
+ def signin_flow()
+ config = Rails.application.config
+
+ self.client = Google::APIClient.new(
+ :application_name => 'JamKazam',
+ :application_version => '1.0.0'
+ )
+
+ flow = Google::APIClient::InstalledAppFlow.new(
+ :client_id => config.google_client_id,
+ :client_secret => config.google_secret,
+ :redirect_uri=>redirect_uri,
+ :scope => 'email profile'
+ )
+
+ self.client.authorization = flow.authorize
+ end
+
+ # Must manually confirm to obtain refresh token:
+ # 4/ZwtU8nNgiEiu2JlJMrmnnw.Qo7Zys7XjRoZPm8kb2vw2M2j2ZEskgI
+ def get_refresh_token
+ config = Rails.application.config
+ conn = Faraday.new(:url => 'https://accounts.google.com',:ssl => {:verify => false}) do |faraday|
+ faraday.request :url_encoded
+ faraday.response :logger
+ faraday.adapter Faraday.default_adapter
+ end
+
+ wait_for_callback do |refresh_token|
+ Rails.logger.info("The refresh_token is #{refresh_token}")
+ end
+
+
+ result = conn.get '/o/oauth2/auth', {
+ 'scope'=>'email profile',
+ 'client_id'=>config.google_client_id,
+ 'response_type'=>"code",
+ 'access_type'=>"offline",
+ 'redirect_uri'=>redirect_uri
+ }
+ end
+
+ def get_access_token(refresh_token)
+ refresh_token = "4/g9uZ8S4lq2Bj1J8PPIkgOFKhTKmCHSmRe68iHA75hRg.gj8Nt5bpVYQdPm8kb2vw2M23tnRnkgI"
+ #refresh_token = "4/ZwtU8nNgiEiu2JlJMrmnnw.Qo7Zys7XjRoZPm8kb2vw2M2j2ZEskgI"
+ config = Rails.application.config
+ conn = Faraday.new(:url => 'https://accounts.google.com',:ssl => {:verify => false}) do |faraday|
+ faraday.request :url_encoded
+ faraday.response :logger
+ faraday.adapter Faraday.default_adapter
+ end
+
+ wait_for_callback do |access_token|
+ Rails.logger.info("The access_token is #{access_token}")
+ #self.server.stop()
+ end
+
+ result = conn.post '/o/oauth2/token', nil, {
+ 'scope'=>'email profile',
+ 'client_id'=>config.google_client_id,
+ 'client_secret'=>config.google_secret,
+ 'refresh_token'=>refresh_token,
+ #'response_type'=>"code",
+ 'grant_type'=>"refresh_token",
+ #'access_type'=>"offline",
+ 'redirect_uri'=>redirect_uri
+ }
+
+ Rails.logger.info("REsult: #{result.inspect}\n\n")
+ end
+
+ def wait_for_callback(port=3000)
+ shutdown()
+ self.server = Thread.new {
+ Rails.logger.info("STARTING SERVER THREAD...")
+ tcp_server = TCPServer.new('localhost', port)
+
+ self.socket = tcp_server.accept
+ if self.socket
+ request = self.socket.gets
+ Rails.logger.info("REQUEST: #{request}")
+
+ params=CGI.parse(request)
+ code = params['code'].first
+ # Whack the end part:
+ access_code = code ? code.split(" ").first : ""
+
+ status = (access_code.present?) ? 'OK' : 'EMPTY'
+ Rails.logger.info("access_code is #{status}")
+ token=exchange_for_token(access_code)
+ yield(token)
+
+ response = "#{status}\n"
+ self.socket.print "HTTP/1.1 200 OK\r\n" +
+ "Content-Type: text/plain\r\n" +
+ "Content-Length: #{response.bytesize}\r\n" +
+ "Connection: close\r\n"
+
+ self.socket.print "\r\n"
+ self.socket.print response
+ self.socket.close
+ socket=nil
+ end
+ }
+ end
+
+
+ def exchange_for_token(access_code)
+ #puts "EXCHANGING token for code: [#{access_code}] #{access_code.class}"
+
+ conn = Faraday.new(:url =>"https://accounts.google.com",:ssl => {:verify => false}) do |faraday|
+ faraday.request :url_encoded
+ # faraday.request :multipart
+ faraday.response :logger
+ faraday.adapter Faraday.default_adapter
+ end
+
+ exchange_parms={
+ 'grant_type'=>'authorization_code',
+ 'code'=>(access_code),
+ 'client_id'=>(config.google_email),#CGI.escape(config.google_client_id),
+ 'client_secret'=>(config.google_secret),
+ 'redirect_uri'=>(redirect_uri),
+ }
+
+ result = conn.post('/o/oauth2/token', exchange_parms)
+ if result.body.nil? || result.body.blank?
+ raise "Result not in correct form: [#{result.body}]"
+ end
+
+ body_hash = JSON.parse(result.body)
+
+ #puts "RESULT #{result.body.class}: #{result.body}"
+ #puts "EXCHANGING for token: [#{body_hash['access_token']}]"
+ body_hash['access_token']
+ end
+
+ # shutdown
+ def shutdown()
+ Rails.logger.info("Stopping oauth server...")
+ #Thread.kill(self.server)
+ if (self.socket)
+ begin
+ self.socket.close
+ rescue IOError
+ # Expected for most cases:
+ Rails.logger.info("Socket already closed.")
+ end
+ self.socket = nil
+ end
+
+ # if (self.server)
+ # Thread.kill(self.server)
+ # self.server = nil
+ # end
+ end
+ end # class
+end # module
+
\ No newline at end of file
diff --git a/web/spec/factories.rb b/web/spec/factories.rb
index bf81273e7..b130450b6 100644
--- a/web/spec/factories.rb
+++ b/web/spec/factories.rb
@@ -272,9 +272,23 @@ FactoryGirl.define do
factory :track, :class => JamRuby::Track do
sound "mono"
sequence(:client_track_id) { |n| "client_track_id_seq_#{n}"}
-
end
+ factory :video_source, :class => JamRuby::VideoSource do
+ #client_video_source_id "test_source_id"
+ sequence(:client_video_source_id) { |n| "client_video_source_id#{n}"}
+ end
+
+ factory :recording, :class => JamRuby::Recording do
+ association :owner, factory: :user
+ association :music_session, factory: :active_music_session
+
+ factory :recording_with_track do
+ before(:create) { |recording|
+ recording.recorded_tracks << FactoryGirl.create(:recorded_track, recording: recording, user: recording.owner)
+ }
+ end
+ end
factory :recorded_track, :class => JamRuby::RecordedTrack do
instrument JamRuby::Instrument.first
@@ -289,16 +303,13 @@ FactoryGirl.define do
association :recording, factory: :recording
end
- factory :recording, :class => JamRuby::Recording do
-
- association :owner, factory: :user
- association :music_session, factory: :active_music_session
-
- factory :recording_with_track do
- before(:create) { |recording|
- recording.recorded_tracks << FactoryGirl.create(:recorded_track, recording: recording, user: recording.owner)
- }
- end
+ factory :recorded_video, :class => JamRuby::RecordedVideo do
+ sequence(:recording_id) { |n| "recording_id-#{n}"}
+ sequence(:client_video_source_id) { |n| "client_video_source_id-#{n}"}
+ fully_uploaded true
+ length 1
+ association :user, factory: :user
+ association :recording, factory: :recording
end
factory :claimed_recording, :class => JamRuby::ClaimedRecording do
diff --git a/web/spec/features/oauth_spec.rb b/web/spec/features/oauth_spec.rb
new file mode 100644
index 000000000..e6c5af5a2
--- /dev/null
+++ b/web/spec/features/oauth_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+require 'youtube_client'
+
+describe "OAuth", :slow=>true, :js=>true, :type=>:feature, :capybara_feature=>true do
+
+ subject { page }
+
+ before(:all) do
+ Capybara.javascript_driver = :poltergeist
+ Capybara.current_driver = Capybara.javascript_driver
+ Capybara.default_wait_time = 10
+ @previous_run_server = Capybara.run_server
+ Capybara.run_server = false
+ @user=FactoryGirl.create(:user, :email=>"jamkazamtest@gmail.com")
+ end
+
+ before(:each) do
+ @youtube_client = YouTubeClient.new()
+ end
+
+ after(:each) do
+ @youtube_client.shutdown if @youtube_client
+ @youtube_client=nil
+ @user.user_authorizations.destroy_all
+ #page.driver.remove_cookie(:remember_token)
+ end
+
+ after(:all) do
+ @user.destroy
+ Capybara.run_server = @previous_run_server
+ end
+
+ it "client should not authorize a wrong password" do
+ expect {
+ authorize_google_user(@youtube_client, @user, "f00bar")
+ }.to raise_error
+
+ @user.reload
+ @user.user_authorizations.count.should eq(0)
+ end
+
+ it "client should authorize a google user" do
+ authorize_google_user(@youtube_client, @user, "stinkyblueberryjam")
+ save_screenshot("working.png")
+ @user.reload
+ @user.user_authorizations.count.should eq(1)
+
+ google_auth = UserAuthorization.google_auth(@user).first
+ google_auth.should_not be_nil
+ google_auth.token.should_not be_nil
+ end
+
+end
diff --git a/web/spec/features/youtube_spec.rb b/web/spec/features/youtube_spec.rb
new file mode 100644
index 000000000..41883cd80
--- /dev/null
+++ b/web/spec/features/youtube_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+require 'youtube_client'
+require 'rest_client'
+
+describe "YouTube", :slow=>true, :js=>true, :type => :feature, :capybara_feature => true do
+ subject { page }
+
+ before(:all) do
+ Capybara.javascript_driver = :poltergeist
+ Capybara.current_driver = Capybara.javascript_driver
+ Capybara.default_wait_time = 10
+ @previous_run_server = Capybara.run_server
+ Capybara.run_server = false
+ @user=FactoryGirl.create(:user, :email => "jamkazamtest@gmail.com")
+ @youtube_client = YouTubeClient.new()
+ authorize_google_user(@youtube_client, @user, "stinkyblueberryjam")
+ google_auth = UserAuthorization.google_auth(@user).first # Consider returning this from above now that it is reliable
+ end
+
+ after(:all) do
+ @user.destroy
+ @youtube_client.shutdown
+ Capybara.run_server = @previous_run_server
+ end
+
+ it "should retrieve upload url" do
+ length = 3276
+ upload_hash=@youtube_client.upload_sign(@user, "test_video.mp4", length)
+ upload_hash.should_not be_nil
+ upload_hash.length.should be >=1
+ upload_hash['method'].should eq("PUT")
+ upload_hash['url'].should_not be_nil
+ upload_hash['Authorization'].should_not be_nil
+ upload_hash['Content-Length'].should_not be_nil
+ upload_hash['Content-Length'].should eq(length)
+ upload_hash['Content-Type'].should_not be_nil
+
+ @youtube_client.verify_upload(@user, upload_hash['url'], length).should be_false
+ end
+
+ it "upload url should allow uploading" do
+ vid_path = Rails.root.join('spec', 'files', 'test_video.mp4')
+ length = File.size?(vid_path)
+ upload_hash=@youtube_client.upload_sign(@user, "test_video.mp4", length)
+ #puts upload_hash.inspect
+ upload_hash.should_not be_nil
+ upload_hash.length.should be >=1
+ upload_hash['method'].should eq("PUT")
+ upload_hash['url'].should_not be_nil
+ upload_hash['Authorization'].should_not be_nil
+ upload_hash['Content-Length'].should_not be_nil
+ upload_hash['Content-Length'].should eq(length)
+ upload_hash['Content-Type'].should_not be_nil
+
+ # Upload this file as the client would:
+ RestClient.put(upload_hash['url'], File.read(vid_path))
+ @youtube_client.verify_upload(@user, upload_hash['url'], length).should be_true
+ #@youtube_client.get_upload_status(@user, upload_hash['url'], length)
+ end
+
+ it "sets upload flag when complete" do
+ @music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true)
+ @connection = FactoryGirl.create(:connection, :user => @user, :music_session => @music_session)
+ @video_source = FactoryGirl.create(:video_source, :connection => @connection)
+ @recording = FactoryGirl.create(:recording, owner: @user, band: nil, duration:1)
+
+
+ vid_path = Rails.root.join('spec', 'files', 'test_video.mp4')
+ length = File.size?(vid_path)
+ upload_hash=@youtube_client.upload_sign(@user, "test_video.mp4", length)
+ upload_hash.should_not be_nil
+ upload_hash['url'].should_not be_nil
+ RestClient.put(upload_hash['url'], File.read(vid_path))
+
+ recorded_video = FactoryGirl.create(:recorded_video,
+ recording: @recording,
+ user: @recording.owner,
+ fully_uploaded: false,
+ url: upload_hash['url'],
+ length: length
+ )
+
+ @recording.recorded_videos << recorded_video
+
+ @youtube_client.verify_upload(@user, upload_hash['url'], length).should be_true
+ @youtube_client.complete_upload(recorded_video).should be_true
+ recorded_video.fully_uploaded.should be_true
+ end
+end
diff --git a/web/spec/files/test_video.mp4 b/web/spec/files/test_video.mp4
new file mode 100644
index 000000000..45b460bfd
Binary files /dev/null and b/web/spec/files/test_video.mp4 differ
diff --git a/web/spec/spec_helper.rb b/web/spec/spec_helper.rb
index 95b6f42e1..483a30851 100644
--- a/web/spec/spec_helper.rb
+++ b/web/spec/spec_helper.rb
@@ -192,10 +192,11 @@ bputs "before register capybara"
end
config.before(:each) do
- if example.metadata[:js]
+ if example.metadata[:js] && (Capybara.current_driver.nil? || Capybara.current_driver.empty? || Capybara.current_driver==:poltergeist)
page.driver.resize(1920, 1080)
page.driver.headers = { 'User-Agent' => 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0' }
end
+
end
config.before(:each, :js => true) do
@@ -212,7 +213,6 @@ bputs "before register capybara"
Capybara.reset_sessions!
reset_session_mapper
-
end
config.after(:each) do
diff --git a/web/spec/support/utilities.rb b/web/spec/support/utilities.rb
index e459ac8b0..d70b273c6 100644
--- a/web/spec/support/utilities.rb
+++ b/web/spec/support/utilities.rb
@@ -73,6 +73,45 @@ def wipe_s3_test_bucket
end
end
+def authorize_google_user(youtube_client, user, google_password)
+ youtube_client.wait_for_callback(2112) do |access_token|
+ #puts("Authorizing with token #{access_token}")
+ user_auth_hash = {
+ :provider => "google_login",
+ :uid => user.email,
+ :token => access_token,
+ :token_expiration => nil,
+ :secret => ""
+ }
+ authorization = user.user_authorizations.build(user_auth_hash)
+ authorization.save
+ end
+
+ url = youtube_client.get_login_url(user.email)
+ #puts("Login URL: #{url}")
+ visit url
+ sleep(1)
+ # save_screenshot("initial.png")
+
+ # Fill in password (the username is filled in with a hint in URL):
+ # fill_in "Usernm", with: user.email
+ fill_in "Passwd", with: google_password
+ #save_screenshot("password.png")
+
+ find('#signIn').trigger(:click)
+ # Wait for submit to enable and then click it:
+ sleep(5)
+
+ #save_screenshot("signin.png")
+
+ #save_screenshot("submit.png")
+ find('#submit_approve_access').trigger(:click)
+ #save_screenshot("log2.png")
+ sleep(5)
+ #save_screenshot("submitted.png")
+
+ youtube_client
+end
def sign_in(user)
visit signin_path
@@ -116,7 +155,8 @@ def sign_in_poltergeist(user, options = {})
should_be_at_root
end
- visit signin_path
+ visit signin_path
+ page.should have_selector('#landing-inner form.signin-form')
within('#landing-inner form.signin-form') do
fill_in "Email Address:", with: user.email
@@ -148,7 +188,7 @@ end
def set_login_cookie(user)
page.driver.set_cookie(:remember_token, user.remember_token)
end
-
+
def sign_out()
if Capybara.javascript_driver == :poltergeist
page.driver.remove_cookie(:remember_token)
diff --git a/web/spec/testing_oauth.txt b/web/spec/testing_oauth.txt
new file mode 100644
index 000000000..db139b81f
--- /dev/null
+++ b/web/spec/testing_oauth.txt
@@ -0,0 +1,28 @@
+For access to the youtube and google APIs, we need an access_token
+
+To obtain an access token, one must actually log into google using a browser running javascript. This redirects to the URL specified, as long as it is specified in the oauth configuration.
+
+Getting an access token for the purposes of automated testing is tricky, but possible using Capybara with a javascript-enabled driver. (Note, web/spec/support/utilities.rb utilizes the JK youtube client to perform the intricate bits):
+
+1) Obtain the login URL. It's ugly, but we can get it from the YouTubeClient. It contains the callback URL, as well as a "hint" that will fill in the username for us.
+2) Start a web server on an enabled callback server, such as localhost:3000
+3) Obtain the URL using a known test user
+4) Visit the URL in a capybara test
+4a) Fill in password with the right value
+4b) Click the login button
+4c) The approve page should load. Wait for the approve button to be enabled. This is usually a second or two after the page loads, but not immediately.
+4d) Click the approve button
+5) After google approves, some javascript will redirect to our test web server, which contains a code. This is not the access_token, but a one-time code that can be exchanged for an access_token, again POSTing to google's auth server. You can see it in gory detail in YouTubeClient.exchange_for_token.
+6) If all goes well, the test web server will call back the invoker with a real access token.
+7) For testing purposes, stick the access token in the user.user_authorizations table for the user for which we are testing.
+
+Notes:
+* When authenticating, client_id is required by several of the APIs. However, this doesn't work for /o/oauth2/token. What actually works is the "email" value from the developer console. This is now saved in the app as well.
+
+The tests in question use the following credentials:
+u: jamkazamtest@gmail.com
+p: stinkyblueberryjam
+
+Also, a server is started on port 2112, as 3000 was already being used on the build server.
+
+