diff --git a/admin/Gemfile b/admin/Gemfile index 7ca5eda28..8a4306801 100644 --- a/admin/Gemfile +++ b/admin/Gemfile @@ -1,4 +1,4 @@ -source 'https://rubygems.org' +source 'http://rubygems.org' source 'https://jamjam:blueberryjam@int.jamkazam.com/gems/' devenv = ENV["BUILD_NUMBER"].nil? # Jenkins sets a build number environment variable diff --git a/admin/app/controllers/artifacts_controller.rb b/admin/app/controllers/artifacts_controller.rb index 8e7012eff..3fb5cc5cc 100644 --- a/admin/app/controllers/artifacts_controller.rb +++ b/admin/app/controllers/artifacts_controller.rb @@ -11,12 +11,16 @@ class ArtifactsController < ApplicationController file = params[:file] environment = params[:environment] - @artifact = ArtifactUpdate.find_or_create_by_product_and_environment(product, environment) + ArtifactUpdate.transaction do + # VRFS-1071: Postpone client update notification until installer is available for download + ArtifactUpdate.connection.execute('SET TRANSACTION ISOLATION LEVEL READ COMMITTED') + @artifact = ArtifactUpdate.find_or_create_by_product_and_environment(product, environment) - @artifact.version = version - @artifact.uri = file + @artifact.version = version + @artifact.uri = file - @artifact.save + @artifact.save + end unless @artifact.errors.any? render :json => {}, :status => :ok diff --git a/admin/config/boot.rb b/admin/config/boot.rb index 2b59194e5..56e91c277 100644 --- a/admin/config/boot.rb +++ b/admin/config/boot.rb @@ -12,7 +12,9 @@ module Rails class Server alias :default_options_alias :default_options def default_options - default_options_alias.merge!(:Port => 3333) + default_options_alias.merge!( + :Port => 3333 + ENV['JAM_INSTANCE'].to_i, + :pid => File.expand_path("tmp/pids/server-#{ENV['JAM_INSTANCE'].to_i}.pid")) end end end \ No newline at end of file diff --git a/admin/script/package/jam-admin.conf b/admin/script/package/jam-admin.conf index 43e8a34cc..c429d3209 100644 --- a/admin/script/package/jam-admin.conf +++ b/admin/script/package/jam-admin.conf @@ -6,4 +6,10 @@ stop on runlevel [016] setuid jam-admin setgid jam-admin +pre-start script + set -e + mkdir -p /var/run/jam-admin + chown jam-admin:jam-admin /var/run/jam-admin +end script + exec start-stop-daemon --start --chdir /var/lib/jam-admin --exec /var/lib/jam-admin/script/package/upstart-run.sh diff --git a/admin/script/package/post-install.sh b/admin/script/package/post-install.sh index 9949dce43..1ed894877 100755 --- a/admin/script/package/post-install.sh +++ b/admin/script/package/post-install.sh @@ -14,9 +14,7 @@ mkdir -p /var/lib/$NAME/log mkdir -p /var/lib/$NAME/tmp mkdir -p /etc/$NAME mkdir -p /var/log/$NAME -mkdir -p /var/run/$NAME chown -R $USER:$GROUP /var/lib/$NAME chown -R $USER:$GROUP /etc/$NAME chown -R $USER:$GROUP /var/log/$NAME -chown -R $USER:$GROUP /var/run/$NAME diff --git a/db/Gemfile b/db/Gemfile index 45bf466d6..0b7addf09 100644 --- a/db/Gemfile +++ b/db/Gemfile @@ -1,4 +1,4 @@ -source 'https://rubygems.org' +source 'http://rubygems.org' # Assumes you have already cloned pg_migrate_ruby in your workspace # $ cd [workspace] diff --git a/db/manifest b/db/manifest index ec3b567a2..042e0661c 100755 --- a/db/manifest +++ b/db/manifest @@ -96,4 +96,6 @@ ms_user_history_add_instruments.sql icecast_config_changed.sql invited_users_facebook_support.sql first_recording_at.sql -share_token.sql \ No newline at end of file +share_token.sql +facebook_signup.sql +audiomixer_mp3.sql \ No newline at end of file diff --git a/db/up/audiomixer_mp3.sql b/db/up/audiomixer_mp3.sql new file mode 100644 index 000000000..d48cfed01 --- /dev/null +++ b/db/up/audiomixer_mp3.sql @@ -0,0 +1,9 @@ +-- add idea of a mix having mp3 as well as ogg + +ALTER TABLE mixes RENAME COLUMN md5 TO ogg_md5; +ALTER TABLE mixes RENAME COLUMN length TO ogg_length; +ALTER TABLE mixes RENAME COLUMN url TO ogg_url; + +ALTER TABLE mixes ADD COLUMN mp3_md5 VARCHAR(100); +ALTER TABLE mixes ADD COLUMN mp3_length INTEGER; +ALTER TABLE mixes ADD COLUMN mp3_url VARCHAR(1024); \ No newline at end of file diff --git a/db/up/facebook_signup.sql b/db/up/facebook_signup.sql new file mode 100644 index 000000000..d27ec0cdd --- /dev/null +++ b/db/up/facebook_signup.sql @@ -0,0 +1,16 @@ +-- when a user authorizes our application to signup, we create this row +CREATE UNLOGGED TABLE facebook_signups ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + lookup_id VARCHAR(255) UNIQUE NOT NULL, + last_name VARCHAR(100), + first_name VARCHAR(100), + gender VARCHAR(1), + email VARCHAR(1024), + uid VARCHAR(1024), + token VARCHAR(1024), + token_expires_at TIMESTAMP, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +ALTER TABLE user_authorizations ADD CONSTRAINT user_authorizations_uniqkey UNIQUE (provider, uid); \ No newline at end of file diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 6ab7a51bb..aa036793d 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -35,6 +35,7 @@ require "jam_ruby/resque/resque_hooks" require "jam_ruby/resque/scheduled/audiomixer_retry" require "jam_ruby/resque/scheduled/icecast_config_retry" require "jam_ruby/resque/scheduled/icecast_source_check" +require "jam_ruby/resque/scheduled/cleanup_facebook_signup" require "jam_ruby/mq_router" require "jam_ruby/base_manager" require "jam_ruby/connection_manager" @@ -117,6 +118,7 @@ require "jam_ruby/models/icecast_server_socket" require "jam_ruby/models/icecast_template_socket" require "jam_ruby/models/icecast_server_group" require "jam_ruby/models/icecast_mount_template" +require "jam_ruby/models/facebook_signup" include Jampb diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.html.erb index a6abfd81a..d5c91c673 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.html.erb @@ -13,7 +13,7 @@ <% end %> <%= user.biography %>

