diff --git a/db/manifest b/db/manifest index c11589332..24dbab76d 100755 --- a/db/manifest +++ b/db/manifest @@ -94,4 +94,5 @@ integrate_icecast_into_sessions.sql ms_recording_anonymous_likes.sql ms_user_history_add_instruments.sql icecast_config_changed.sql +invited_users_facebook_support.sql first_recording_at.sql \ No newline at end of file diff --git a/db/up/invited_users_facebook_support.sql b/db/up/invited_users_facebook_support.sql new file mode 100644 index 000000000..efc79b5de --- /dev/null +++ b/db/up/invited_users_facebook_support.sql @@ -0,0 +1,3 @@ +ALTER TABLE invited_users ALTER COLUMN email DROP NOT NULL; +ALTER TABLE invited_users ADD COLUMN invite_medium VARCHAR(64); + diff --git a/ruby/lib/jam_ruby/app/mailers/invited_user_mailer.rb b/ruby/lib/jam_ruby/app/mailers/invited_user_mailer.rb index a443f9c6d..ce1a007ac 100644 --- a/ruby/lib/jam_ruby/app/mailers/invited_user_mailer.rb +++ b/ruby/lib/jam_ruby/app/mailers/invited_user_mailer.rb @@ -48,7 +48,8 @@ module JamRuby end def generate_signup_url(invited_user) - "http://www.jamkazam.com/signup?invitation_code=#{invited_user.invitation_code}" + invited_user.generate_signup_url + # "http://www.jamkazam.com/signup?invitation_code=#{invited_user.invitation_code}" end end end diff --git a/ruby/lib/jam_ruby/models/invited_user.rb b/ruby/lib/jam_ruby/models/invited_user.rb index 6481a6bdb..579766cf7 100644 --- a/ruby/lib/jam_ruby/models/invited_user.rb +++ b/ruby/lib/jam_ruby/models/invited_user.rb @@ -14,13 +14,15 @@ module JamRuby belongs_to :sender , :inverse_of => :invited_users, :class_name => "JamRuby::User", :foreign_key => "sender_id" # who is the invitation sent to? - validates :email, :presence => true, format: {with: VALID_EMAIL_REGEX} + validates :email, format: {with: VALID_EMAIL_REGEX}, :if => lambda { |iu| iu.email.present? } validates :autofriend, :inclusion => {:in => [nil, true, false]} validates :invitation_code, :presence => true validates :note, length: {maximum: 400}, no_profanity: true # 400 == arbitrary. + validate :one_facebook_invite_per_user, :if => lambda { |iu| iu.invite_medium == FB_MEDIUM } validate :valid_personalized_invitation - validate :not_accepted_twice + # validate :not_accepted_twice + validate :not_accepted_twice, :if => lambda { |iu| iu.email } validate :can_invite? after_save :track_user_progression @@ -31,6 +33,12 @@ module JamRuby self.sender_id = nil if self.sender_id.blank? # this coercion was done just to make activeadmin work end + FB_MEDIUM = 'facebook' + + def self.facebook_invite(user) + where(:sender_id => user.id, :invite_medium => FB_MEDIUM).limit(1).first + end + def track_user_progression self.sender.update_progression_field(:first_invited_at) unless self.sender.nil? end @@ -54,6 +62,15 @@ module JamRuby def invited_by_administrator? sender.nil? || sender.admin # a nil sender can only be created by someone using jam-admin end + + def generate_signup_url + if 'development'==Rails.env + "http://jamkazamdev.local:3000/signup?invitation_code=#{self.invitation_code}" + else + "http://www.jamkazam.com/signup?invitation_code=#{self.invitation_code}" + end + end + private def can_invite? @@ -67,5 +84,12 @@ module JamRuby def not_accepted_twice errors.add(:accepted, "you can only accept an invitation once") if accepted_twice end + + def one_facebook_invite_per_user + rel = InvitedUser.where(:invite_medium => FB_MEDIUM, :sender_id => self.sender_id) + rel = rel.where(['id != ?',self.id]) if self.id + errors.add(:invite_medium, "one facebook invite allowed per user") if 0 < rel.count + end + end end diff --git a/ruby/lib/jam_ruby/models/invited_user_observer.rb b/ruby/lib/jam_ruby/models/invited_user_observer.rb index a4a007a93..cd62bd2d1 100644 --- a/ruby/lib/jam_ruby/models/invited_user_observer.rb +++ b/ruby/lib/jam_ruby/models/invited_user_observer.rb @@ -8,7 +8,7 @@ module JamRuby InvitedUserMailer.welcome_betauser(invited_user).deliver else InvitedUserMailer.friend_invitation(invited_user).deliver - end + end if invited_user.email.present? end end -end \ No newline at end of file +end diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 7fe2f24f3..5af2ebf7b 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -1007,10 +1007,21 @@ module JamRuby end end - #def first_recording_at - # Recording.where(:owner_id => self.id).order('created_at ASC').first.try(:created_at) - #end - + def first_recording_at + Recording.where(:owner_id => self.id).order('created_at ASC').first.try(:created_at) + end + + def facebook_invite! + unless iu = InvitedUser.facebook_invite(self) + iu = InvitedUser.new + iu.sender = self + iu.autofriend = true + iu.invite_medium = InvitedUser::FB_MEDIUM + iu.save + end + iu + end + # devise compatibility #def encrypted_password diff --git a/ruby/spec/jam_ruby/models/invited_user_spec.rb b/ruby/spec/jam_ruby/models/invited_user_spec.rb index 0ccf6865e..ca4288939 100644 --- a/ruby/spec/jam_ruby/models/invited_user_spec.rb +++ b/ruby/spec/jam_ruby/models/invited_user_spec.rb @@ -104,4 +104,28 @@ describe InvitedUser do invited_user.valid?.should be_false end + it 'accepts empty emails' do + user1 = FactoryGirl.create(:user) + invited_user = FactoryGirl.create(:invited_user, :sender_id => user1.id, :email => '') + expect(invited_user.valid?).to eq(true) + end + + it 'accepts one facebook invite per user' do + user1 = FactoryGirl.create(:user) + invited_user = FactoryGirl.create(:invited_user, :sender_id => user1.id, :invite_medium => InvitedUser::FB_MEDIUM) + expect(invited_user.valid?).to eq(true) + invited_user.autofriend = !invited_user.autofriend + invited_user.save + expect(invited_user.valid?).to eq(true) + invited_user1 = InvitedUser.new(:email => 'foobar@example.com', :sender_id => user1.id) + invited_user1.autofriend = true + invited_user1.invite_medium = InvitedUser::FB_MEDIUM + invited_user1.save + expect(invited_user1.valid?).to eq(false) + expect(InvitedUser.facebook_invite(user1).id).to eq(invited_user.id) + user2 = FactoryGirl.create(:user) + iu = user1.facebook_invite! + expect(user1.facebook_invite!.id).to eq(iu.id) + end + end diff --git a/web/app/assets/javascripts/createSession.js b/web/app/assets/javascripts/createSession.js index ae3e5ff92..2d2c58f10 100644 --- a/web/app/assets/javascripts/createSession.js +++ b/web/app/assets/javascripts/createSession.js @@ -200,8 +200,8 @@ invitationDialog.showGoogleDialog(); }); - $('div[layout-id="createSession"] .btn-facebook-invitation').click(function() { - invitationDialog.showFacebookDialog(); + $('div[layout-id="createSession"] .btn-facebook-invitation').click(function(e) { + invitationDialog.showFacebookDialog(e); }); $('#friend-input').focus(function() { $(this).val(''); }) diff --git a/web/app/assets/javascripts/invitationDialog.js b/web/app/assets/javascripts/invitationDialog.js index a3c569f59..f268554b7 100644 --- a/web/app/assets/javascripts/invitationDialog.js +++ b/web/app/assets/javascripts/invitationDialog.js @@ -1,5 +1,4 @@ (function(context,$) { - "use strict"; context.JK = context.JK || {}; context.JK.InvitationDialog = function(app) { @@ -7,6 +6,7 @@ var rest = context.JK.Rest(); var waitForUserToStopTypingTimer; var sendingEmail = false; + var fbInviteURL_ = null; function trackMetrics(emails, googleInviteCount) { var allInvitations = emails.length; // all email invites, regardless of how they got in the form @@ -152,49 +152,114 @@ }; window._oauth_win = window.open("/auth/google_login", "_blank", "height=500,width=500,menubar=no,resizable=no,status=no"); } + + ////////////// + // FB handlers - function showFacebookDialog() { - /* - $('#invitation-textarea-container').hide(); - $('#invitation-checkbox-container').show(); - $('#btn-send-invitation').hide(); - $('#btn-next-invitation').show(); - invitationDialog.showDialog(); - $('#invitation-checkboxes').html('
Loading your contacts...
'); - */ - window._oauth_callback = function() { - window._oauth_win.close(); - window._oauth_win = null; - window._oauth_callback = null; - /* - $.ajax({ - type: "GET", - url: "/gmail_contacts", - success: function(response) { - $('#invitation-checkboxes').html(''); - for (var i in response) { - $('#invitation-checkboxes').append(""); - } - - $('.invitation-checkbox').change(function() { - var checkedBoxes = $('.invitation-checkbox:checkbox:checked'); - var emails = ''; - for (var i = 0; i < checkedBoxes.length; i++) { - emails += $(checkedBoxes[i]).data('email') + ', '; - } - emails = emails.replace(/, $/, ''); - $('#txt-emails').val(emails); - }); - }, - error: function() { - $('#invitation-checkboxes').html("Load failed"); - } - }); - */ - }; - window._oauth_win = window.open("/auth/facebook_login", "_blank", "height=500,width=500,menubar=no,resizable=no,status=no"); + // Additional initialization code such as adding Event Listeners goes here + function handle_fblogin_response(response) { + if (response.status === 'connected') { + // the user is logged in and has authenticated your + // app, and response.authResponse supplies + // the user's ID, a valid access token, a signed + // request, and the time the access token + // and signed request each expire + var uid = response.authResponse.userID; + var accessToken = response.authResponse.accessToken; + window.fb_logged_in_state = "connected"; + } else if (response.status === 'not_authorized') { + // the user is logged in to Facebook, + // but has not authenticated your app + // TODO: popup authorization dialog + window.fb_logged_in_state = "not_authorized"; + } + else { + // the user isn't logged in to Facebook. + window.fb_logged_in_state = "not_logged_in"; + } } + this.fb_login = function() { + FB.login(function(response) { + handle_fblogin_response(response); + }, {scope:'publish_stream'}); + } + + function fbInviteURL() { + if (fbInviteURL_ === null) { + $.ajax({ + type: "GET", + async: false, + url: '/api/invited_users/facebook', + success: function(response) { + fbInviteURL_ = response['signup_url']; + }, + error: app.ajaxError + }); + } + return fbInviteURL_; + } + + function show_feed_dialog() { + var obj = { + method: 'feed', + link: fbInviteURL(), + picture: 'http://jamkazam.com/assets/logo.png', + name: 'Join me on JamKazam', + caption: 'Play live music in real-time sessions with others over the Internet, as if in the same room.', + description: '', + actions: [{ name: 'Signup', link: fbInviteURL() }] + }; + function fbFeedDialogCallback(response) { + //console.log("feedback dialog closed: " + response['post_id']) + } + FB.ui(obj, fbFeedDialogCallback); + } + + function showFacebookDialog(evt) { + if (!(evt === undefined)) evt.stopPropagation(); + + var fb_state = window.fb_logged_in_state; + + if (fb_state == "connected") { + show_feed_dialog(); + } else if (fb_state == "not_authorized") { + this.fb_login(); + } else { + this.fb_login(); + } + } + + function callFB(fbAppID){ + var fbAppID_ = fbAppID; + window.fbAsyncInit = function() { + FB.init({ + appId : fbAppID_, + // channelUrl : '//WWW.YOUR_DOMAIN.COM/channel.html', + status : true, // check the login status upon init? + cookie : true, // set sessions cookies to allow server to access the session? + xfbml : true, // parse XFBML tags on this page? + oauth : true, // enable OAuth 2.0 + }); + // listen to see if the user is known/logged in + FB.getLoginStatus(function(response) { + handle_fblogin_response(response); + }); + }; + + // Load the SDK Asynchronously + (function(d){ + var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;} + js = d.createElement('script'); js.id = id; js.async = true; + js.src = "//connect.facebook.net/en_US/all.js"; + d.getElementsByTagName('head')[0].appendChild(js); + }(document)); + } + + // END FB handlers + ////////////// + + function clearTextFields() { $('#txt-emails').val('').removeData('google_invite_count'); $('#txt-message').val(''); @@ -211,16 +276,17 @@ registerEvents(false); } - function initialize(){ + function initialize(fbAppID){ var dialogBindings = { 'beforeShow' : beforeShow, 'afterHide': afterHide }; app.bindDialog('inviteUsers', dialogBindings); + + callFB(fbAppID); }; - this.initialize = initialize; this.showEmailDialog = showEmailDialog; this.showGoogleDialog = showGoogleDialog; @@ -228,4 +294,4 @@ } return this; -})(window,jQuery); \ No newline at end of file +})(window,jQuery); diff --git a/web/app/assets/javascripts/shareDialog.js b/web/app/assets/javascripts/shareDialog.js index ddf0640ce..dff0ce41b 100644 --- a/web/app/assets/javascripts/shareDialog.js +++ b/web/app/assets/javascripts/shareDialog.js @@ -10,6 +10,10 @@ } + function showDialog() { + app.layout.showDialog('share-dialog'); + } + /*function showEmailDialog() { $('#invitation-dialog').show(); $('#invitation-textarea-container').show(); @@ -94,8 +98,8 @@ app.bindDialog('shareSessionRecording', dialogBindings); }; - this.initialize = initialize; + this.showDialog = showDialog; } return this; diff --git a/web/app/assets/javascripts/sidebar.js b/web/app/assets/javascripts/sidebar.js index 23e84db36..f946c6149 100644 --- a/web/app/assets/javascripts/sidebar.js +++ b/web/app/assets/javascripts/sidebar.js @@ -442,6 +442,11 @@ invitationDialog.showGoogleDialog(); return false; }); + + $('#sidebar-div .btn-facebook-invitation').click(function(evt) { + invitationDialog.showFacebookDialog(evt); + return false; + }); } function registerFriendUpdate() { diff --git a/web/app/assets/javascripts/user_dropdown.js b/web/app/assets/javascripts/user_dropdown.js index b003f2d76..2973dda7d 100644 --- a/web/app/assets/javascripts/user_dropdown.js +++ b/web/app/assets/javascripts/user_dropdown.js @@ -37,6 +37,10 @@ invitationDialog.showEmailDialog(); }); + $('.invite-friends .facebook-invite a').on('click', function(e) { + invitationDialog.showFacebookDialog(e); + }); + $('#header-avatar').on('avatar_changed', function(event, newAvatarUrl) { updateAvatar(newAvatarUrl); event.preventDefault(); diff --git a/web/app/assets/stylesheets/client/content.css.scss b/web/app/assets/stylesheets/client/content.css.scss index 440d926f2..a7a4d6584 100644 --- a/web/app/assets/stylesheets/client/content.css.scss +++ b/web/app/assets/stylesheets/client/content.css.scss @@ -430,7 +430,7 @@ ul.shortcuts { color:#FFCC00; } - li.google-invite, li.email-invite { + li.google-invite, li.email-invite, li.facebook-invite { padding-left:9px; } diff --git a/web/app/controllers/api_invited_users_controller.rb b/web/app/controllers/api_invited_users_controller.rb index d0540f24d..66c1a9340 100644 --- a/web/app/controllers/api_invited_users_controller.rb +++ b/web/app/controllers/api_invited_users_controller.rb @@ -10,7 +10,11 @@ class ApiInvitedUsersController < ApiController end def show - @invited_user = InvitedUser.find(params[:id]) + if 'facebook' == params[:id] + @invited_user = current_user.facebook_invite! + else + @invited_user = InvitedUser.find(params[:id]) + end end def create diff --git a/web/app/controllers/users_controller.rb b/web/app/controllers/users_controller.rb index 1192408f7..24411b3d4 100644 --- a/web/app/controllers/users_controller.rb +++ b/web/app/controllers/users_controller.rb @@ -29,7 +29,7 @@ class UsersController < ApplicationController @invited_user = load_invited_user(params) - if !@invited_user.nil? && @invited_user.accepted + if !@invited_user.nil? && @invited_user.email && @invited_user.accepted # short-circuit out if this invitation is already accepted render "already_signed_up", :layout => 'landing' return diff --git a/web/app/views/api_invited_users/invited_user.rabl b/web/app/views/api_invited_users/invited_user.rabl index 3bce327fa..f86d788c1 100644 --- a/web/app/views/api_invited_users/invited_user.rabl +++ b/web/app/views/api_invited_users/invited_user.rabl @@ -1,3 +1,4 @@ object @invited_user -attributes :id, :created_at, :updated_at, :email, :note, :accepted \ No newline at end of file +attributes :id, :created_at, :updated_at, :email, :note, :accepted + diff --git a/web/app/views/api_invited_users/show.rabl b/web/app/views/api_invited_users/show.rabl index e62c95f69..4e95e7c91 100644 --- a/web/app/views/api_invited_users/show.rabl +++ b/web/app/views/api_invited_users/show.rabl @@ -1,3 +1,5 @@ object @invited_user extends "api_invited_users/invited_user" + +node :signup_url do @invited_user.generate_signup_url end diff --git a/web/app/views/clients/_createSession.html.erb b/web/app/views/clients/_createSession.html.erb index 7a0b3f565..c37a4af3e 100644 --- a/web/app/views/clients/_createSession.html.erb +++ b/web/app/views/clients/_createSession.html.erb @@ -98,16 +98,14 @@
E-mail
- diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 2a504baab..7ddf73b83 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -45,6 +45,7 @@ <%= render "clients/banners/disconnected" %> <%= render "overlay_small" %> <%= render "help" %> +