diff --git a/db/manifest b/db/manifest index 4520d6016..93204fbd6 100755 --- a/db/manifest +++ b/db/manifest @@ -101,3 +101,4 @@ facebook_signup.sql audiomixer_mp3.sql share_token_2.sql large_photo_url.sql +add_secret_to_user_authorization.sql \ No newline at end of file diff --git a/db/up/add_secret_to_user_authorization.sql b/db/up/add_secret_to_user_authorization.sql new file mode 100644 index 000000000..2dab2c08c --- /dev/null +++ b/db/up/add_secret_to_user_authorization.sql @@ -0,0 +1 @@ +ALTER TABLE user_authorizations ADD COLUMN secret VARCHAR(255); \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 8496ea897..73936ba66 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -974,6 +974,48 @@ module JamRuby end end + def invalidate_user_authorization(provider) + auth = user_authorization(provider) + auth.destroy if auth + end + + def user_authorization(provider) + user_authorizations.where(provider: provider).first + end + + def auth_twitter + !user_authorization('twitter').nil? + end + + def update_twitter_authorization(auth_hash) + + twitter_uid = auth_hash[:uid] + credentials = auth_hash[:credentials] + secret = credentials[:secret] if credentials + token = credentials[:token] if credentials + + if twitter_uid && secret && token + user_authorization = nil + + unless self.new_record? + # see if this user has an existing user_authorization for this provider + user_authorization = UserAuthorization.find_by_user_id_and_provider(self.id, 'twitter') + end + end + + if user_authorization.nil? + self.user_authorizations.build provider: 'twitter', + uid: twitter_uid, + token: token, + secret: secret + else + user_authorization.uid = twitter_uid + user_authorization.token = token + user_authorization.secret = secret + user_authorization.save! + end + end + # updates an existing user_authorization for facebook, or creates a new one if none exist def update_fb_authorization(fb_signup) if fb_signup.uid && fb_signup.token && fb_signup.token_expires_at @@ -994,6 +1036,7 @@ module JamRuby user_authorization.uid = fb_signup.uid user_authorization.token = fb_signup.token user_authorization.token_expiration = fb_signup.token_expires_at + user_authorization.save! end end end diff --git a/ruby/lib/jam_ruby/models/user_authorization.rb b/ruby/lib/jam_ruby/models/user_authorization.rb index 47061e17e..adc464d13 100644 --- a/ruby/lib/jam_ruby/models/user_authorization.rb +++ b/ruby/lib/jam_ruby/models/user_authorization.rb @@ -1,7 +1,7 @@ module JamRuby class UserAuthorization < ActiveRecord::Base - attr_accessible :provider, :uid, :token, :token_expiration + attr_accessible :provider, :uid, :token, :token_expiration, :secret self.table_name = "user_authorizations" diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index b4d3a6d73..b5ad894e5 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -795,6 +795,16 @@ }) } + function tweet(options) { + return $.ajax({ + type: "POST", + dataType: "json", + contentType: 'application/json', + url: "/api/twitter/tweet", + data: JSON.stringify(options) + }) + } + function initialize() { return self; } @@ -863,6 +873,7 @@ this.login = login; this.getShareSession = getShareSession; this.getShareRecording = getShareRecording; + this.tweet = tweet; return this; }; diff --git a/web/app/assets/javascripts/jamkazam.js b/web/app/assets/javascripts/jamkazam.js index fbee40c0c..fe6d2e355 100644 --- a/web/app/assets/javascripts/jamkazam.js +++ b/web/app/assets/javascripts/jamkazam.js @@ -54,7 +54,7 @@ if (targetArg) { targetUrl += "/:" + targetArg; } - rules[target] = {route: '/' + targetUrl + '/d:?', method: target}; + rules[target] = {route: '/' + targetUrl + '/:d?', method: target}; routingContext[target] = fn; }); routes.context(routingContext); @@ -337,6 +337,7 @@ context.location = url; } + this.unloadFunction = function() { logger.debug("window.unload function called."); @@ -359,6 +360,7 @@ this.layout = new context.JK.Layout(); this.layout.initialize(this.opts.layoutOpts); events(); + this.layout.handleDialogState(); if(opts.inClient) { registerLoginAck(); diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js index 4a8e45d2c..c2f09578f 100644 --- a/web/app/assets/javascripts/layout.js +++ b/web/app/assets/javascripts/layout.js @@ -589,6 +589,29 @@ context.JK.GA.virtualPageView(location.pathname + location.search + location.hash); } + function handleDialogState() { + var rawDialogState = $.cookie('dialog_state'); + try { + var dialogState = JSON.parse(rawDialogState); + if(!dialogState) { $.removeCookie('dialog_state'); return; } + } + catch (e) {$.removeCookie('dialog_state'); return; } + + var dialogName = dialogState['name']; + if(dialogName) { + setTimeout(function() { + // TODO: we need a 'everything is initialized' event + showDialog(dialogName); + }, 0); + } + $.removeCookie('dialog_state'); + } + + // on next page load, a dialog of this name will show + function queueDialog(name) { + $.cookie('dialog_state', JSON.stringify({name:name})) + } + function events() { $(context).resize(function() { if (resizing) { @@ -736,6 +759,9 @@ this.closeDialog = closeDialog; + this.handleDialogState = handleDialogState; + this.queueDialog = queueDialog; + /** * Given information on a grid, and a given card's grid settings, use the * margin options and return a list of [top, left, width, height] diff --git a/web/app/assets/javascripts/shareDialog.js b/web/app/assets/javascripts/shareDialog.js index efe69f037..f5eb882d7 100644 --- a/web/app/assets/javascripts/shareDialog.js +++ b/web/app/assets/javascripts/shareDialog.js @@ -8,6 +8,8 @@ var facebookRest = context.JK.FacebookRest(); var facebookHelper = null; var dialogId = '#share-dialog'; + var userDetail = null; + var entity = null; var textMap = { LIVE_SESSION: "LIVE SESSION", @@ -100,7 +102,33 @@ function handleSessionShareWithTwitter(message) { var defer = $.Deferred(); - defer.resolve(); // remove when implemented + rest.getShareSession({ provider:'twitter', music_session: entityId}) + .done(function(data) { + + rest.tweet({message: message + ' ' + data.message + ' ' + entity.share_url}) + .done(function() { + defer.resolve(); + }) + .fail(function(jqXHR) { + if(jqXHR.status == 422) { + // implies twitter token error. + app.notify({ + title : "Failed to Tweet", + text : "You need to re-authorize JamKazam to access your Twitter account. Click (sign in) in the Share Dialog.", + "icon_url": "/assets/content/icon_alert_big.png" + }); + disableTwitter(); + } + else { + app.notifyServerError(jqXHR, "Unable to Share with Twitter"); + } + defer.reject(); + }) + }) + .fail(function(jqXHR) { + app.notifyServerError(jqXHR, "Unable to Populate Share Data"); + defer.reject(); + }) return defer; } @@ -147,6 +175,14 @@ return defer; } + function messageTooLongForTwitter(message) { + // put user message, then stock content, then url + var maxLength = 140; + var remainingCap = maxLength - 22 - 2; // 22 for shortened url by twitter, 2 is for the two spaces + + return message.length > remainingCap; + } + function socialShare() { var facebookCheckbox = $(dialogId + ' .share-with-facebook input'); var shareWithFacebook = facebookCheckbox.is(':checked') && !facebookCheckbox.is(':disabled'); @@ -165,6 +201,11 @@ $(dialogId + ' .share-options').removeClass('error') } + // validate twitter message length + if(messageTooLongForTwitter(message)) { + $(dialogId + ' .'); + } + var message = $(dialogId + ' .share-message').val(); if(!message) { message = undefined; } @@ -217,6 +258,16 @@ $(dialogId + ' .share-with-facebook a').css('visibility', 'visible'); } + function enableTwitter() { + $(dialogId + ' .share-with-twitter input').removeAttr('disabled') + $(dialogId + ' .share-with-twitter a').css('visibility', 'hidden'); + } + + function disableTwitter() { + $(dialogId + ' .share-with-twitter input').attr('disabled', 'disabled') + $(dialogId + ' .share-with-twitter a').css('visibility', 'visible'); + } + function handleFbStateChange(response) { if(response && response.status == "connected") @@ -237,7 +288,13 @@ $(dialogId + ' .share-with-facebook a').unbind('click').click(function(e) { facebookHelper.promptLogin(); return false; - }) + }); + + $(dialogId + ' .share-with-twitter a').unbind('click').click(function(e) { + app.layout.queueDialog('share-dialog') + window.location = '/auth/twitter'; + return false; + }); } function showDialog() { @@ -310,6 +367,40 @@ } function beforeShow() { + disableTwitter(); + // no disableFacebook on purpose + + if(entityType == 'recording') { + rest.getClaimedRecording(entityId) + .done(function(data) { + entity = data; + $(dialogId + ' .link-contents').text(entity.share_url) + }) + .fail(function(jqXHR) { + app.notifyServerError(jqXHR, "Unable to Fetch Session Data"); + }) + } + else { + rest.getSession(entityId) + .done(function(data) { + entity = data; + $(dialogId + ' .link-contents').text(entity.share_url) + }) + .fail(function(jqXHR) { + app.notifyServerError(jqXHR, "Unable to Fetch Session Data"); + }); + } + rest.getUserDetail() + .done(function(data) { + userDetail = data; + if(data.auth_twitter) { + enableTwitter(); + } + else { + disableTwitter(); + } + }); + $(dialogId + ' .share-options').removeClass('error'); registerEvents(true); } diff --git a/web/app/assets/javascripts/web/web.js b/web/app/assets/javascripts/web/web.js index 716eacba8..43fbbc147 100644 --- a/web/app/assets/javascripts/web/web.js +++ b/web/app/assets/javascripts/web/web.js @@ -2,6 +2,7 @@ //= require jquery_ujs //= require jquery.queryparams //= require jquery.hoverIntent +//= require jquery.cookie //= require AAA_Log //= require AAC_underscore //= require globals diff --git a/web/app/controllers/api_twitters_controller.rb b/web/app/controllers/api_twitters_controller.rb new file mode 100644 index 000000000..7b3d27d2b --- /dev/null +++ b/web/app/controllers/api_twitters_controller.rb @@ -0,0 +1,34 @@ +class ApiTwittersController < ApiController + + before_filter :api_signed_in_user + + respond_to :json + + rescue_from 'Twitter::Error::Unauthorized' do |exception| + # invalidate current tokens + current_user.invalidate_user_authorization('twitter') + + render :json => { :errors => { :token => ["is invalid"] } }, :status => 422 + end + + + def tweet + + twitter_auth = current_user.user_authorization('twitter') + + raise JamRuby::PermissionError unless twitter_auth + + client = Twitter::REST::Client.new do |config| + config.consumer_key = Rails.application.config.twitter_app_id + config.consumer_secret = Rails.application.config.twitter_app_secret + config.access_token = twitter_auth.token + config.access_token_secret = twitter_auth.secret + end + + text = params[:message] + + client.update(text) + + render json: {}, status: :ok + end +end diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index 8bc9b0fab..59ac5c6fd 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -559,12 +559,18 @@ class ApiUsersController < ApiController if provider == 'facebook' render json: { - description: view_context.facebook_description_for_music_session_history(history), - title: view_context.facebook_title_for_music_session_history(history, current_user), + description: view_context.description_for_music_session_history(history), + title: view_context.title_for_music_session_history(history, current_user), photo_url: view_context.facebook_image_for_music_session_history(history), caption: 'www.jamkazam.com' }, status: 200 + elsif provider == 'twitter' + + render json: { + message: view_context.title_for_music_session_history(history, current_user) + }, status: 200 + else render :json => { :errors => {:provider => ['not valid']} }, :status => 422 end @@ -579,12 +585,19 @@ class ApiUsersController < ApiController if provider == 'facebook' render json: { - description: view_context.facebook_description_for_claimed_recording(claimed_recording), - title: view_context.facebook_title_for_claimed_recording(claimed_recording, current_user), + description: view_context.description_for_claimed_recording(claimed_recording), + title: view_context.title_for_claimed_recording(claimed_recording, current_user), photo_url: view_context.facebook_image_for_claimed_recording(claimed_recording), caption: 'www.jamkazam.com' }, status: 200 + elsif provider == 'twitter' + + render json: { + message: view_context.title_for_claimed_recording(history, current_user) + " at " + request.host_with_port + }, status: 200 + + else render :json => { :errors => {:provider => ['not valid']} }, :status => 422 end diff --git a/web/app/controllers/sessions_controller.rb b/web/app/controllers/sessions_controller.rb index 135ebf638..16a734115 100644 --- a/web/app/controllers/sessions_controller.rb +++ b/web/app/controllers/sessions_controller.rb @@ -1,6 +1,8 @@ # this is not a jam session - this is an 'auth session' class SessionsController < ApplicationController + layout "web" + def new @login_error = false render :layout => "landing" @@ -23,6 +25,7 @@ class SessionsController < ApplicationController end end + # OAuth docs # http://net.tutsplus.com/tutorials/ruby/how-to-use-omniauth-to-authenticate-your-users/ def create_oauth @@ -64,14 +67,20 @@ class SessionsController < ApplicationController end end - + + # https://github.com/intridea/omniauth/wiki/Saving-User-Location def oauth_callback auth_hash = request.env['omniauth.auth'] provider = auth_hash[:provider] - if provider == 'facebook' + if provider == 'twitter' + current_user.update_twitter_authorization(auth_hash) + current_user.save! + redirect_to request.env['omniauth.origin'] || '/' + return + elsif provider == 'facebook' fb_uid = auth_hash[:uid] token = auth_hash[:credentials][:token] token_expiration = Time.at(auth_hash[:credentials][:expires_at]) @@ -143,7 +152,7 @@ class SessionsController < ApplicationController end def failure - + redirect_to request.query_parameters['origin'] || '/' end def connection_state diff --git a/web/app/helpers/music_session_helper.rb b/web/app/helpers/music_session_helper.rb index 307e588bc..a9e733f40 100644 --- a/web/app/helpers/music_session_helper.rb +++ b/web/app/helpers/music_session_helper.rb @@ -19,7 +19,7 @@ module MusicSessionHelper end end - def facebook_title_for_music_session_history(music_session, sharer = nil) + def title_for_music_session_history(music_session, sharer = nil) if music_session.band "LIVE SESSION: #{music_session.band.name}" else @@ -42,7 +42,7 @@ module MusicSessionHelper end end - def facebook_description_for_music_session_history(music_session) + def description_for_music_session_history(music_session) truncate(music_session.description, length:250) end end diff --git a/web/app/helpers/recording_helper.rb b/web/app/helpers/recording_helper.rb index ea4a64652..d443c475d 100644 --- a/web/app/helpers/recording_helper.rb +++ b/web/app/helpers/recording_helper.rb @@ -19,7 +19,7 @@ module RecordingHelper end end - def facebook_title_for_claimed_recording(claimed_recording, sharer = nil) + def title_for_claimed_recording(claimed_recording, sharer = nil) if claimed_recording.recording.band "RECORDING: #{claimed_recording.recording.band.name}" else @@ -42,7 +42,7 @@ module RecordingHelper end end - def facebook_description_for_claimed_recording(claimed_recording) + def description_for_claimed_recording(claimed_recording) truncate(claimed_recording.name, length:250) end end diff --git a/web/app/views/api_users/show.rabl b/web/app/views/api_users/show.rabl index fb3e14d28..4d78029b3 100644 --- a/web/app/views/api_users/show.rabl +++ b/web/app/views/api_users/show.rabl @@ -10,7 +10,8 @@ end # give back more info if the user being fetched is yourself if @user == current_user - attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :subscribe_email + attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :subscribe_email, :auth_twitter + elsif current_user node :is_friend do |uu| current_user.friends?(@user) diff --git a/web/app/views/clients/_shareDialog.html.erb b/web/app/views/clients/_shareDialog.html.erb index 531221816..cc28817e5 100644 --- a/web/app/views/clients/_shareDialog.html.erb +++ b/web/app/views/clients/_shareDialog.html.erb @@ -39,10 +39,6 @@