-Profile   +Profile   diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.text.erb index c69f9965f..13e391154 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.text.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.text.erb @@ -1,7 +1,7 @@ New JamKazam Musicians in your Area <% @new_nearby.each do |user| %> -<%= user.name %> (http://<%= @host %>/#/profile/<%= user.id %>) +<%= user.name %> (http://<%= @host %>/client#/profile/<%= user.id %>) <%= user.location %> <% user.instruments.collect { |inst| inst.description }.join(', ') %> <%= user.biography %> diff --git a/ruby/lib/jam_ruby/models/facebook_signup.rb b/ruby/lib/jam_ruby/models/facebook_signup.rb new file mode 100644 index 000000000..9e232b2a1 --- /dev/null +++ b/ruby/lib/jam_ruby/models/facebook_signup.rb @@ -0,0 +1,15 @@ +module JamRuby + class FacebookSignup < ActiveRecord::Base + + before_create :generate_lookup_id + + def self.delete_old + FacebookSignup.where("created_at < :week", {:week => 1.week.ago}).delete_all + end + + private + def generate_lookup_id + self.lookup_id = SecureRandom.urlsafe_base64 + end + end +end diff --git a/ruby/lib/jam_ruby/models/invited_user.rb b/ruby/lib/jam_ruby/models/invited_user.rb index 228fc024f..cebc0d25e 100644 --- a/ruby/lib/jam_ruby/models/invited_user.rb +++ b/ruby/lib/jam_ruby/models/invited_user.rb @@ -65,7 +65,7 @@ module JamRuby def generate_signup_url if 'development'==Rails.env - "http://jamkazamdev.local:3000/signup?invitation_code=#{self.invitation_code}" + "http://localhost:3000/signup?invitation_code=#{self.invitation_code}" else "http://www.jamkazam.com/signup?invitation_code=#{self.invitation_code}" end diff --git a/ruby/lib/jam_ruby/models/mix.rb b/ruby/lib/jam_ruby/models/mix.rb index 66a58e1e5..9ec1c1e66 100644 --- a/ruby/lib/jam_ruby/models/mix.rb +++ b/ruby/lib/jam_ruby/models/mix.rb @@ -17,7 +17,8 @@ module JamRuby mix = Mix.new mix.recording = recording mix.save - mix.url = construct_filename(mix.created_at, recording.id, mix.id) + mix.ogg_url = construct_filename(mix.created_at, recording.id, mix.id, type='ogg') + mix.mp3_url = construct_filename(mix.created_at, recording.id, mix.id, type='mp3') if mix.save mix.enqueue end @@ -27,7 +28,7 @@ module JamRuby def enqueue begin - Resque.enqueue(AudioMixer, self.id, self.sign_put) + Resque.enqueue(AudioMixer, self.id, self.sign_put(3600 * 24, 'ogg'), self.sign_put(3600 * 24, 'mp3')) rescue # implies redis is down. we don't update started_at false @@ -51,10 +52,12 @@ module JamRuby save end - def finish(length, md5) + def finish(ogg_length, ogg_md5, mp3_length, mp3_md5) self.completed_at = Time.now - self.length = length - self.md5 = md5 + self.ogg_length = ogg_length + self.ogg_md5 = ogg_md5 + self.mp3_length = mp3_length + self.mp3_md5 = mp3_md5 self.completed = true if save Notification.send_recording_master_mix_complete(recording) @@ -74,41 +77,56 @@ module JamRuby manifest["timeline"] << { "timestamp" => 0, "mix" => mix_params } manifest["output"] = { "codec" => "vorbis" } - manifest["recording_id"] = self.id + manifest["recording_id"] = self.recording.id manifest end - def s3_url - s3_manager.s3_url(url) + def s3_url(type='ogg') + if type == 'ogg' + s3_manager.s3_url(ogg_url) + else + s3_manager.s3_url(mp3_url) + end + + end def is_completed completed end - def sign_url(expiration_time = 120) + def sign_url(expiration_time = 120, type='ogg') # expire link in 1 minute--the expectation is that a client is immediately following this link - s3_manager.sign_url(self.url, {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false}) + if type == 'ogg' + s3_manager.sign_url(self.ogg_url, {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false}) + else + s3_manager.sign_url(self.mp3_url, {:expires => expiration_time, :response_content_type => 'audio/mp3', :secure => false}) + end end - def sign_put(expiration_time = 3600 * 24) - s3_manager.sign_url(self.url, {:expires => expiration_time, :content_type => 'audio/ogg', :secure => false}, :put) + def sign_put(expiration_time = 3600 * 24, type='ogg') + if type == 'ogg' + s3_manager.sign_url(self.ogg_url, {:expires => expiration_time, :content_type => 'audio/ogg', :secure => false}, :put) + else + s3_manager.sign_url(self.mp3_url, {:expires => expiration_time, :content_type => 'audio/mp3', :secure => false}, :put) + end end private def delete_s3_files - s3_manager.delete(filename) + s3_manager.delete(filename(type='ogg')) + s3_manager.delete(filename(type='mp3')) end - def filename + def filename(type='ogg') # construct a path for s3 - Mix.construct_filename(self.created_at, self.recording.id, self.id) + Mix.construct_filename(self.created_at, self.recording.id, self.id, type) end - def self.construct_filename(created_at, recording_id, id) + def self.construct_filename(created_at, recording_id, id, type='ogg') raise "unknown ID" unless id - "recordings/#{created_at.strftime('%m-%d-%Y')}/#{recording_id}/mix-#{id}.ogg" + "recordings/#{created_at.strftime('%m-%d-%Y')}/#{recording_id}/mix-#{id}.#{type}" end end end diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb index 450385067..beba63625 100644 --- a/ruby/lib/jam_ruby/models/recording.rb +++ b/ruby/lib/jam_ruby/models/recording.rb @@ -105,9 +105,6 @@ module JamRuby end end - connection = Connection.where(:user_id => owner.id).where(:music_session_id => music_session.id).first - Notification.send_recording_started(music_session, connection, owner) - recording end @@ -122,9 +119,6 @@ module JamRuby self.save end - connection = Connection.where(:user_id => self.owner.id).where(:music_session_id => music_session.id).first - Notification.send_recording_ended(music_session, connection, self.owner) - self end @@ -214,9 +208,9 @@ module JamRuby :type => "mix", :id => mix.id.to_s, :recording_id => mix.recording_id, - :length => mix.length, - :md5 => mix.md5, - :url => mix.url, + :length => mix.ogg_length, + :md5 => mix.ogg_md5, + :url => mix.ogg_url, :created_at => mix.created_at, :next => mix.id } diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 00011b407..e137b8a81 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -697,8 +697,23 @@ module JamRuby # throws ActiveRecord::RecordNotFound if instrument is invalid # throws an email delivery error if unable to connect out to SMTP - def self.signup(first_name, last_name, email, password, password_confirmation, terms_of_service, - location, instruments, birth_date, musician, photo_url, invited_user, signup_confirm_url) + def self.signup(options) + + first_name = options[:first_name] + last_name = options[:last_name] + email = options[:email] + password = options[:password] + password_confirmation = options[:password_confirmation] + terms_of_service = options[:terms_of_service] + location = options[:location] + instruments = options[:instruments] + birth_date = options[:birth_date] + musician = options[:musician] + photo_url = options[:photo_url] + invited_user = options[:invited_user] + fb_signup = options[:fb_signup] + signup_confirm_url = options[:signup_confirm_url] + user = User.new UserManager.active_record_transaction do |user_manager| @@ -744,11 +759,25 @@ module JamRuby user.photo_url = photo_url + unless fb_signup.nil? + user.update_fb_authorization(fb_signup) + + if fb_signup.email.casecmp(user.email).zero? + user.email_confirmed = true + user.signup_token = nil + else + user.email_confirmed = false + user.signup_token = SecureRandom.urlsafe_base64 + end + end if invited_user.nil? user.can_invite = Limits::USERS_CAN_INVITE - user.email_confirmed = false - user.signup_token = SecureRandom.urlsafe_base64 + + unless user.email_confirmed # important that the only time this goes true is if some other mechanism, like fb_signup, set this high + user.email_confirmed = false + user.signup_token = SecureRandom.urlsafe_base64 + end else # if you are invited by an admin, we'll say you can invite too. # but if not, then you can not invite @@ -785,15 +814,10 @@ module JamRuby if user.errors.any? raise ActiveRecord::Rollback else - # don't send an signup email if the user was invited already *and* they used the same email that they were invited with - if !invited_user.nil? && invited_user.email.casecmp(user.email).zero? + # don't send an signup email if email is already confirmed + if user.email_confirmed UserMailer.welcome_message(user).deliver else - - # FIXME: - # It's not standard to require a confirmation when a user signs up with Facebook. - # We should stop asking for it. - # # any errors here should also rollback the transaction; that's OK. If emails aren't going to be delivered, # it's already a really bad situation; make user signup again UserMailer.confirm_email(user, signup_confirm_url.nil? ? nil : (signup_confirm_url + "/" + user.signup_token) ).deliver @@ -941,6 +965,30 @@ module JamRuby 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 + + 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, 'facebook') + end + + if user_authorization.nil? + self.user_authorizations.build provider: 'facebook', + uid: fb_signup.uid, + token: fb_signup.token, + token_expiration: fb_signup.token_expires_at + else + user_authorization.uid = fb_signup.uid + user_authorization.token = fb_signup.token + user_authorization.token_expiration = fb_signup.token_expires_at + end + end + end + def provides_location? !self.city.blank? && (!self.state.blank? || !self.country.blank?) end diff --git a/ruby/lib/jam_ruby/models/user_authorization.rb b/ruby/lib/jam_ruby/models/user_authorization.rb index 5f7c97e25..47061e17e 100644 --- a/ruby/lib/jam_ruby/models/user_authorization.rb +++ b/ruby/lib/jam_ruby/models/user_authorization.rb @@ -9,8 +9,10 @@ module JamRuby belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "user_id" validates :provider, :uid, :presence => true + validates_uniqueness_of :uid, scope: :provider # token and token_expiration can be missing - + + end end diff --git a/ruby/lib/jam_ruby/resque/audiomixer.rb b/ruby/lib/jam_ruby/resque/audiomixer.rb index 3424b11d0..dd1a304e0 100644 --- a/ruby/lib/jam_ruby/resque/audiomixer.rb +++ b/ruby/lib/jam_ruby/resque/audiomixer.rb @@ -13,14 +13,16 @@ module JamRuby @@log = Logging.logger[AudioMixer] - attr_accessor :mix_id, :manifest, :manifest_file, :output_filename, :error_out_filename, :postback_output_url, + attr_accessor :mix_id, :manifest, :manifest_file, :output_filename, :error_out_filename, + :postback_ogg_url, :postback_mp3_url, :error_reason, :error_detail - def self.perform(mix_id, postback_output_url) + def self.perform(mix_id, postback_ogg_url, postback_mp3_url) JamWebEventMachine.run_wait_stop do audiomixer = AudioMixer.new() - audiomixer.postback_output_url = postback_output_url + audiomixer.postback_ogg_url = postback_ogg_url + audiomixer.postback_mp3_url = postback_mp3_url audiomixer.mix_id = mix_id audiomixer.run end @@ -123,12 +125,13 @@ module JamRuby # make a suitable location to store the output mix, and pass the chosen filepath into the manifest def prepare_output - @output_filename = Dir::Tmpname.make_tmpname( ["#{Dir.tmpdir}/audiomixer-output-#{@manifest[:recording_id]}", '.ogg'], nil) + @output_ogg_filename = Dir::Tmpname.make_tmpname( ["#{Dir.tmpdir}/audiomixer-output-#{@manifest[:recording_id]}", '.ogg'], nil) + @output_mp3_filename = Dir::Tmpname.make_tmpname( ["#{Dir.tmpdir}/audiomixer-output-#{@manifest[:recording_id]}", '.mp3'], nil) # update manifest so that audiomixer writes here - @manifest[:output][:filename] = @output_filename + @manifest[:output][:filename] = @output_ogg_filename - @@log.debug("output ogg file: #{@output_filename}") + @@log.debug("output ogg file: #{@output_ogg_filename}, output mp3 file: #{@output_mp3_filename}") end # make a suitable location to store an output error file, which will be populated on failure to help diagnose problems. @@ -157,39 +160,62 @@ module JamRuby end def postback - raise "no output file after mix" unless File.exist? @output_filename - @@log.debug("posting mix to #{@postback_output_url}") + @@log.debug("posting ogg mix to #{@postback_ogg_url}") - uri = URI.parse(@postback_output_url) + uri = URI.parse(@postback_ogg_url) http = Net::HTTP.new(uri.host, uri.port) request = Net::HTTP::Put.new(uri.request_uri) response = nil - File.open(@output_filename,"r") do |f| + File.open(@output_ogg_filename,"r") do |f| request.body_stream=f request["Content-Type"] = "audio/ogg" - request.add_field('Content-Length', File.size(@output_filename)) + request.add_field('Content-Length', File.size(@output_ogg_filename)) response = http.request(request) end response_code = response.code.to_i unless response_code >= 200 && response_code <= 299 - @error_reason = "postback-mix-to-s3" - raise "unable to put to url: #{@postback_output_url}, status: #{response.code}, body: #{response.body}" + @error_reason = "postback-ogg-mix-to-s3" + raise "unable to put to url: #{@postback_ogg_url}, status: #{response.code}, body: #{response.body}" + end + + + @@log.debug("posting mp3 mix to #{@postback_mp3_url}") + + uri = URI.parse(@postback_mp3_url) + http = Net::HTTP.new(uri.host, uri.port) + request = Net::HTTP::Put.new(uri.request_uri) + + response = nil + File.open(@output_mp3_filename,"r") do |f| + request.body_stream=f + request["Content-Type"] = "audio/mp3" + request.add_field('Content-Length', File.size(@output_mp3_filename)) + response = http.request(request) + end + + response_code = response.code.to_i + unless response_code >= 200 && response_code <= 299 + @error_reason = "postback-mp3-mix-to-s3" + raise "unable to put to url: #{@postback_mp3_url}, status: #{response.code}, body: #{response.body}" end end def post_success(mix) - raise "no output file after mix" unless File.exist? @output_filename - length = File.size(@output_filename) + ogg_length = File.size(@output_ogg_filename) + ogg_md5 = Digest::MD5.new + File.open(@output_ogg_filename, 'rb').each {|line| ogg_md5.update(line)} - md5 = Digest::MD5.new - File.open(@output_filename, 'rb').each {|line| md5.update(line)} + mp3_length = File.size(@output_mp3_filename) + mp3_md5 = Digest::MD5.new + File.open(@output_mp3_filename, 'rb').each {|line| mp3_md5.update(line)} - mix.finish(length, md5.to_s) + + mix.finish(ogg_length, ogg_md5.to_s, mp3_length, mp3_md5.to_s) end def post_error(mix, e) @@ -233,16 +259,12 @@ module JamRuby execute(@manifest_file) - if $? == 0 - postback - post_success(mix) - @@log.info("audiomixer job successful. mix_id #{mix_id}") - else - parse_error_out - error_msg = "audiomixer job failed status=#{$?} error_reason=#{error_reason} error_detail=#{error_detail}" - @@log.info(error_msg) - raise error_msg - end + postback + + post_success(mix) + + @@log.info("audiomixer job successful. mix_id #{mix_id}") + rescue Exception => e post_error(mix, e) raise @@ -260,20 +282,41 @@ module JamRuby unless File.exist? APP_CONFIG.audiomixer_path @@log.error("unable to find audiomixer") - error_msg = "audiomixer job failed status=#{$?} error_reason=#{error_reason} error_detail=#{error_detail}" + error_msg = "audiomixer job failed status=#{$?} error_reason=#{@error_reason} error_detail=#{@error_detail}" @@log.info(error_msg) @error_reason = "unable-find-appmixer" @error_detail = APP_CONFIG.audiomixer_path raise error_msg end - audiomixer_cmd = "#{APP_CONFIG.audiomixer_path} #{manifest_file}" - @@log.debug("executing #{audiomixer_cmd}") system(audiomixer_cmd) + + unless $? == 0 + parse_error_out + error_msg = "audiomixer job failed status=#{$?} error_reason=#{@error_reason} error_detail=#{@error_detail}" + @@log.info(error_msg) + raise error_msg + end + + raise "no output ogg file after mix" unless File.exist? @output_ogg_filename + + ffmpeg_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{@output_ogg_filename}\" -ab 128k -metadata JamRecordingId=#{@manifest[:recording_id]} -metadata JamMixId=#{@mix_id} -metadata JamType=Mix \"#{@output_mp3_filename}\"" + + system(ffmpeg_cmd) + + unless $? == 0 + @error_reason = 'ffmpeg-failed' + @error_detail = $?.to_s + error_msg = "ffmpeg failed status=#{$?} error_reason=#{@error_reason} error_detail=#{@error_detail}" + @@log.info(error_msg) + raise error_msg + end + + raise "no output mp3 file after conversion" unless File.exist? @output_mp3_filename end def symbolize_keys(obj) diff --git a/ruby/lib/jam_ruby/resque/scheduled/cleanup_facebook_signup.rb b/ruby/lib/jam_ruby/resque/scheduled/cleanup_facebook_signup.rb new file mode 100644 index 000000000..a2cbab476 --- /dev/null +++ b/ruby/lib/jam_ruby/resque/scheduled/cleanup_facebook_signup.rb @@ -0,0 +1,19 @@ + +module JamRuby + class CleanupFacebookSignup + + @queue = :cleanup_facebook_signup + + @@log = Logging.logger[CleanupFacebookSignup] + + + def self.perform + @@log.debug("waking up") + + FacebookSignup.delete_old + + @@log.debug("done") + end + + end +end diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 68b71c553..a8eda4e0a 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -317,4 +317,14 @@ FactoryGirl.define do association :authentication, :factory => :icecast_user_authentication end + factory :facebook_signup, :class => JamRuby::FacebookSignup do + sequence(:lookup_id) { |n| "lookup-#{n}"} + sequence(:first_name) { |n| "first-#{n}"} + sequence(:last_name) { |n| "last-#{n}"} + gender 'M' + sequence(:email) { |n| "jammin-#{n}@jamkazam.com"} + sequence(:uid) { |n| "uid-#{n}"} + sequence(:token) { |n| "token-#{n}"} + token_expires_at Time.now + end end diff --git a/ruby/spec/jam_ruby/models/facebook_signup_spec.rb b/ruby/spec/jam_ruby/models/facebook_signup_spec.rb new file mode 100644 index 000000000..6edbab157 --- /dev/null +++ b/ruby/spec/jam_ruby/models/facebook_signup_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe FacebookSignup do + + it "does not delete new one" do + new_signup = FactoryGirl.create(:facebook_signup) + + FacebookSignup.delete_old + + FacebookSignup.find(new_signup) + end + + it "does delete old one" do + old_signup = FactoryGirl.create(:facebook_signup, :created_at => 10.days.ago) + + FacebookSignup.delete_old + + FacebookSignup.find_by_id(old_signup.id).should be_nil + end +end diff --git a/ruby/spec/jam_ruby/models/mix_spec.rb b/ruby/spec/jam_ruby/models/mix_spec.rb index bb361bc99..359100423 100755 --- a/ruby/spec/jam_ruby/models/mix_spec.rb +++ b/ruby/spec/jam_ruby/models/mix_spec.rb @@ -26,11 +26,19 @@ describe Mix do end it "should record when a mix has finished" do - Mix.find(@mix.id).finish(10000, "md5hash") + Mix.find(@mix.id).finish(10000, "md5hash", 10000, "md5hash") @mix.reload @mix.completed_at.should_not be_nil - @mix.length.should == 10000 - @mix.md5.should == "md5hash" + @mix.ogg_length.should == 10000 + @mix.ogg_md5.should == "md5hash" + end + + it "create a good manifest" do + Mix.find(@mix.id).finish(10000, "md5hash", 10000, "md5hash") + @mix.reload + manifest = @mix.manifest + manifest["recording_id"].should == @recording.id + manifest["files"].length.should == 1 end it "signs url" do @@ -40,7 +48,7 @@ describe Mix do it "mixes are restricted by user" do - @mix.finish(1, "abc") + @mix.finish(1, "abc", 1, "def") @mix.reload @mix.errors.any?.should be_false diff --git a/ruby/spec/jam_ruby/models/user_spec.rb b/ruby/spec/jam_ruby/models/user_spec.rb index a29179a8a..a069e7ee0 100644 --- a/ruby/spec/jam_ruby/models/user_spec.rb +++ b/ruby/spec/jam_ruby/models/user_spec.rb @@ -397,6 +397,34 @@ describe User do end end + describe "user_authorizations" do + + it "can create" do + @user.user_authorizations.build provider: 'facebook', + uid: '1', + token: '1', + token_expiration: Time.now + @user.save! + end + + it "fails on duplicate" do + @user.user_authorizations.build provider: 'facebook', + uid: '1', + token: '1', + token_expiration: Time.now + @user.save! + + @user2 = FactoryGirl.create(:user) + @user2.user_authorizations.build provider: 'facebook', + uid: '1', + token: '1', + token_expiration: Time.now + @user2.save.should be_false + @user2.errors[:user_authorizations].should == ['is invalid'] + end + + + end =begin describe "update avatar" do diff --git a/ruby/spec/jam_ruby/resque/audiomixer_spec.rb b/ruby/spec/jam_ruby/resque/audiomixer_spec.rb index 5b27e35d7..5411acc39 100644 --- a/ruby/spec/jam_ruby/resque/audiomixer_spec.rb +++ b/ruby/spec/jam_ruby/resque/audiomixer_spec.rb @@ -233,8 +233,8 @@ describe AudioMixer do @mix.reload @mix.completed.should be_true - @mix.length.should == 0 - @mix.md5.should == 'd41d8cd98f00b204e9800998ecf8427e' # that's the md5 of a touched file, which is what the stubbed :execute does + @mix.ogg_length.should == 0 + @mix.ogg_md5.should == 'd41d8cd98f00b204e9800998ecf8427e' # that's the md5 of a touched file, which is what the stubbed :execute does end it "bails out with no error if already completed" do diff --git a/ruby/spec/support/utilities.rb b/ruby/spec/support/utilities.rb index 52d00aea6..8886ac7bb 100644 --- a/ruby/spec/support/utilities.rb +++ b/ruby/spec/support/utilities.rb @@ -21,6 +21,10 @@ def app_config ENV['AUDIOMIXER_PATH'] || audiomixer_workspace_path || "/var/lib/audiomixer/audiomixer/audiomixerapp" end + def ffmpeg_path + ENV['FFMPEG_PATH'] || '/usr/local/bin/ffmpeg' + end + def icecast_reload_cmd 'true' # as in, /bin/true end diff --git a/web/Gemfile b/web/Gemfile index ac5e4551f..93b0ffd53 100644 --- a/web/Gemfile +++ b/web/Gemfile @@ -1,4 +1,4 @@ -source 'https://rubygems.org' +source 'http://rubygems.org' unless ENV["LOCAL_DEV"] == "1" source 'https://jamjam:blueberryjam@int.jamkazam.com/gems/' diff --git a/web/app/assets/images/content/button_facebook_signin.png b/web/app/assets/images/content/button_facebook_signin.png new file mode 100644 index 000000000..08b15b7ba Binary files /dev/null and b/web/app/assets/images/content/button_facebook_signin.png differ diff --git a/web/app/assets/javascripts/bandProfile.js b/web/app/assets/javascripts/bandProfile.js index 7551163e3..8cae06e1b 100644 --- a/web/app/assets/javascripts/bandProfile.js +++ b/web/app/assets/javascripts/bandProfile.js @@ -152,12 +152,14 @@ $('#btn-follow-band').text('STOP FOLLOWING'); $('#btn-follow-band').click(function() { removeFollowing(true, bandId); + return false; }); } else { $('#btn-follow-band').text('FOLLOW'); $('#btn-follow-band').click(function() { addFollowing(true, bandId); + return false; }); } } @@ -176,12 +178,14 @@ $btnFollowMember.text('UN-FOLLOW'); $btnFollowMember.click(function() { removeFollowing(false, userId); + return false; }); } else { $btnFollowMember.text('FOLLOW'); $btnFollowMember.click(function() { addFollowing(false, userId); + return false; }); } } @@ -385,7 +389,7 @@ var template = $('#template-band-profile-members').html(); var memberHtml = context.JK.fillTemplate(template, { userId: musician.id, - profile_url: "/#/profile/" + musician.id, + profile_url: "/client#/profile/" + musician.id, avatar_url: context.JK.resolveAvatarUrl(musician.photo_url), name: musician.name, location: musician.location, @@ -424,6 +428,8 @@ $divMember.remove(); }) .fail(app.ajaxError); + + return false; }); } else { @@ -475,7 +481,8 @@ $("#btn-edit-band-profile").click(function() { $('div[layout-id="band/setup"] .hdn-band-id').val(bandId); - context.location = "#/band/setup"; + context.location = "/client#/band/setup"; + return false; }); } diff --git a/web/app/assets/javascripts/findBand.js b/web/app/assets/javascripts/findBand.js index 3fcfb3960..bcedec98b 100644 --- a/web/app/assets/javascripts/findBand.js +++ b/web/app/assets/javascripts/findBand.js @@ -106,7 +106,7 @@ playerVals = { player_name: aPlayer.name, - profile_url: '/#/profile/' + aPlayer.user_id, + profile_url: '/client#/profile/' + aPlayer.user_id, avatar_url: context.JK.resolveAvatarUrl(aPlayer.photo_url), player_instruments: player_instrs }; @@ -126,7 +126,7 @@ bVals = { avatar_url: context.JK.resolveAvatarUrl(bb.photo_url), - profile_url: "/#/profile/" + bb.id, + profile_url: "/client#/profile/" + bb.id, band_name: bb.name, band_location: bb.city + ', ' + bb.state, genres: bgenres, diff --git a/web/app/assets/javascripts/findMusician.js b/web/app/assets/javascripts/findMusician.js index 91ecca218..9293852c9 100644 --- a/web/app/assets/javascripts/findMusician.js +++ b/web/app/assets/javascripts/findMusician.js @@ -106,14 +106,14 @@ aFollow = mm['followings'][jj]; followVals = { musician_name: aFollow.name, - profile_url: '/#/profile/' + aFollow.user_id, + profile_url: '/client#/profile/' + aFollow.user_id, avatar_url: context.JK.resolveAvatarUrl(aFollow.photo_url), }; follows += context.JK.fillTemplate(fTemplate, followVals); if (2 == jj) break; } var actionVals = { - profile_url: "/#/profile/" + mm.id, + profile_url: "/client#/profile/" + mm.id, friend_class: 'button-' + (mm['is_friend'] ? 'grey' : 'orange'), friend_caption: (mm.is_friend ? 'DIS':'')+'CONNECT', follow_class: 'button-' + (mm['is_following'] ? 'grey' : 'orange'), @@ -124,7 +124,7 @@ mVals = { avatar_url: context.JK.resolveAvatarUrl(mm.photo_url), - profile_url: "/#/profile/" + mm.id, + profile_url: "/client#/profile/" + mm.id, musician_name: mm.name, musician_location: mm.city + ', ' + mm.state, instruments: instr_logos, diff --git a/web/app/assets/javascripts/homeScreen.js b/web/app/assets/javascripts/homeScreen.js index 9f94bc624..fd6d58ec9 100644 --- a/web/app/assets/javascripts/homeScreen.js +++ b/web/app/assets/javascripts/homeScreen.js @@ -88,7 +88,7 @@ events(); $('.profile').on('click', function() { - context.location = '#/profile/' + context.JK.currentUserId; + context.location = '/client#/profile/' + context.JK.currentUserId; }); }; diff --git a/web/app/assets/javascripts/invitationDialog.js b/web/app/assets/javascripts/invitationDialog.js index 8fd550df9..044b54bbf 100644 --- a/web/app/assets/javascripts/invitationDialog.js +++ b/web/app/assets/javascripts/invitationDialog.js @@ -212,6 +212,9 @@ }; function fbFeedDialogCallback(response) { //console.log("feedback dialog closed: " + response['post_id']) + if (response['post_id']) { + context.JK.GA.trackServiceInvitations(context.JK.GA.InvitationTypes.facebook, 1); + } } FB.ui(obj, fbFeedDialogCallback); } diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 2ffa8bb9f..fa3ae4ea3 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -199,6 +199,19 @@ }); } + function login(options) { + var url = '/api/auths/login'; + + return $.ajax({ + type: "POST", + dataType: "json", + url: url, + processData: false, + contentType: 'application/json', + data: JSON.stringify(options) + }); + } + function getUserDetail(options) { var id = getId(options); @@ -813,6 +826,7 @@ this.createBandInvitation = createBandInvitation; this.updateBandInvitation = updateBandInvitation; this.removeBandMember = removeBandMember; + this.login = login; return this; }; diff --git a/web/app/assets/javascripts/jamkazam.js b/web/app/assets/javascripts/jamkazam.js index d9ec02fba..fba55b8f8 100644 --- a/web/app/assets/javascripts/jamkazam.js +++ b/web/app/assets/javascripts/jamkazam.js @@ -322,10 +322,17 @@ var hash = context.location.hash; + try { context.RouteMap.parse(hash); } + catch(e) { + console.log("ignoring bogus screen name: %o", hash) + hash = null; + } + var url = '#/home'; if (hash) { url = hash; } + logger.debug("Changing screen to " + url); context.location = url; } diff --git a/web/app/assets/javascripts/playbackControls.js b/web/app/assets/javascripts/playbackControls.js index cf1df1323..1df7e6f1c 100644 --- a/web/app/assets/javascripts/playbackControls.js +++ b/web/app/assets/javascripts/playbackControls.js @@ -168,6 +168,7 @@ currentTimeMs = playbackDurationMs; stopPlay(); endReached = true; + console.log("end reached"); } else { return; diff --git a/web/app/assets/javascripts/searchResults.js b/web/app/assets/javascripts/searchResults.js index ad14a5d84..7795f4076 100644 --- a/web/app/assets/javascripts/searchResults.js +++ b/web/app/assets/javascripts/searchResults.js @@ -91,7 +91,7 @@ var args = { userId: val.id, avatar_url: context.JK.resolveAvatarUrl(val.photo_url), - profile_url: "/#/profile/" + val.id, + profile_url: "/client#/profile/" + val.id, userName: val.name, location: val.location, instruments: getInstrumentHtml(val.instruments) @@ -104,7 +104,7 @@ var invitationSentHtml = context.JK.fillTemplate($(selector).html(), { userId: val.id, first_name: val.first_name, - profile_url: "/#/profile/" + val.id + profile_url: "/client#/profile/" + val.id }); selector = isSidebar ? '#sidebar-search-results' : '#search-results'; @@ -154,7 +154,7 @@ var searchResultHtml = context.JK.fillTemplate($(template_name).html(), { userId: val.id, avatar_url: context.JK.resolveAvatarUrl(val.photo_url), - profile_url: "/#/profile/" + val.id, + profile_url: "/client#/profile/" + val.id, userName: val.name, location: val.location }); diff --git a/web/app/assets/javascripts/sessionList.js b/web/app/assets/javascripts/sessionList.js index 95ac0cde9..0a3fc572f 100644 --- a/web/app/assets/javascripts/sessionList.js +++ b/web/app/assets/javascripts/sessionList.js @@ -93,7 +93,7 @@ var photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url); var musicianVals = { avatar_url: photoUrl, - profile_url: "/#/profile/" + id, + profile_url: "/client#/profile/" + id, musician_name: name, instruments: instrumentLogoHtml }; diff --git a/web/app/assets/javascripts/shareDialog.js b/web/app/assets/javascripts/shareDialog.js index 042cb43ec..53e477470 100644 --- a/web/app/assets/javascripts/shareDialog.js +++ b/web/app/assets/javascripts/shareDialog.js @@ -5,6 +5,7 @@ context.JK.ShareDialog = function(app, entityId, entityType) { var logger = context.JK.logger; var rest = context.JK.Rest(); + var dialogId = '#share-dialog' function socialShare() { var shareWithFacebook = $('.share-with-facebook').is(':checked'); @@ -24,10 +25,17 @@ if (onOff) { - } - else { + $(dialogId + ' .dialog-share-button').unbind('click').click(function(e) { - } + + return false; + }) + } + + this.fb_login = function() { + FB.login(function(response) { + handle_fblogin_response(response); + }, {scope:'publish_stream'}); } function showDialog() { @@ -93,69 +101,6 @@ } } - /*function showEmailDialog() { - $('#invitation-dialog').show(); - $('#invitation-textarea-container').show(); - $('#invitation-checkbox-container').hide(); - $('#btn-send-invitation').show(); - $('#btn-next-invitation').hide(); - clearTextFields(); - app.layout.showDialog('inviteUsers') - } - - function showGoogleDialog() { - $('#invitation-dialog').show(); - $('#invitation-textarea-container').hide(); - $('#invitation-checkbox-container').show(); - $('#btn-send-invitation').hide(); - $('#btn-next-invitation').show(); - clearTextFields(); - - app.layout.showDialog('inviteUsers') - - $('#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(/, $/, ''); - // track how many of these came from google - $('#txt-emails').val(emails).data('google_invite_count', checkedBoxes.length); - }); - }, - error: function() { - $('#invitation-checkboxes').html("Load failed!"); - } - }); - - }; - window._oauth_win = window.open("/auth/google_login", "_blank", "height=500,width=500,menubar=no,resizable=no,status=no"); - } - - function showFacebookDialog() { - window._oauth_callback = function() { - window._oauth_win.close(); - window._oauth_win = null; - window._oauth_callback = null; - }; - window._oauth_win = window.open("/auth/facebook_login", "_blank", "height=500,width=500,menubar=no,resizable=no,status=no"); - }*/ - function clearTextFields() { } @@ -175,9 +120,9 @@ 'afterHide': afterHide }; - app.bindDialog('shareSessionRecording', dialogBindings); + app.bindDialog('shareRecording', dialogBindings); - initDialog(); + // callFB(fbAppID); }; this.initialize = initialize; diff --git a/web/app/assets/javascripts/web/signinDialog.js b/web/app/assets/javascripts/web/signinDialog.js new file mode 100644 index 000000000..5d1f4e989 --- /dev/null +++ b/web/app/assets/javascripts/web/signinDialog.js @@ -0,0 +1,87 @@ +(function(context,$) { + + "use strict"; + + context.JK = context.JK || {}; + + context.JK.SigninDialog = function(app) { + var logger = context.JK.logger; + var rest = context.JK.Rest(); + var dialogId = '#signin-dialog'; + + function reset() { + $(dialogId + ' #signin-form').removeClass('login-error') + + $(dialogId + ' input[name=email]').val(''); + $(dialogId + ' input[name=password]').val(''); + $(dialogId + ' input[name=remember_me]').attr('checked', 'checked') + } + + function login() { + var email = $(dialogId + ' input[name=email]').val(); + var password = $(dialogId + ' input[name=password]').val(); + var rememberMe = $(dialogId + ' input[name=remember_me]').is(':checked') + + rest.login({email: email, password: password, remember_me: rememberMe}) + .done(function() { + app.layout.closeDialog('signin-dialog') + window.location = '/client' + }) + .fail(function(jqXHR) { + if(jqXHR.status == 422) { + $(dialogId + ' #signin-form').addClass('login-error') + } + else { + app.notifyServerError(jqXHR, "Unable to log in") + } + }) + } + + function events() { + $(dialogId + ' .signin-cancel').click(function(e) { + app.layout.closeDialog('signin-dialog'); + e.stopPropagation(); + return false; + }); + + $(dialogId + ' #signin-form').submit(function(e) { + login(); + return false; + }); + + $(dialogId + ' .signin-submit').click(function(e) { + login(); + return false; + }); + + $(dialogId + ' .show-signup-dialog').click(function(e) { + app.layout.closeDialog('signin-dialog') + app.layout.showDialog('signup-dialog') + return false; + }) + } + + function beforeShow() { + reset(); + } + + function afterHide() { + + } + + function initialize(){ + + var dialogBindings = { + 'beforeShow' : beforeShow, + 'afterHide': afterHide + }; + + app.bindDialog('signin-dialog', dialogBindings); + + events(); + } + + this.initialize = initialize; + + } +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/web/signupDialog.js b/web/app/assets/javascripts/web/signupDialog.js index 5320d5a88..fc126cb57 100644 --- a/web/app/assets/javascripts/web/signupDialog.js +++ b/web/app/assets/javascripts/web/signupDialog.js @@ -10,8 +10,17 @@ var dialogId = '#signup-dialog'; function events() { + $(dialogId + ' .signup-cancel').click(function(e) { + app.layout.closeDialog('signup-dialog'); + e.stopPropagation(); + return false; + }); - + $(dialogId + ' .show-signin-dialog').click(function(e) { + app.layout.closeDialog('signup-dialog') + app.layout.showDialog('signin-dialog') + return false; + }) } function beforeShow() { diff --git a/web/app/assets/javascripts/web/web.js b/web/app/assets/javascripts/web/web.js index 920e82019..e59c4512b 100644 --- a/web/app/assets/javascripts/web/web.js +++ b/web/app/assets/javascripts/web/web.js @@ -7,6 +7,7 @@ //= require globals //= require facebook_helper //= require web/signupDialog +//= require web/signinDialog //= require invitationDialog //= require shareDialog //= require layout @@ -21,3 +22,4 @@ //= require web/congratulations //= require web/sessions //= require web/recordings +//= require web/welcome diff --git a/web/app/assets/javascripts/web/welcome.js b/web/app/assets/javascripts/web/welcome.js index 7d4728e03..582e6d220 100644 --- a/web/app/assets/javascripts/web/welcome.js +++ b/web/app/assets/javascripts/web/welcome.js @@ -10,8 +10,16 @@ e.preventDefault(); return false; }); + + $('#signin').click(function(e) { + context.JK.app.layout.showDialog('signin-dialog'); + e.preventDefault(); + return false; + }); } - initialize() + $(function() { + initialize(); + }) })(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/content.css.scss b/web/app/assets/stylesheets/client/content.css.scss index a7a4d6584..807a4ae93 100644 --- a/web/app/assets/stylesheets/client/content.css.scss +++ b/web/app/assets/stylesheets/client/content.css.scss @@ -190,13 +190,17 @@ font-size:24px; } -.content-wrapper select, .content-wrapper textarea, .content-wrapper input[type=text], .content-wrapper input[type=password], div.friendbox, .ftue-inner input[type=text], .ftue-inner input[type=password], .dialog-inner textarea, .dialog-inner input[type=text], .dialog-inner select { - font-family:"Raleway", arial, sans-serif; - background-color:#c5c5c5; - border:none; - -webkit-box-shadow: inset 2px 2px 3px 0px #888; - box-shadow: inset 2px 2px 3px 0px #888; - color:#666; +.content-wrapper, .dialog, .dialog-inner, .ftue-inner { + + select, textarea, input[type=text], input[type=password], div.friendbox { + font-family:"Raleway", arial, sans-serif; + background-color:#c5c5c5; + border:none; + -webkit-box-shadow: inset 2px 2px 3px 0px #888; + box-shadow: inset 2px 2px 3px 0px #888; + color:#666; + } + } .create-session-description { diff --git a/web/app/assets/stylesheets/client/screen_common.css.scss b/web/app/assets/stylesheets/client/screen_common.css.scss index fe9d3497b..fbc3dbdf9 100644 --- a/web/app/assets/stylesheets/client/screen_common.css.scss +++ b/web/app/assets/stylesheets/client/screen_common.css.scss @@ -353,7 +353,6 @@ small, .small {font-size:11px;} position:absolute; left:50%; top:20%; - margin-left:-150px; background-color:#333; border: 1px solid #ed3618; z-index:1000; diff --git a/web/app/assets/stylesheets/client/user_dropdown.css.scss b/web/app/assets/stylesheets/client/user_dropdown.css.scss index 42e8081af..79096c8b8 100644 --- a/web/app/assets/stylesheets/client/user_dropdown.css.scss +++ b/web/app/assets/stylesheets/client/user_dropdown.css.scss @@ -9,7 +9,6 @@ #profile { float: right; height: 54px; - margin-top: 30px; text-align: right; ul { diff --git a/web/app/assets/stylesheets/users/signinDialog.css.scss b/web/app/assets/stylesheets/users/signinDialog.css.scss new file mode 100644 index 000000000..3bcb8cc63 --- /dev/null +++ b/web/app/assets/stylesheets/users/signinDialog.css.scss @@ -0,0 +1,56 @@ +#signin-dialog { + height:auto; +} + +#signin-dialog { + + div.field { + width:100%; + } + + div.overlay-inner { + height:auto; + } + + label { + margin-bottom:2px; + } + + div.email { + margin-top:5px; + } + + div.password { + margin-top:20px; + } + + div.actions { + margin-top:20px; + } + + .login-error { + background-color: #330000; + border: 1px solid #990000; + padding:4px; + + div.actions { + margin-top:10px; + } + } + + .login-error-msg { + display:none; + margin-top:10px; + text-align:center; + color:#F00; + font-size:11px; + } + + .login-error .login-error-msg { + display:block; + } + + input[type=text], input[type=password]{ + box-sizing: border-box; + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/web/web.css b/web/app/assets/stylesheets/web/web.css index 712b85f30..d2d554ed6 100644 --- a/web/app/assets/stylesheets/web/web.css +++ b/web/app/assets/stylesheets/web/web.css @@ -14,4 +14,5 @@ *= require web/recordings *= require web/welcome #= require web/sessions +*= require users/signinDialog */ \ No newline at end of file diff --git a/web/app/controllers/api_auths_controller.rb b/web/app/controllers/api_auths_controller.rb new file mode 100644 index 000000000..00001a7e4 --- /dev/null +++ b/web/app/controllers/api_auths_controller.rb @@ -0,0 +1,22 @@ +class ApiAuthsController < ApiController + + respond_to :json + + def login + user = User.authenticate(params[:email], params[:password]) + + if user.nil? + render :json => {}, :status => 422 + else + if jkclient_agent? + user.update_progression_field(:first_ran_client_at) + end + + @session_only_cookie = !jkclient_agent? && !params[:remember_me] + + sign_in user + + render :json => {}, :status => :ok + end + end +end diff --git a/web/app/controllers/api_mixes_controller.rb b/web/app/controllers/api_mixes_controller.rb index 60944c480..26e9ded8d 100644 --- a/web/app/controllers/api_mixes_controller.rb +++ b/web/app/controllers/api_mixes_controller.rb @@ -20,15 +20,7 @@ class ApiMixesController < ApiController render :json => { :message => "next mix could not be found" }, :status => 403 end end - - def finish - begin - @mix.finish - rescue - render :json => { :message => "mix finish failed" }, :status => 403 - end - respond_with responder: ApiResponder, :status => 204 - end + def download @mix = Mix.find(params[:id]) diff --git a/web/app/controllers/api_sessions_controller.rb b/web/app/controllers/api_sessions_controller.rb new file mode 100644 index 000000000..b2f860747 --- /dev/null +++ b/web/app/controllers/api_sessions_controller.rb @@ -0,0 +1,20 @@ +class ApiSearchController < ApiController + + def login + user = User.authenticate(params[:email], params[:password]) + + if user.nil? + render :json => {}, :status => 422 + else + if jkclient_agent? + user.update_progression_field(:first_ran_client_at) + end + + @session_only_cookie = !jkclient_agent? && 0 == params[:remember_me].to_i + + sign_in user + + render :json => {}, :status => :ok + end + end +end diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index 0565d95b5..01eb5107e 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -27,32 +27,6 @@ class ApiUsersController < ApiController respond_with @user, responder: ApiResponder, :status => 200 end - # this API call is disabled by virtue of it being commented out in routes.rb - # the reason is that it has no captcha, and is therefore a bit abuseable - # if someone wants to use it, please add in captcha or some other bot-protector - def create - # sends email to email account for confirmation - @user = UserManager.new.signup(params[:first_name], - params[:last_name], - params[:email], - params[:password], - params[:password_confirmation], - params[:city], - params[:state], - params[:country], - params[:instruments], - params[:photo_url], - ApplicationHelper.base_uri(request) + "/confirm") - - # check for errors - unless @user.errors.any? - render :json => {}, :status => :ok # an empty response, but 200 OK - else - response.status = :unprocessable_entity - respond_with @user, responder: ApiResponder - end - end - def update @user = User.find(params[:id]) diff --git a/web/app/controllers/clients_controller.rb b/web/app/controllers/clients_controller.rb index 009256e40..d8dde3f7a 100644 --- a/web/app/controllers/clients_controller.rb +++ b/web/app/controllers/clients_controller.rb @@ -35,7 +35,7 @@ class ClientsController < ApplicationController if current_user render :layout => 'client' else - redirect_to "/signin" + redirect_to root_url end end diff --git a/web/app/controllers/sessions_controller.rb b/web/app/controllers/sessions_controller.rb index 45e019b27..019c52b99 100644 --- a/web/app/controllers/sessions_controller.rb +++ b/web/app/controllers/sessions_controller.rb @@ -48,15 +48,10 @@ class SessionsController < ApplicationController # an email and whatnot. # # Also, should we grab their photo from facebook? - user = UserManager.new.signup(remote_ip(), - auth_hash[:info][:first_name], - auth_hash[:info][:last_name], - auth_hash[:info][:email], - nil, - nil, - nil, # instruments - nil, # photo_url - nil) + user = UserManager.new.signup(remote_ip: remote_ip(), + first_name: auth_hash[:info][:first_name], + last_name: auth_hash[:info][:last_name], + email: auth_hash[:info][:email]) # Users who sign up using oauth are presumed to have valid email adddresses. user.confirm_email! @@ -72,18 +67,49 @@ class SessionsController < ApplicationController def oauth_callback + + auth_hash = request.env['omniauth.auth'] + + provider = auth_hash[:provider] + + if provider == 'facebook' + fb_uid = auth_hash[:uid] + token = auth_hash[:credentials][:token] + token_expiration = Time.at(auth_hash[:credentials][:expires_at]) + first_name = auth_hash[:extra][:raw_info][:first_name] + last_name = auth_hash[:extra][:raw_info][:last_name] + email = auth_hash[:extra][:raw_info][:email] + gender = auth_hash[:extra][:raw_info][:gender] + + fb_signup = FacebookSignup.new + fb_signup.uid = fb_uid + fb_signup.token = token + fb_signup.token_expires_at = token_expiration + fb_signup.first_name = first_name + fb_signup.last_name = last_name + fb_signup.email = email + if gender == 'male' + fb_signup.gender = 'M' + elsif gender == 'female' + fb_signup.gender = 'F' + end + fb_signup.save! + + redirect_to "#{signup_path}?facebook_signup=#{fb_signup.lookup_id}" + return + end + if current_user.nil? render :nothing => true, :status => 404 return end - auth_hash = request.env['omniauth.auth'] #authorization = UserAuthorization.find_by_provider_and_uid(auth_hash["provider"], auth_hash["uid"]) # Always make and save a new authorization. This is because they expire, and honestly there's no cost # to just making and saving it. #if authorization.nil? - authorization = current_user.user_authorizations.build :provider => auth_hash[:provider], + authorization = current_user.user_authorizations.build :provider => auth_hash[:provider], :uid => auth_hash[:uid], :token => auth_hash[:credentials][:token], :token_expiration => Time.at(auth_hash[:credentials][:expires_at]) diff --git a/web/app/controllers/users_controller.rb b/web/app/controllers/users_controller.rb index e2aae2132..6c98a031d 100644 --- a/web/app/controllers/users_controller.rb +++ b/web/app/controllers/users_controller.rb @@ -27,6 +27,33 @@ class UsersController < ApplicationController return end + @fb_signup = load_facebook_signup(params) + + + # check if the email specified by @fb_signup already exists in the databse--if so, log them in and redirect + if @fb_signup && @fb_signup.email + user = User.find_by_email_and_email_confirmed(@fb_signup, true) + if user + # update user_authorization for user because this is fresher + user.update_fb_authorization(@fb_signup) + sign_in(user) + redirect_to client_url + return + end + end + + # check if the uid specified by @fb_signup already exists in the databse--if so, log them in and redirect + if @fb_signup && @fb_signup.uid + user_authorization = UserAuthorization.find_by_uid_and_provider(@fb_signup.uid, 'facebook') + # update user_authorization for user because this is fresher + if user_authorization + user_authorization.user.update_fb_authorization(@fb_signup) + sign_in(user_authorization.user) + redirect_to client_url + return + end + end + @invited_user = load_invited_user(params) if !@invited_user.nil? && @invited_user.has_required_email? && @invited_user.accepted @@ -34,7 +61,7 @@ class UsersController < ApplicationController render "already_signed_up", :layout => 'landing' return end - @signup_postback = load_postback(@invited_user) + @signup_postback = load_postback(@invited_user, @fb_signup) load_location(request.remote_ip) @@ -42,21 +69,54 @@ class UsersController < ApplicationController @user.musician = true # default the UI to musician as selected option # preseed the form with the invited email as a convenience to the user - unless @invited_user.nil? - @user.email = @invited_user.email + @user.email = @invited_user.email unless @invited_user.nil? + + if @fb_signup + @user.email = @fb_signup.email + @user.first_name = @fb_signup.first_name + @user.last_name = @fb_signup.last_name + @user.gender = @fb_signup.gender end render :layout => 'web' end def create + if current_user redirect_to client_url return end + @fb_signup = load_facebook_signup(params) + + # check if the email specified by @fb_signup already exists in the databse--if so, log them in and redirect + if @fb_signup && @fb_signup.email + user = User.find_by_email_and_email_confirmed(@fb_signup, true) + if user + # update user_authorization for user because this is fresher + user.update_fb_authorization(@fb_signup) + sign_in(user) + redirect_to client_url + return + end + end + + # check if the uid specified by @fb_signup already exists in the databse--if so, log them in and redirect + if @fb_signup && @fb_signup.uid + user_authorization = UserAuthorization.find_by_uid_and_provider(@fb_signup.uid, 'facebook') + # update user_authorization for user because this is fresher + if user_authorization + user_authorization.user.update_fb_authorization(@fb_signup) + sign_in(user_authorization.user) + redirect_to client_url + return + end + + end + @invited_user = load_invited_user(params) - @signup_postback = load_postback(@invited_user) + @signup_postback = load_postback(@invited_user, @fb_signup) @user = User.new @@ -73,21 +133,20 @@ class UsersController < ApplicationController terms_of_service = params[:jam_ruby_user][:terms_of_service].nil? ? false : true musician = params[:jam_ruby_user][:musician] - - @user = UserManager.new.signup(request.remote_ip, - params[:jam_ruby_user][:first_name], - params[:jam_ruby_user][:last_name], - params[:jam_ruby_user][:email], - params[:jam_ruby_user][:password], - params[:jam_ruby_user][:password_confirmation], - terms_of_service, - instruments, - birth_date, - location, - musician, - nil, # we don't accept photo url on the signup form yet - @invited_user, - ApplicationHelper.base_uri(request) + "/confirm") + @user = UserManager.new.signup(remote_ip: request.remote_ip, + first_name: params[:jam_ruby_user][:first_name], + last_name: params[:jam_ruby_user][:last_name], + email: params[:jam_ruby_user][:email], + password: params[:jam_ruby_user][:password], + password_confirmation: params[:jam_ruby_user][:password_confirmation], + terms_of_service: terms_of_service, + instruments: instruments, + birth_date: birth_date, + location: location, + musician: musician, + invited_user: @invited_user, + fb_signup: @fb_signup, + signup_confirm_url: ApplicationHelper.base_uri(request) + "/confirm") # check for errors if @user.errors.any? @@ -308,6 +367,12 @@ class UsersController < ApplicationController return Date.new(year.to_i, month.to_i, day.to_i) end + def load_facebook_signup(params) + lookup_id = params[:facebook_signup] + + FacebookSignup.find_by_lookup_id(lookup_id) + end + def load_invited_user(params) # check if this an anonymous request, or result of invitation code invitation_code = params[:invitation_code] @@ -336,11 +401,10 @@ class UsersController < ApplicationController @cities = @location[:state].nil? ? [] : MaxMindManager.cities(@location[:country], @location[:state]) end - def load_postback(invited_user) - if invited_user.nil? - signup_path - else - signup_path + "?invitation_code=" + invited_user.invitation_code - end + def load_postback(invited_user, fb_signup) + query = {} + query[:invitation_code] = invited_user.invitation_code if invited_user + query[:facebook_signup] = fb_signup.lookup_id if fb_signup + signup_path + "?" + params.to_query end end diff --git a/web/app/views/api_claimed_recordings/show.rabl b/web/app/views/api_claimed_recordings/show.rabl index f23967a14..c487f0d22 100644 --- a/web/app/views/api_claimed_recordings/show.rabl +++ b/web/app/views/api_claimed_recordings/show.rabl @@ -18,7 +18,7 @@ child(:recording => :recording) { } child(:mixes => :mixes) { - attributes :id, :url, :is_completed + attributes :id, :mp3_url, :ogg_url :is_completed } child(:recorded_tracks => :recorded_tracks) { diff --git a/web/app/views/api_music_sessions/show.rabl b/web/app/views/api_music_sessions/show.rabl index 54a4b5e83..3c49ffc1b 100644 --- a/web/app/views/api_music_sessions/show.rabl +++ b/web/app/views/api_music_sessions/show.rabl @@ -53,7 +53,7 @@ node(:claimed_recording, :if => lambda { |music_session| music_session.users.exi } child(:mixes => :mixes) { - attributes :id, :url, :is_completed + attributes :id, :mp3_url, :ogg_url, :is_completed } child(:recorded_tracks => :recorded_tracks) { diff --git a/web/app/views/clients/_shareDialog.html.erb b/web/app/views/clients/_shareDialog.html.erb index 84f5699b5..9c411df14 100644 --- a/web/app/views/clients/_shareDialog.html.erb +++ b/web/app/views/clients/_shareDialog.html.erb @@ -1,6 +1,6 @@ -
-

share this

+
+

@@ -16,7 +16,7 @@ <%= image_tag "content/icon_google.png", :size => "24x24", :align => "absmiddle", :alt => "", :style => "vertical-align:middle", :class => "share-with-google" %> 
- +
diff --git a/web/app/views/layouts/web.erb b/web/app/views/layouts/web.erb index 58c2b22a4..e167c9434 100644 --- a/web/app/views/layouts/web.erb +++ b/web/app/views/layouts/web.erb @@ -50,6 +50,7 @@ <%= render "clients/invitationDialog" %> <%= render "users/signupDialog" %> + <%= render "users/signinDialog" %>